fetta 1.4.3 → 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.
@@ -0,0 +1,1762 @@
1
+ // src/internal/kerningUpkeep.ts
2
+ var CONTEXTUAL_SCRIPT_REGEX = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF\u0590-\u05FF\uFB1D-\uFB4F\u0E00-\u0E7F\u0900-\u097F\u0980-\u09FF\u0A00-\u0A7F\u0A80-\u0AFF\u0B00-\u0B7F\u0B80-\u0BFF\u0C00-\u0C7F\u0C80-\u0CFF\u0D00-\u0D7F]/;
3
+ function hasContextualScript(chars) {
4
+ return chars.some((char) => CONTEXTUAL_SCRIPT_REGEX.test(char));
5
+ }
6
+ var KERNING_STYLE_PROPS = [
7
+ "font",
8
+ "font-family",
9
+ "font-size",
10
+ "font-style",
11
+ "font-weight",
12
+ "font-variant",
13
+ "line-height",
14
+ "font-kerning",
15
+ "font-variant-ligatures",
16
+ "font-feature-settings",
17
+ "font-variation-settings",
18
+ "font-optical-sizing",
19
+ "font-size-adjust",
20
+ "font-stretch",
21
+ "font-variant-caps",
22
+ "font-variant-numeric",
23
+ "font-variant-east-asian",
24
+ "font-synthesis",
25
+ "font-synthesis-weight",
26
+ "font-synthesis-style",
27
+ "letter-spacing",
28
+ "word-spacing",
29
+ "text-rendering",
30
+ "text-transform",
31
+ "direction",
32
+ "unicode-bidi",
33
+ "writing-mode",
34
+ "text-orientation",
35
+ "text-combine-upright"
36
+ ];
37
+ var KERNING_STYLE_PROPS_SET = new Set(KERNING_STYLE_PROPS);
38
+ function copyKerningStyles(target, styles) {
39
+ for (let i = 0; i < styles.length; i++) {
40
+ const prop = styles[i];
41
+ if (!KERNING_STYLE_PROPS_SET.has(prop) && !prop.startsWith("font-")) {
42
+ continue;
43
+ }
44
+ const value = styles.getPropertyValue(prop);
45
+ if (value) target.style.setProperty(prop, value);
46
+ }
47
+ }
48
+ function buildKerningStyleKey(styles) {
49
+ return KERNING_STYLE_PROPS.map((prop) => styles.getPropertyValue(prop)).join(
50
+ "|"
51
+ );
52
+ }
53
+ var isSafariBrowser = null;
54
+ function isSafari() {
55
+ if (isSafariBrowser !== null) return isSafariBrowser;
56
+ if (typeof navigator === "undefined") return false;
57
+ isSafariBrowser = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
58
+ return isSafariBrowser;
59
+ }
60
+ var kerningMeasureRoots = /* @__PURE__ */ new WeakMap();
61
+ function getKerningMeasureRoot(doc) {
62
+ var _a;
63
+ const existing = kerningMeasureRoots.get(doc);
64
+ if (existing && existing.isConnected) return existing;
65
+ const host = (_a = doc.body) != null ? _a : doc.documentElement;
66
+ if (!host) return null;
67
+ const root = doc.createElement("div");
68
+ root.setAttribute("data-fetta-kerning-root", "true");
69
+ root.style.cssText = [
70
+ "position:fixed",
71
+ "left:0",
72
+ "top:0",
73
+ "visibility:hidden",
74
+ "pointer-events:none",
75
+ "z-index:-2147483647",
76
+ "contain:layout paint"
77
+ ].join(";");
78
+ host.appendChild(root);
79
+ kerningMeasureRoots.set(doc, root);
80
+ return root;
81
+ }
82
+ function measureKerningDOM(measureRoot, styleSource, chars, styles) {
83
+ const kerningMap = /* @__PURE__ */ new Map();
84
+ if (chars.length < 2) return kerningMap;
85
+ const doc = styleSource.ownerDocument;
86
+ const measurer = doc.createElement("span");
87
+ measurer.style.cssText = `
88
+ position: absolute;
89
+ visibility: hidden;
90
+ white-space: pre;
91
+ `;
92
+ const computedStyles = styles != null ? styles : getComputedStyle(styleSource);
93
+ copyKerningStyles(measurer, computedStyles);
94
+ const webkitSmoothing = computedStyles.webkitFontSmoothing || computedStyles["-webkit-font-smoothing"];
95
+ const mozSmoothing = computedStyles.MozOsxFontSmoothing || computedStyles["-moz-osx-font-smoothing"];
96
+ if (webkitSmoothing) {
97
+ measurer.style.webkitFontSmoothing = webkitSmoothing;
98
+ }
99
+ if (mozSmoothing) {
100
+ measurer.style.MozOsxFontSmoothing = mozSmoothing;
101
+ }
102
+ measureRoot.appendChild(measurer);
103
+ const charWidths = /* @__PURE__ */ new Map();
104
+ for (const char of new Set(chars)) {
105
+ measurer.textContent = char;
106
+ charWidths.set(char, measurer.getBoundingClientRect().width);
107
+ }
108
+ for (let i = 0; i < chars.length - 1; i++) {
109
+ const char1 = chars[i];
110
+ const char2 = chars[i + 1];
111
+ measurer.textContent = char1 + char2;
112
+ const pairWidth = measurer.getBoundingClientRect().width;
113
+ const kerning = pairWidth - charWidths.get(char1) - charWidths.get(char2);
114
+ if (Math.abs(kerning) > 1e-3) {
115
+ kerningMap.set(i + 1, kerning);
116
+ }
117
+ }
118
+ measureRoot.removeChild(measurer);
119
+ return kerningMap;
120
+ }
121
+ function measureKerningRange(measureRoot, styleSource, chars, styles) {
122
+ const kerningMap = /* @__PURE__ */ new Map();
123
+ if (chars.length < 2) return kerningMap;
124
+ const doc = styleSource.ownerDocument;
125
+ const measurer = doc.createElement("span");
126
+ measurer.style.cssText = "position:absolute;visibility:hidden;white-space:pre;";
127
+ const computedStyles = styles != null ? styles : getComputedStyle(styleSource);
128
+ copyKerningStyles(measurer, computedStyles);
129
+ measureRoot.appendChild(measurer);
130
+ const range = doc.createRange();
131
+ const measureWidth = () => {
132
+ const textNode = measurer.firstChild;
133
+ if (!textNode) return 0;
134
+ range.selectNodeContents(textNode);
135
+ return range.getBoundingClientRect().width;
136
+ };
137
+ const charWidths = /* @__PURE__ */ new Map();
138
+ for (const char of new Set(chars)) {
139
+ measurer.textContent = char;
140
+ charWidths.set(char, measureWidth());
141
+ }
142
+ for (let i = 0; i < chars.length - 1; i++) {
143
+ const char1 = chars[i];
144
+ const char2 = chars[i + 1];
145
+ measurer.textContent = char1 + char2;
146
+ const kerning = measureWidth() - charWidths.get(char1) - charWidths.get(char2);
147
+ if (Math.abs(kerning) > 1e-3) {
148
+ kerningMap.set(i + 1, kerning);
149
+ }
150
+ }
151
+ range.detach();
152
+ measureRoot.removeChild(measurer);
153
+ return kerningMap;
154
+ }
155
+ function measureKerning(container, styleSource, chars, styles, isolateKerningMeasurement = true) {
156
+ if (chars.length < 2) return /* @__PURE__ */ new Map();
157
+ if (!styleSource.isConnected) {
158
+ console.warn(
159
+ "splitText: kerning measurement requires a connected DOM element. Skipping kerning."
160
+ );
161
+ return /* @__PURE__ */ new Map();
162
+ }
163
+ const computedStyles = styles != null ? styles : getComputedStyle(styleSource);
164
+ if (!isolateKerningMeasurement) {
165
+ if (!container.isConnected) {
166
+ console.warn(
167
+ "splitText: kerning measurement requires a connected DOM element. Skipping kerning."
168
+ );
169
+ return /* @__PURE__ */ new Map();
170
+ }
171
+ return isSafari() ? measureKerningDOM(container, styleSource, chars, computedStyles) : measureKerningRange(container, styleSource, chars, computedStyles);
172
+ }
173
+ const measureRoot = getKerningMeasureRoot(styleSource.ownerDocument);
174
+ if (!measureRoot) {
175
+ if (!container.isConnected) {
176
+ console.warn(
177
+ "splitText: kerning measurement requires a connected DOM element. Skipping kerning."
178
+ );
179
+ return /* @__PURE__ */ new Map();
180
+ }
181
+ return isSafari() ? measureKerningDOM(container, styleSource, chars, computedStyles) : measureKerningRange(container, styleSource, chars, computedStyles);
182
+ }
183
+ return isSafari() ? measureKerningDOM(measureRoot, styleSource, chars, computedStyles) : measureKerningRange(measureRoot, styleSource, chars, computedStyles);
184
+ }
185
+ function classSelector(className) {
186
+ const tokens = className.split(/\s+/).filter(Boolean);
187
+ if (tokens.length === 0) return "";
188
+ return `.${tokens.join(".")}`;
189
+ }
190
+ function querySplitWords(element, wordClass) {
191
+ const selector = classSelector(wordClass);
192
+ if (!selector) return [];
193
+ return Array.from(element.querySelectorAll(selector));
194
+ }
195
+ function hasNoSpaceBefore(wordSpan) {
196
+ return wordSpan.dataset.fettaNoSpaceBefore === "true";
197
+ }
198
+ function hasHardBreakBefore(wordSpan) {
199
+ return wordSpan.dataset.fettaHardBreakBefore === "true";
200
+ }
201
+ function getCharKerningTarget(charSpan, mask) {
202
+ if (mask === "chars" && charSpan.parentElement) {
203
+ return charSpan.parentElement;
204
+ }
205
+ return charSpan;
206
+ }
207
+ function getWordKerningTarget(wordSpan, mask) {
208
+ if (mask === "words" && wordSpan.parentElement) {
209
+ return wordSpan.parentElement;
210
+ }
211
+ return wordSpan;
212
+ }
213
+ function clearKerningCompensation(allWords, charClass, splitChars, splitWords, mask) {
214
+ if (splitChars) {
215
+ const charSelector = classSelector(charClass);
216
+ if (!charSelector) return;
217
+ for (const wordSpan of allWords) {
218
+ const wordChars = Array.from(
219
+ wordSpan.querySelectorAll(charSelector)
220
+ );
221
+ for (const charSpan of wordChars) {
222
+ const targetElement = getCharKerningTarget(charSpan, mask);
223
+ targetElement.style.marginLeft = "";
224
+ }
225
+ }
226
+ return;
227
+ }
228
+ if (splitWords) {
229
+ for (const wordSpan of allWords) {
230
+ const targetElement = getWordKerningTarget(wordSpan, mask);
231
+ targetElement.style.marginLeft = "";
232
+ }
233
+ }
234
+ }
235
+ function applyKerningCompensation(element, allWords, charClass, splitChars, splitWords, options) {
236
+ if (options == null ? void 0 : options.disableKerning) return;
237
+ if (splitChars && allWords.length > 0) {
238
+ const charSelector = classSelector(charClass);
239
+ if (!charSelector) return;
240
+ for (const wordSpan of allWords) {
241
+ const wordChars = Array.from(
242
+ wordSpan.querySelectorAll(charSelector)
243
+ );
244
+ if (wordChars.length < 2) continue;
245
+ const charStringsForCheck = wordChars.map((char) => char.textContent || "");
246
+ if (hasContextualScript(charStringsForCheck)) continue;
247
+ const styleGroups = [];
248
+ const firstCharStyles = getComputedStyle(wordChars[0]);
249
+ let currentKey = buildKerningStyleKey(firstCharStyles);
250
+ let currentGroup = {
251
+ chars: [wordChars[0]],
252
+ styleSource: wordChars[0],
253
+ styles: firstCharStyles
254
+ };
255
+ for (let i = 1; i < wordChars.length; i++) {
256
+ const char = wordChars[i];
257
+ const charStyles = getComputedStyle(char);
258
+ const key = buildKerningStyleKey(charStyles);
259
+ if (key === currentKey) {
260
+ currentGroup.chars.push(char);
261
+ } else {
262
+ styleGroups.push(currentGroup);
263
+ currentKey = key;
264
+ currentGroup = { chars: [char], styleSource: char, styles: charStyles };
265
+ }
266
+ }
267
+ styleGroups.push(currentGroup);
268
+ for (const group of styleGroups) {
269
+ if (group.chars.length < 2) continue;
270
+ const charStrings = group.chars.map((char) => char.textContent || "");
271
+ const kerningMap = measureKerning(
272
+ element,
273
+ group.styleSource,
274
+ charStrings,
275
+ group.styles,
276
+ (options == null ? void 0 : options.isolateKerningMeasurement) !== false
277
+ );
278
+ for (const [charIndex, kerning] of kerningMap) {
279
+ const charSpan = group.chars[charIndex];
280
+ if (charSpan && Math.abs(kerning) < 20) {
281
+ const targetElement = getCharKerningTarget(charSpan, options == null ? void 0 : options.mask);
282
+ targetElement.style.marginLeft = `${kerning}px`;
283
+ }
284
+ }
285
+ }
286
+ }
287
+ for (let wordIdx = 1; wordIdx < allWords.length; wordIdx++) {
288
+ const currWord = allWords[wordIdx];
289
+ if (hasNoSpaceBefore(currWord) || hasHardBreakBefore(currWord)) {
290
+ continue;
291
+ }
292
+ const prevWord = allWords[wordIdx - 1];
293
+ const prevChars = Array.from(
294
+ prevWord.querySelectorAll(charSelector)
295
+ );
296
+ const currChars = Array.from(
297
+ currWord.querySelectorAll(charSelector)
298
+ );
299
+ if (prevChars.length === 0 || currChars.length === 0) continue;
300
+ const lastCharSpan = prevChars[prevChars.length - 1];
301
+ const firstCharSpan = currChars[0];
302
+ const lastChar = lastCharSpan.textContent || "";
303
+ const firstChar = firstCharSpan.textContent || "";
304
+ if (!lastChar || !firstChar) continue;
305
+ if (hasContextualScript([lastChar, firstChar])) continue;
306
+ const styles = getComputedStyle(firstCharSpan);
307
+ const kerningMap = measureKerning(
308
+ element,
309
+ firstCharSpan,
310
+ [lastChar, " ", firstChar],
311
+ styles,
312
+ (options == null ? void 0 : options.isolateKerningMeasurement) !== false
313
+ );
314
+ let totalKerning = 0;
315
+ if (kerningMap.has(1)) totalKerning += kerningMap.get(1);
316
+ if (kerningMap.has(2)) totalKerning += kerningMap.get(2);
317
+ if (Math.abs(totalKerning) > 1e-3 && Math.abs(totalKerning) < 20) {
318
+ const targetElement = getCharKerningTarget(firstCharSpan, options == null ? void 0 : options.mask);
319
+ targetElement.style.marginLeft = `${totalKerning}px`;
320
+ }
321
+ }
322
+ return;
323
+ }
324
+ if (splitWords && allWords.length > 1) {
325
+ for (let wordIdx = 1; wordIdx < allWords.length; wordIdx++) {
326
+ const currWord = allWords[wordIdx];
327
+ if (hasNoSpaceBefore(currWord) || hasHardBreakBefore(currWord)) {
328
+ continue;
329
+ }
330
+ const prevWord = allWords[wordIdx - 1];
331
+ const prevText = prevWord.textContent || "";
332
+ const currText = currWord.textContent || "";
333
+ if (!prevText || !currText) continue;
334
+ const lastChar = prevText[prevText.length - 1];
335
+ const firstChar = currText[0];
336
+ if (hasContextualScript([lastChar, firstChar])) continue;
337
+ const styles = getComputedStyle(currWord);
338
+ const kerningMap = measureKerning(
339
+ element,
340
+ currWord,
341
+ [lastChar, " ", firstChar],
342
+ styles,
343
+ (options == null ? void 0 : options.isolateKerningMeasurement) !== false
344
+ );
345
+ let totalKerning = 0;
346
+ if (kerningMap.has(1)) totalKerning += kerningMap.get(1);
347
+ if (kerningMap.has(2)) totalKerning += kerningMap.get(2);
348
+ if (Math.abs(totalKerning) > 1e-3 && Math.abs(totalKerning) < 20) {
349
+ const targetElement = getWordKerningTarget(currWord, options == null ? void 0 : options.mask);
350
+ targetElement.style.marginLeft = `${totalKerning}px`;
351
+ }
352
+ }
353
+ }
354
+ }
355
+
356
+ // src/internal/autoSplitResize.ts
357
+ var TARGET_COUPLING_EPSILON_PX = 0.5;
358
+ var WIDTH_CHANGE_EPSILON_PX = 0.05;
359
+ function getRenderableWidth(element) {
360
+ const width = element.getBoundingClientRect().width;
361
+ return Number.isFinite(width) ? width : 0;
362
+ }
363
+ function getSplitElementParent(element) {
364
+ let target = element.parentElement;
365
+ if (!target) return null;
366
+ if (target.dataset.fettaAutoSplitWrapper === "true" && target.parentElement instanceof HTMLElement) {
367
+ target = target.parentElement;
368
+ }
369
+ return target;
370
+ }
371
+ function resolveAutoSplitTargets(splitElement) {
372
+ const baseTarget = getSplitElementParent(splitElement);
373
+ if (!baseTarget) return [];
374
+ const targets = [baseTarget];
375
+ if (baseTarget.parentElement instanceof HTMLElement) {
376
+ const targetWidth = getRenderableWidth(baseTarget);
377
+ const elementWidth = getRenderableWidth(splitElement);
378
+ if (Math.abs(targetWidth - elementWidth) < TARGET_COUPLING_EPSILON_PX) {
379
+ targets.push(baseTarget.parentElement);
380
+ }
381
+ }
382
+ return Array.from(new Set(targets));
383
+ }
384
+ function getObservedWidth(entry, target) {
385
+ var _a;
386
+ const entryWidth = (_a = entry == null ? void 0 : entry.contentRect) == null ? void 0 : _a.width;
387
+ if (typeof entryWidth === "number" && Number.isFinite(entryWidth)) {
388
+ return entryWidth;
389
+ }
390
+ if (!target) return null;
391
+ const targetWidth = getRenderableWidth(target);
392
+ return Number.isFinite(targetWidth) ? targetWidth : null;
393
+ }
394
+ function recordWidthChange(widthByTarget, target, nextWidth, epsilon = WIDTH_CHANGE_EPSILON_PX) {
395
+ const previousWidth = widthByTarget.get(target);
396
+ widthByTarget.set(target, nextWidth);
397
+ if (previousWidth === void 0) return false;
398
+ return Math.abs(previousWidth - nextWidth) > epsilon;
399
+ }
400
+ function resolveAutoSplitWidth(targets, widthByTarget, changedTarget) {
401
+ const orderedTargets = [];
402
+ if (changedTarget && targets.includes(changedTarget)) {
403
+ orderedTargets.push(changedTarget);
404
+ }
405
+ for (const target of targets) {
406
+ if (!orderedTargets.includes(target)) {
407
+ orderedTargets.push(target);
408
+ }
409
+ }
410
+ for (const target of orderedTargets) {
411
+ const measuredWidth = widthByTarget.get(target);
412
+ if (typeof measuredWidth === "number" && Number.isFinite(measuredWidth)) {
413
+ return measuredWidth;
414
+ }
415
+ const fallbackWidth = target.offsetWidth;
416
+ if (Number.isFinite(fallbackWidth)) {
417
+ return fallbackWidth;
418
+ }
419
+ }
420
+ return 0;
421
+ }
422
+
423
+ // src/core/splitText.ts
424
+ var ARIA_LABEL_ALLOWED_TAGS = /* @__PURE__ */ new Set([
425
+ "h1",
426
+ "h2",
427
+ "h3",
428
+ "h4",
429
+ "h5",
430
+ "h6",
431
+ "a",
432
+ "button",
433
+ "img",
434
+ "input",
435
+ "select",
436
+ "textarea",
437
+ "table",
438
+ "figure",
439
+ "form",
440
+ "fieldset",
441
+ "dialog",
442
+ "details",
443
+ "section",
444
+ "article",
445
+ "nav",
446
+ "aside",
447
+ "header",
448
+ "footer",
449
+ "main"
450
+ ]);
451
+ function splitTextData(element, rawOptions = {}) {
452
+ var _a, _b;
453
+ const options = rawOptions;
454
+ const {
455
+ type = "chars,words,lines",
456
+ charClass = "split-char",
457
+ wordClass = "split-word",
458
+ lineClass = "split-line",
459
+ mask,
460
+ propIndex = false,
461
+ disableKerning = false,
462
+ initialStyles,
463
+ initialClasses
464
+ } = options;
465
+ const isolateKerningMeasurement = options.isolateKerningMeasurement !== false;
466
+ if (!(element instanceof HTMLElement)) {
467
+ throw new Error("splitTextData: element must be an HTMLElement");
468
+ }
469
+ const text = (_b = (_a = element.textContent) == null ? void 0 : _a.trim()) != null ? _b : "";
470
+ if (!text) {
471
+ console.warn("splitTextData: element has no text content");
472
+ return {
473
+ nodes: [],
474
+ meta: {
475
+ text: "",
476
+ type,
477
+ mask,
478
+ charClass,
479
+ wordClass,
480
+ lineClass,
481
+ propIndex,
482
+ useAriaLabel: false,
483
+ ariaLabel: null
484
+ }
485
+ };
486
+ }
487
+ const originalHTML = element.innerHTML;
488
+ const originalAriaLabel = element.getAttribute("aria-label");
489
+ const originalStyle = element.getAttribute("style");
490
+ let splitChars = type.includes("chars");
491
+ let splitWords = type.includes("words");
492
+ let splitLines = type.includes("lines");
493
+ if (!splitChars && !splitWords && !splitLines) {
494
+ console.warn(
495
+ 'splitTextData: type must include at least one of: chars, words, lines. Defaulting to "chars,words,lines".'
496
+ );
497
+ splitChars = splitWords = splitLines = true;
498
+ }
499
+ if (splitChars) {
500
+ element.style.fontVariantLigatures = "none";
501
+ }
502
+ const trackAncestors = hasInlineDescendants(element);
503
+ const measuredWords = collectTextStructure(element, trackAncestors);
504
+ const useAriaLabel = !trackAncestors && ARIA_LABEL_ALLOWED_TAGS.has(element.tagName.toLowerCase());
505
+ performSplit(
506
+ element,
507
+ measuredWords,
508
+ charClass,
509
+ wordClass,
510
+ lineClass,
511
+ splitChars,
512
+ splitWords,
513
+ splitLines,
514
+ {
515
+ propIndex,
516
+ mask,
517
+ ariaHidden: useAriaLabel,
518
+ disableKerning,
519
+ isolateKerningMeasurement,
520
+ initialStyles,
521
+ initialClasses
522
+ }
523
+ );
524
+ if (trackAncestors) {
525
+ injectSrOnlyStyles();
526
+ const visualWrapper = document.createElement("span");
527
+ visualWrapper.setAttribute("aria-hidden", "true");
528
+ visualWrapper.dataset.fettaVisual = "true";
529
+ while (element.firstChild) {
530
+ visualWrapper.appendChild(element.firstChild);
531
+ }
532
+ element.appendChild(visualWrapper);
533
+ element.appendChild(createScreenReaderCopy(originalHTML));
534
+ } else if (useAriaLabel) {
535
+ if (originalAriaLabel === null) {
536
+ element.setAttribute("aria-label", text);
537
+ }
538
+ } else {
539
+ injectSrOnlyStyles();
540
+ const visualWrapper = document.createElement("span");
541
+ visualWrapper.setAttribute("aria-hidden", "true");
542
+ visualWrapper.dataset.fettaVisual = "true";
543
+ while (element.firstChild) {
544
+ visualWrapper.appendChild(element.firstChild);
545
+ }
546
+ element.appendChild(visualWrapper);
547
+ element.appendChild(createScreenReaderCopy(originalHTML));
548
+ }
549
+ const nodes = serializeChildren(element, { charClass, wordClass, lineClass });
550
+ const ariaLabel = element.getAttribute("aria-label");
551
+ element.innerHTML = originalHTML;
552
+ if (originalAriaLabel !== null) {
553
+ element.setAttribute("aria-label", originalAriaLabel);
554
+ } else {
555
+ element.removeAttribute("aria-label");
556
+ }
557
+ if (originalStyle !== null) {
558
+ element.setAttribute("style", originalStyle);
559
+ } else {
560
+ element.removeAttribute("style");
561
+ }
562
+ return {
563
+ nodes,
564
+ meta: {
565
+ text,
566
+ type,
567
+ mask,
568
+ charClass,
569
+ wordClass,
570
+ lineClass,
571
+ propIndex,
572
+ useAriaLabel,
573
+ ariaLabel
574
+ }
575
+ };
576
+ }
577
+ var BREAK_CHARS = /* @__PURE__ */ new Set([
578
+ "\u2014",
579
+ // em-dash
580
+ "\u2013",
581
+ // en-dash
582
+ "-",
583
+ // hyphen
584
+ "/",
585
+ // slash
586
+ "\u2012",
587
+ // figure dash (U+2012)
588
+ "\u2015"
589
+ // horizontal bar (U+2015)
590
+ ]);
591
+ var INLINE_ELEMENTS = /* @__PURE__ */ new Set([
592
+ "a",
593
+ "abbr",
594
+ "acronym",
595
+ "b",
596
+ "bdi",
597
+ "bdo",
598
+ "big",
599
+ "cite",
600
+ "code",
601
+ "data",
602
+ "del",
603
+ "dfn",
604
+ "em",
605
+ "i",
606
+ "ins",
607
+ "kbd",
608
+ "mark",
609
+ "q",
610
+ "s",
611
+ "samp",
612
+ "small",
613
+ "span",
614
+ "strong",
615
+ "sub",
616
+ "sup",
617
+ "time",
618
+ "u",
619
+ "var"
620
+ ]);
621
+ var srOnlyStylesInjected = false;
622
+ function injectSrOnlyStyles() {
623
+ if (srOnlyStylesInjected || typeof document === "undefined") return;
624
+ const style = document.createElement("style");
625
+ style.textContent = `
626
+ .fetta-sr-only {
627
+ position: absolute;
628
+ width: 1px;
629
+ height: 1px;
630
+ padding: 0;
631
+ margin: -1px;
632
+ overflow: hidden;
633
+ clip-path: inset(50%);
634
+ white-space: nowrap;
635
+ border-width: 0;
636
+ }`;
637
+ document.head.appendChild(style);
638
+ srOnlyStylesInjected = true;
639
+ }
640
+ function createScreenReaderCopy(originalHTML) {
641
+ const srCopy = document.createElement("span");
642
+ srCopy.className = "fetta-sr-only";
643
+ srCopy.innerHTML = originalHTML;
644
+ srCopy.dataset.fettaSrCopy = "true";
645
+ return srCopy;
646
+ }
647
+ function hasInlineDescendants(element) {
648
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT);
649
+ let node;
650
+ while (node = walker.nextNode()) {
651
+ if (INLINE_ELEMENTS.has(node.tagName.toLowerCase())) {
652
+ return true;
653
+ }
654
+ }
655
+ return false;
656
+ }
657
+ function ancestorChainsEqual(a, b) {
658
+ if (a.length !== b.length) return false;
659
+ for (let i = 0; i < a.length; i++) {
660
+ if (a[i].instanceId !== b[i].instanceId) return false;
661
+ }
662
+ return true;
663
+ }
664
+ function groupCharsByAncestors(chars) {
665
+ if (chars.length === 0) return [];
666
+ const groups = [];
667
+ let currentGroup = [chars[0]];
668
+ let currentAncestors = chars[0].ancestors;
669
+ for (let i = 1; i < chars.length; i++) {
670
+ const char = chars[i];
671
+ if (ancestorChainsEqual(char.ancestors, currentAncestors)) {
672
+ currentGroup.push(char);
673
+ } else {
674
+ groups.push({ ancestors: currentAncestors, chars: currentGroup });
675
+ currentGroup = [char];
676
+ currentAncestors = char.ancestors;
677
+ }
678
+ }
679
+ groups.push({ ancestors: currentAncestors, chars: currentGroup });
680
+ return groups;
681
+ }
682
+ function cloneAncestorAsWrapper(info) {
683
+ const el = document.createElement(info.tagName);
684
+ info.attributes.forEach((value, key) => {
685
+ el.setAttribute(key, value);
686
+ });
687
+ return el;
688
+ }
689
+ function wrapInAncestors(content, ancestors) {
690
+ if (ancestors.length === 0) return content;
691
+ let wrapped = content;
692
+ for (let i = 0; i < ancestors.length; i++) {
693
+ const wrapper = cloneAncestorAsWrapper(ancestors[i]);
694
+ wrapper.appendChild(wrapped);
695
+ wrapped = wrapper;
696
+ }
697
+ return wrapped;
698
+ }
699
+ function hasAllClasses(element, className) {
700
+ if (!className) return false;
701
+ const tokens = className.split(/\s+/).filter(Boolean);
702
+ if (tokens.length === 0) return false;
703
+ return tokens.every((token) => element.classList.contains(token));
704
+ }
705
+ function getSplitRole(element, classInfo) {
706
+ if (hasAllClasses(element, classInfo.charClass)) return "char";
707
+ if (hasAllClasses(element, classInfo.wordClass)) return "word";
708
+ if (hasAllClasses(element, classInfo.lineClass)) return "line";
709
+ return void 0;
710
+ }
711
+ function serializeNode(node, classInfo) {
712
+ var _a;
713
+ if (node.nodeType === Node.TEXT_NODE) {
714
+ return {
715
+ type: "text",
716
+ text: (_a = node.textContent) != null ? _a : ""
717
+ };
718
+ }
719
+ if (node.nodeType !== Node.ELEMENT_NODE) return null;
720
+ const element = node;
721
+ const attrs = {};
722
+ for (const attr of Array.from(element.attributes)) {
723
+ attrs[attr.name] = attr.value;
724
+ }
725
+ const children = [];
726
+ element.childNodes.forEach((child) => {
727
+ const serialized = serializeNode(child, classInfo);
728
+ if (serialized) children.push(serialized);
729
+ });
730
+ const split = getSplitRole(element, classInfo);
731
+ return {
732
+ type: "element",
733
+ tag: element.tagName.toLowerCase(),
734
+ attrs,
735
+ children,
736
+ split
737
+ };
738
+ }
739
+ function serializeChildren(element, classInfo) {
740
+ const nodes = [];
741
+ element.childNodes.forEach((child) => {
742
+ const serialized = serializeNode(child, classInfo);
743
+ if (serialized) nodes.push(serialized);
744
+ });
745
+ return nodes;
746
+ }
747
+ function normalizeToPromise(value) {
748
+ if (!value) return null;
749
+ if (value instanceof Promise) return value;
750
+ if (typeof value === "object") {
751
+ if ("finished" in value) {
752
+ return value.finished;
753
+ }
754
+ if ("then" in value && typeof value.then === "function") {
755
+ return Promise.resolve(value);
756
+ }
757
+ }
758
+ if (Array.isArray(value)) {
759
+ const promises = value.map(normalizeToPromise).filter((p) => p !== null);
760
+ return promises.length ? Promise.all(promises) : null;
761
+ }
762
+ return null;
763
+ }
764
+ var segmenterCache = null;
765
+ function segmentGraphemes(text) {
766
+ if (!segmenterCache) {
767
+ segmenterCache = new Intl.Segmenter(void 0, { granularity: "grapheme" });
768
+ }
769
+ return [...segmenterCache.segment(text)].map((s) => s.segment);
770
+ }
771
+ function buildAncestorChain(textNode, rootElement, ancestorCache) {
772
+ const ancestors = [];
773
+ let current = textNode.parentNode;
774
+ while (current && current !== rootElement && current instanceof Element) {
775
+ const tagName = current.tagName.toLowerCase();
776
+ if (INLINE_ELEMENTS.has(tagName)) {
777
+ let info = ancestorCache.get(current);
778
+ if (!info) {
779
+ const attributes = /* @__PURE__ */ new Map();
780
+ for (const attr of current.attributes) {
781
+ attributes.set(attr.name, attr.value);
782
+ }
783
+ info = {
784
+ tagName,
785
+ attributes,
786
+ instanceId: /* @__PURE__ */ Symbol()
787
+ };
788
+ ancestorCache.set(current, info);
789
+ }
790
+ ancestors.push(info);
791
+ }
792
+ current = current.parentNode;
793
+ }
794
+ return ancestors;
795
+ }
796
+ function isBoundaryDisplayValue(display) {
797
+ return !(display.startsWith("inline") || display === "contents");
798
+ }
799
+ function isBlockBoundaryElement(element, cache) {
800
+ const cached = cache.get(element);
801
+ if (cached !== void 0) return cached;
802
+ const tagName = element.tagName.toLowerCase();
803
+ let isBoundary = false;
804
+ if (tagName === "br") {
805
+ isBoundary = true;
806
+ } else if (element instanceof HTMLElement) {
807
+ const display = getComputedStyle(element).display;
808
+ isBoundary = isBoundaryDisplayValue(display);
809
+ }
810
+ cache.set(element, isBoundary);
811
+ return isBoundary;
812
+ }
813
+ function getNearestBlockBoundaryAncestor(textNode, rootElement, cache) {
814
+ let current = textNode.parentElement;
815
+ while (current && current !== rootElement) {
816
+ if (isBlockBoundaryElement(current, cache)) {
817
+ if (current.tagName.toLowerCase() !== "br") {
818
+ return current;
819
+ }
820
+ }
821
+ current = current.parentElement;
822
+ }
823
+ return null;
824
+ }
825
+ function collectTextStructure(element, trackAncestors) {
826
+ const words = [];
827
+ const ancestorCache = trackAncestors ? /* @__PURE__ */ new WeakMap() : null;
828
+ const walker = document.createTreeWalker(
829
+ element,
830
+ NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT
831
+ );
832
+ let node;
833
+ let currentWord = [];
834
+ let noSpaceBeforeNext = false;
835
+ let hardBreakBeforeNextWord = false;
836
+ let lastBoundaryOwner;
837
+ const boundaryCache = /* @__PURE__ */ new WeakMap();
838
+ const pushWord = () => {
839
+ if (currentWord.length > 0) {
840
+ words.push({
841
+ chars: currentWord,
842
+ noSpaceBefore: noSpaceBeforeNext,
843
+ hardBreakBefore: hardBreakBeforeNextWord
844
+ });
845
+ currentWord = [];
846
+ noSpaceBeforeNext = false;
847
+ hardBreakBeforeNextWord = false;
848
+ }
849
+ };
850
+ const markHardBreakBeforeNextWord = () => {
851
+ pushWord();
852
+ if (words.length > 0) {
853
+ hardBreakBeforeNextWord = true;
854
+ }
855
+ };
856
+ const emptyAncestors = [];
857
+ while (node = walker.nextNode()) {
858
+ if (node.nodeType === Node.ELEMENT_NODE) {
859
+ const elementNode = node;
860
+ if (elementNode.tagName.toLowerCase() === "br") {
861
+ markHardBreakBeforeNextWord();
862
+ }
863
+ continue;
864
+ }
865
+ if (node.nodeType !== Node.TEXT_NODE) continue;
866
+ const textNode = node;
867
+ const text = textNode.textContent || "";
868
+ const boundaryOwner = getNearestBlockBoundaryAncestor(
869
+ textNode,
870
+ element,
871
+ boundaryCache
872
+ );
873
+ if (lastBoundaryOwner !== void 0 && boundaryOwner !== lastBoundaryOwner) {
874
+ markHardBreakBeforeNextWord();
875
+ }
876
+ const ancestors = trackAncestors ? buildAncestorChain(textNode, element, ancestorCache) : emptyAncestors;
877
+ const graphemes = segmentGraphemes(text);
878
+ let hasVisibleChars = false;
879
+ for (const grapheme of graphemes) {
880
+ if (grapheme === " " || grapheme === "\n" || grapheme === " ") {
881
+ pushWord();
882
+ noSpaceBeforeNext = false;
883
+ continue;
884
+ }
885
+ hasVisibleChars = true;
886
+ currentWord.push({ char: grapheme, ancestors });
887
+ if (BREAK_CHARS.has(grapheme)) {
888
+ pushWord();
889
+ noSpaceBeforeNext = true;
890
+ }
891
+ }
892
+ if (hasVisibleChars) {
893
+ lastBoundaryOwner = boundaryOwner;
894
+ }
895
+ }
896
+ pushWord();
897
+ return words;
898
+ }
899
+ function splitByHardBreak(items, isHardBreakBefore) {
900
+ if (items.length === 0) return [];
901
+ const segments = [];
902
+ let currentSegment = [];
903
+ items.forEach((item, index) => {
904
+ if (index > 0 && isHardBreakBefore(item, index)) {
905
+ if (currentSegment.length > 0) {
906
+ segments.push(currentSegment);
907
+ }
908
+ currentSegment = [item];
909
+ return;
910
+ }
911
+ currentSegment.push(item);
912
+ });
913
+ if (currentSegment.length > 0) {
914
+ segments.push(currentSegment);
915
+ }
916
+ return segments;
917
+ }
918
+ function createSpan(className, index, display = "inline-block", options) {
919
+ const span = document.createElement("span");
920
+ if (className) {
921
+ span.className = className;
922
+ }
923
+ if (index !== void 0 && (options == null ? void 0 : options.propName)) {
924
+ span.setAttribute(`data-${options.propName}-index`, index.toString());
925
+ if (options.propIndex) {
926
+ span.style.setProperty(`--${options.propName}-index`, index.toString());
927
+ }
928
+ }
929
+ span.style.display = display;
930
+ span.style.position = "relative";
931
+ span.style.textDecoration = "inherit";
932
+ if (options == null ? void 0 : options.ariaHidden) {
933
+ span.setAttribute("aria-hidden", "true");
934
+ }
935
+ return span;
936
+ }
937
+ function createMaskWrapper(display = "inline-block") {
938
+ const wrapper = document.createElement("span");
939
+ wrapper.style.display = display;
940
+ wrapper.style.position = "relative";
941
+ wrapper.style.overflow = "clip";
942
+ return wrapper;
943
+ }
944
+ var PROTECTED_STYLES = /* @__PURE__ */ new Set([
945
+ "display",
946
+ "position",
947
+ "textDecoration",
948
+ "fontVariantLigatures"
949
+ ]);
950
+ function applyInitialStyles(elements, style) {
951
+ if (!style || elements.length === 0) return;
952
+ const isFn = typeof style === "function";
953
+ for (let i = 0; i < elements.length; i++) {
954
+ const el = elements[i];
955
+ const styles = isFn ? style(el, i) : style;
956
+ for (const [key, value] of Object.entries(styles)) {
957
+ if (!PROTECTED_STYLES.has(key) && value !== void 0) {
958
+ el.style[key] = value;
959
+ }
960
+ }
961
+ }
962
+ }
963
+ function applyInitialClasses(elements, className) {
964
+ if (!className || elements.length === 0) return;
965
+ const classes = className.split(/\s+/).filter(Boolean);
966
+ for (const el of elements) {
967
+ el.classList.add(...classes);
968
+ }
969
+ }
970
+ function groupIntoLines(elements, element) {
971
+ const fontSize = parseFloat(getComputedStyle(element).fontSize);
972
+ const tolerance = Math.max(5, fontSize * 0.3);
973
+ const lineGroups = [];
974
+ let currentLine = [];
975
+ let currentY = null;
976
+ elements.forEach((el) => {
977
+ const rect = el instanceof HTMLElement ? el.getBoundingClientRect() : el.parentElement.getBoundingClientRect();
978
+ const y = Math.round(rect.top);
979
+ if (currentY === null) {
980
+ currentY = y;
981
+ currentLine.push(el);
982
+ } else if (Math.abs(y - currentY) < tolerance) {
983
+ currentLine.push(el);
984
+ } else {
985
+ lineGroups.push(currentLine);
986
+ currentLine = [el];
987
+ currentY = y;
988
+ }
989
+ });
990
+ if (currentLine.length > 0) {
991
+ lineGroups.push(currentLine);
992
+ }
993
+ return lineGroups;
994
+ }
995
+ function performSplit(element, measuredWords, charClass, wordClass, lineClass, splitChars, splitWords, splitLines, options) {
996
+ var _a, _b;
997
+ element.textContent = "";
998
+ const allChars = [];
999
+ const allWords = [];
1000
+ const needWordWrappers = splitChars || splitWords;
1001
+ if (needWordWrappers) {
1002
+ const noSpaceBeforeSet = /* @__PURE__ */ new Set();
1003
+ const hardBreakBeforeSet = /* @__PURE__ */ new Set();
1004
+ const wordLevelAncestors = /* @__PURE__ */ new Map();
1005
+ let globalCharIndex = 0;
1006
+ measuredWords.forEach((measuredWord, wordIndex) => {
1007
+ const wordSpan = createSpan(wordClass, wordIndex, "inline-block", {
1008
+ propIndex: options == null ? void 0 : options.propIndex,
1009
+ propName: "word",
1010
+ ariaHidden: options == null ? void 0 : options.ariaHidden
1011
+ });
1012
+ if (measuredWord.noSpaceBefore) {
1013
+ noSpaceBeforeSet.add(wordSpan);
1014
+ wordSpan.dataset.fettaNoSpaceBefore = "true";
1015
+ }
1016
+ if (measuredWord.hardBreakBefore) {
1017
+ hardBreakBeforeSet.add(wordSpan);
1018
+ wordSpan.dataset.fettaHardBreakBefore = "true";
1019
+ }
1020
+ if (splitChars) {
1021
+ const hasAnyAncestors = measuredWord.chars.some((c) => c.ancestors.length > 0);
1022
+ if (!hasAnyAncestors) {
1023
+ measuredWord.chars.forEach((measuredChar, charIndexInWord) => {
1024
+ const charSpan = createSpan(charClass, globalCharIndex, "inline-block", {
1025
+ propIndex: options == null ? void 0 : options.propIndex,
1026
+ propName: "char",
1027
+ ariaHidden: options == null ? void 0 : options.ariaHidden
1028
+ });
1029
+ charSpan.textContent = measuredChar.char;
1030
+ globalCharIndex++;
1031
+ if ((options == null ? void 0 : options.mask) === "chars") {
1032
+ const charWrapper = createMaskWrapper("inline-block");
1033
+ charWrapper.appendChild(charSpan);
1034
+ wordSpan.appendChild(charWrapper);
1035
+ } else {
1036
+ wordSpan.appendChild(charSpan);
1037
+ }
1038
+ allChars.push(charSpan);
1039
+ });
1040
+ } else {
1041
+ const charGroups = groupCharsByAncestors(measuredWord.chars);
1042
+ const hasWordLevelAncestors = charGroups.length === 1 && charGroups[0].ancestors.length > 0;
1043
+ if (hasWordLevelAncestors) {
1044
+ wordLevelAncestors.set(wordSpan, charGroups[0].ancestors);
1045
+ }
1046
+ charGroups.forEach((group) => {
1047
+ group.chars.forEach((measuredChar) => {
1048
+ const charSpan = createSpan(charClass, globalCharIndex, "inline-block", {
1049
+ propIndex: options == null ? void 0 : options.propIndex,
1050
+ propName: "char"
1051
+ });
1052
+ charSpan.textContent = measuredChar.char;
1053
+ globalCharIndex++;
1054
+ if ((options == null ? void 0 : options.mask) === "chars") {
1055
+ const charWrapper = createMaskWrapper("inline-block");
1056
+ charWrapper.appendChild(charSpan);
1057
+ wordSpan.appendChild(charWrapper);
1058
+ } else {
1059
+ wordSpan.appendChild(charSpan);
1060
+ }
1061
+ allChars.push(charSpan);
1062
+ });
1063
+ if (!hasWordLevelAncestors && group.ancestors.length > 0) {
1064
+ const charsToWrap = Array.from(wordSpan.childNodes);
1065
+ const lastNChars = charsToWrap.slice(-group.chars.length);
1066
+ lastNChars.forEach((node) => wordSpan.removeChild(node));
1067
+ const fragment = document.createDocumentFragment();
1068
+ lastNChars.forEach((node) => fragment.appendChild(node));
1069
+ const wrapped = wrapInAncestors(fragment, group.ancestors);
1070
+ wordSpan.appendChild(wrapped);
1071
+ }
1072
+ });
1073
+ }
1074
+ } else {
1075
+ const hasAnyAncestors = measuredWord.chars.some((c) => c.ancestors.length > 0);
1076
+ if (!hasAnyAncestors) {
1077
+ wordSpan.textContent = measuredWord.chars.map((c) => c.char).join("");
1078
+ } else {
1079
+ const charGroups = groupCharsByAncestors(measuredWord.chars);
1080
+ const hasWordLevelAncestors = charGroups.length === 1 && charGroups[0].ancestors.length > 0;
1081
+ if (hasWordLevelAncestors) {
1082
+ wordLevelAncestors.set(wordSpan, charGroups[0].ancestors);
1083
+ wordSpan.textContent = measuredWord.chars.map((c) => c.char).join("");
1084
+ } else {
1085
+ charGroups.forEach((group) => {
1086
+ const text = group.chars.map((c) => c.char).join("");
1087
+ const textNode = document.createTextNode(text);
1088
+ if (group.ancestors.length > 0) {
1089
+ const wrapped = wrapInAncestors(textNode, group.ancestors);
1090
+ wordSpan.appendChild(wrapped);
1091
+ } else {
1092
+ wordSpan.appendChild(textNode);
1093
+ }
1094
+ });
1095
+ }
1096
+ }
1097
+ }
1098
+ allWords.push(wordSpan);
1099
+ });
1100
+ const appendBoundaryBeforeWord = (nextWordSpan) => {
1101
+ if (hardBreakBeforeSet.has(nextWordSpan)) {
1102
+ element.appendChild(document.createElement("br"));
1103
+ } else if (!noSpaceBeforeSet.has(nextWordSpan)) {
1104
+ element.appendChild(document.createTextNode(" "));
1105
+ }
1106
+ };
1107
+ let i = 0;
1108
+ while (i < allWords.length) {
1109
+ const wordSpan = allWords[i];
1110
+ const ancestors = wordLevelAncestors.get(wordSpan);
1111
+ if (ancestors && ancestors.length > 0) {
1112
+ const wordGroup = [wordSpan];
1113
+ let j = i + 1;
1114
+ while (j < allWords.length) {
1115
+ const nextWordSpan = allWords[j];
1116
+ const nextAncestors = wordLevelAncestors.get(nextWordSpan);
1117
+ if (hardBreakBeforeSet.has(nextWordSpan)) {
1118
+ break;
1119
+ }
1120
+ if (nextAncestors && ancestorChainsEqual(ancestors, nextAncestors)) {
1121
+ wordGroup.push(nextWordSpan);
1122
+ j++;
1123
+ } else {
1124
+ break;
1125
+ }
1126
+ }
1127
+ const fragment = document.createDocumentFragment();
1128
+ wordGroup.forEach((ws, idx) => {
1129
+ if ((options == null ? void 0 : options.mask) === "words") {
1130
+ const wordWrapper = createMaskWrapper("inline-block");
1131
+ wordWrapper.appendChild(ws);
1132
+ fragment.appendChild(wordWrapper);
1133
+ } else {
1134
+ fragment.appendChild(ws);
1135
+ }
1136
+ if (idx < wordGroup.length - 1 && !noSpaceBeforeSet.has(wordGroup[idx + 1]) && !hardBreakBeforeSet.has(wordGroup[idx + 1])) {
1137
+ fragment.appendChild(document.createTextNode(" "));
1138
+ }
1139
+ });
1140
+ const wrapped = wrapInAncestors(fragment, ancestors);
1141
+ element.appendChild(wrapped);
1142
+ if (j < allWords.length) {
1143
+ appendBoundaryBeforeWord(allWords[j]);
1144
+ }
1145
+ i = j;
1146
+ } else {
1147
+ if ((options == null ? void 0 : options.mask) === "words") {
1148
+ const wordWrapper = createMaskWrapper("inline-block");
1149
+ wordWrapper.appendChild(wordSpan);
1150
+ element.appendChild(wordWrapper);
1151
+ } else {
1152
+ element.appendChild(wordSpan);
1153
+ }
1154
+ if (i < allWords.length - 1) {
1155
+ appendBoundaryBeforeWord(allWords[i + 1]);
1156
+ }
1157
+ i++;
1158
+ }
1159
+ }
1160
+ applyKerningCompensation(
1161
+ element,
1162
+ allWords,
1163
+ charClass,
1164
+ splitChars,
1165
+ splitWords,
1166
+ {
1167
+ disableKerning: options == null ? void 0 : options.disableKerning,
1168
+ isolateKerningMeasurement: options == null ? void 0 : options.isolateKerningMeasurement,
1169
+ mask: options == null ? void 0 : options.mask
1170
+ }
1171
+ );
1172
+ if (splitLines) {
1173
+ const wordSegments = splitByHardBreak(
1174
+ allWords,
1175
+ (wordSpan, index) => index > 0 && hardBreakBeforeSet.has(wordSpan)
1176
+ );
1177
+ const lineGroups = wordSegments.flatMap(
1178
+ (segment) => groupIntoLines(segment, element)
1179
+ );
1180
+ element.textContent = "";
1181
+ const allLines = [];
1182
+ lineGroups.forEach((words, lineIndex) => {
1183
+ const lineSpan = createSpan(lineClass, lineIndex, "block", {
1184
+ propIndex: options == null ? void 0 : options.propIndex,
1185
+ propName: "line",
1186
+ ariaHidden: options == null ? void 0 : options.ariaHidden
1187
+ });
1188
+ allLines.push(lineSpan);
1189
+ let wi = 0;
1190
+ while (wi < words.length) {
1191
+ const wordSpan = words[wi];
1192
+ const ancestors = wordLevelAncestors.get(wordSpan);
1193
+ if (ancestors && ancestors.length > 0) {
1194
+ const wordGroup = [wordSpan];
1195
+ let wj = wi + 1;
1196
+ while (wj < words.length) {
1197
+ const nextWordSpan = words[wj];
1198
+ const nextAncestors = wordLevelAncestors.get(nextWordSpan);
1199
+ if (nextAncestors && ancestorChainsEqual(ancestors, nextAncestors)) {
1200
+ wordGroup.push(nextWordSpan);
1201
+ wj++;
1202
+ } else {
1203
+ break;
1204
+ }
1205
+ }
1206
+ const fragment = document.createDocumentFragment();
1207
+ wordGroup.forEach((ws, idx) => {
1208
+ if ((options == null ? void 0 : options.mask) === "words") {
1209
+ const wordWrapper = createMaskWrapper("inline-block");
1210
+ wordWrapper.appendChild(ws);
1211
+ fragment.appendChild(wordWrapper);
1212
+ } else {
1213
+ fragment.appendChild(ws);
1214
+ }
1215
+ if (idx < wordGroup.length - 1 && !noSpaceBeforeSet.has(wordGroup[idx + 1])) {
1216
+ fragment.appendChild(document.createTextNode(" "));
1217
+ }
1218
+ });
1219
+ const wrapped = wrapInAncestors(fragment, ancestors);
1220
+ lineSpan.appendChild(wrapped);
1221
+ if (wj < words.length && !noSpaceBeforeSet.has(words[wj])) {
1222
+ lineSpan.appendChild(document.createTextNode(" "));
1223
+ }
1224
+ wi = wj;
1225
+ } else {
1226
+ if ((options == null ? void 0 : options.mask) === "words") {
1227
+ const wordWrapper = createMaskWrapper("inline-block");
1228
+ wordWrapper.appendChild(wordSpan);
1229
+ lineSpan.appendChild(wordWrapper);
1230
+ } else {
1231
+ lineSpan.appendChild(wordSpan);
1232
+ }
1233
+ if (wi < words.length - 1 && !noSpaceBeforeSet.has(words[wi + 1])) {
1234
+ lineSpan.appendChild(document.createTextNode(" "));
1235
+ }
1236
+ wi++;
1237
+ }
1238
+ }
1239
+ if ((options == null ? void 0 : options.mask) === "lines") {
1240
+ const lineWrapper = createMaskWrapper("block");
1241
+ lineWrapper.appendChild(lineSpan);
1242
+ element.appendChild(lineWrapper);
1243
+ } else {
1244
+ element.appendChild(lineSpan);
1245
+ }
1246
+ });
1247
+ if (options == null ? void 0 : options.initialStyles) {
1248
+ const { chars, words, lines } = options.initialStyles;
1249
+ if (chars) applyInitialStyles(allChars, chars);
1250
+ if (words) applyInitialStyles(allWords, words);
1251
+ if (lines) applyInitialStyles(allLines, lines);
1252
+ }
1253
+ if (options == null ? void 0 : options.initialClasses) {
1254
+ const { chars, words, lines } = options.initialClasses;
1255
+ if (chars) applyInitialClasses(allChars, chars);
1256
+ if (words) applyInitialClasses(allWords, words);
1257
+ if (lines) applyInitialClasses(allLines, lines);
1258
+ }
1259
+ return {
1260
+ chars: allChars,
1261
+ words: splitWords ? allWords : [],
1262
+ lines: allLines
1263
+ };
1264
+ }
1265
+ if (options == null ? void 0 : options.initialStyles) {
1266
+ const { chars, words } = options.initialStyles;
1267
+ if (chars) applyInitialStyles(allChars, chars);
1268
+ if (words) applyInitialStyles(allWords, words);
1269
+ }
1270
+ if (options == null ? void 0 : options.initialClasses) {
1271
+ const { chars, words } = options.initialClasses;
1272
+ if (chars) applyInitialClasses(allChars, chars);
1273
+ if (words) applyInitialClasses(allWords, words);
1274
+ }
1275
+ return {
1276
+ chars: allChars,
1277
+ words: splitWords ? allWords : [],
1278
+ lines: []
1279
+ };
1280
+ } else {
1281
+ if (splitLines) {
1282
+ const wordWrappers = [];
1283
+ measuredWords.forEach((measuredWord, idx) => {
1284
+ const textNode = document.createTextNode(
1285
+ measuredWord.chars.map((c) => c.char).join("")
1286
+ );
1287
+ const wrapper = document.createElement("span");
1288
+ wrapper.style.display = "inline";
1289
+ wrapper.appendChild(textNode);
1290
+ element.appendChild(wrapper);
1291
+ wordWrappers.push({ wrapper, wordIndex: idx });
1292
+ if (idx < measuredWords.length - 1 && !measuredWords[idx + 1].noSpaceBefore && !measuredWords[idx + 1].hardBreakBefore) {
1293
+ const spaceNode = document.createTextNode(" ");
1294
+ element.appendChild(spaceNode);
1295
+ }
1296
+ });
1297
+ const wordWrapperSegments = splitByHardBreak(
1298
+ wordWrappers,
1299
+ (wordWrapper, index) => index > 0 && measuredWords[wordWrapper.wordIndex].hardBreakBefore
1300
+ );
1301
+ const lineGroups = wordWrapperSegments.flatMap(
1302
+ (segment) => groupIntoLines(
1303
+ segment.map((wordWrapper) => wordWrapper.wrapper),
1304
+ element
1305
+ )
1306
+ );
1307
+ element.textContent = "";
1308
+ const allLines = [];
1309
+ lineGroups.forEach((wrappers, lineIndex) => {
1310
+ const lineSpan = createSpan(lineClass, lineIndex, "block", {
1311
+ propIndex: options == null ? void 0 : options.propIndex,
1312
+ propName: "line"
1313
+ });
1314
+ allLines.push(lineSpan);
1315
+ wrappers.forEach((wrapper, wrapperIdx) => {
1316
+ while (wrapper.firstChild) {
1317
+ lineSpan.appendChild(wrapper.firstChild);
1318
+ }
1319
+ if (wrapperIdx < wrappers.length - 1) {
1320
+ const nextWrapper = wrappers[wrapperIdx + 1];
1321
+ const nextWordInfo = wordWrappers.find((w) => w.wrapper === nextWrapper);
1322
+ if (nextWordInfo && !measuredWords[nextWordInfo.wordIndex].noSpaceBefore) {
1323
+ lineSpan.appendChild(document.createTextNode(" "));
1324
+ }
1325
+ }
1326
+ });
1327
+ if ((options == null ? void 0 : options.mask) === "lines") {
1328
+ const lineWrapper = createMaskWrapper("block");
1329
+ lineWrapper.appendChild(lineSpan);
1330
+ element.appendChild(lineWrapper);
1331
+ } else {
1332
+ element.appendChild(lineSpan);
1333
+ }
1334
+ });
1335
+ if ((_a = options == null ? void 0 : options.initialStyles) == null ? void 0 : _a.lines) {
1336
+ applyInitialStyles(allLines, options.initialStyles.lines);
1337
+ }
1338
+ if ((_b = options == null ? void 0 : options.initialClasses) == null ? void 0 : _b.lines) {
1339
+ applyInitialClasses(allLines, options.initialClasses.lines);
1340
+ }
1341
+ return { chars: [], words: [], lines: allLines };
1342
+ } else {
1343
+ const fullText = measuredWords.map((w) => w.chars.map((c) => c.char).join("")).join(" ");
1344
+ element.textContent = fullText;
1345
+ return { chars: [], words: [], lines: [] };
1346
+ }
1347
+ }
1348
+ }
1349
+ function normalizeLineFingerprintText(value) {
1350
+ return value.replace(/\u00A0/g, " ").replace(/\s+/g, " ").trim();
1351
+ }
1352
+ function buildLineFingerprintFromElements(lines) {
1353
+ if (lines.length === 0) return "";
1354
+ return lines.map((line) => {
1355
+ var _a;
1356
+ return normalizeLineFingerprintText((_a = line.textContent) != null ? _a : "");
1357
+ }).join("\n");
1358
+ }
1359
+ function collectNodeText(node) {
1360
+ if (node.type === "text") return node.text;
1361
+ return node.children.map((child) => collectNodeText(child)).join("");
1362
+ }
1363
+ function collectLineTextsFromData(nodes, lineTexts) {
1364
+ for (const node of nodes) {
1365
+ if (node.type !== "element") continue;
1366
+ if (node.split === "line") {
1367
+ lineTexts.push(normalizeLineFingerprintText(collectNodeText(node)));
1368
+ continue;
1369
+ }
1370
+ collectLineTextsFromData(node.children, lineTexts);
1371
+ }
1372
+ }
1373
+ function buildLineFingerprintFromData(data) {
1374
+ const lineTexts = [];
1375
+ collectLineTextsFromData(data.nodes, lineTexts);
1376
+ return lineTexts.join("\n");
1377
+ }
1378
+ function splitText(element, rawOptions = {}) {
1379
+ var _a;
1380
+ const options = rawOptions;
1381
+ const {
1382
+ type = "chars,words,lines",
1383
+ charClass = "split-char",
1384
+ wordClass = "split-word",
1385
+ lineClass = "split-line",
1386
+ mask,
1387
+ autoSplit = false,
1388
+ onResplit,
1389
+ onSplit,
1390
+ revertOnComplete = false,
1391
+ propIndex = false,
1392
+ disableKerning = false,
1393
+ initialStyles,
1394
+ initialClasses
1395
+ } = options;
1396
+ const isolateKerningMeasurement = options.isolateKerningMeasurement !== false;
1397
+ if (!(element instanceof HTMLElement)) {
1398
+ throw new Error("splitText: element must be an HTMLElement");
1399
+ }
1400
+ const text = (_a = element.textContent) == null ? void 0 : _a.trim();
1401
+ if (!text) {
1402
+ console.warn("splitText: element has no text content");
1403
+ return {
1404
+ chars: [],
1405
+ words: [],
1406
+ lines: [],
1407
+ revert: () => {
1408
+ }
1409
+ };
1410
+ }
1411
+ if (autoSplit && !element.parentElement) {
1412
+ console.warn(
1413
+ "splitText: autoSplit requires a parent element. AutoSplit will not work."
1414
+ );
1415
+ }
1416
+ const originalHTML = element.innerHTML;
1417
+ const originalAriaLabel = element.getAttribute("aria-label");
1418
+ let splitChars = type.includes("chars");
1419
+ let splitWords = type.includes("words");
1420
+ let splitLines = type.includes("lines");
1421
+ if (!splitChars && !splitWords && !splitLines) {
1422
+ console.warn(
1423
+ 'splitText: type must include at least one of: chars, words, lines. Defaulting to "chars,words,lines".'
1424
+ );
1425
+ splitChars = splitWords = splitLines = true;
1426
+ }
1427
+ let isActive = true;
1428
+ let autoSplitObserver = null;
1429
+ let autoSplitDebounceTimer = null;
1430
+ let kerningObserver = null;
1431
+ let kerningAnimationFrame = null;
1432
+ let removeKerningResizeListener = null;
1433
+ let autoSplitWidthByTarget = /* @__PURE__ */ new Map();
1434
+ let lastKerningStyleKey = null;
1435
+ let currentChars = [];
1436
+ let currentWords = [];
1437
+ let currentLines = [];
1438
+ let currentLineFingerprint = "";
1439
+ let useAriaLabel = false;
1440
+ const applyDirectSplit = () => {
1441
+ const trackAncestors = hasInlineDescendants(element);
1442
+ const measuredWords = collectTextStructure(element, trackAncestors);
1443
+ useAriaLabel = !trackAncestors && ARIA_LABEL_ALLOWED_TAGS.has(element.tagName.toLowerCase());
1444
+ const rendered = performSplit(
1445
+ element,
1446
+ measuredWords,
1447
+ charClass,
1448
+ wordClass,
1449
+ lineClass,
1450
+ splitChars,
1451
+ splitWords,
1452
+ splitLines,
1453
+ {
1454
+ propIndex,
1455
+ mask,
1456
+ ariaHidden: useAriaLabel,
1457
+ disableKerning,
1458
+ isolateKerningMeasurement,
1459
+ initialStyles,
1460
+ initialClasses
1461
+ }
1462
+ );
1463
+ currentChars = rendered.chars;
1464
+ currentWords = rendered.words;
1465
+ currentLines = rendered.lines;
1466
+ currentLineFingerprint = splitLines ? buildLineFingerprintFromElements(currentLines) : "";
1467
+ if (trackAncestors) {
1468
+ injectSrOnlyStyles();
1469
+ const visualWrapper = document.createElement("span");
1470
+ visualWrapper.setAttribute("aria-hidden", "true");
1471
+ visualWrapper.dataset.fettaVisual = "true";
1472
+ while (element.firstChild) {
1473
+ visualWrapper.appendChild(element.firstChild);
1474
+ }
1475
+ element.appendChild(visualWrapper);
1476
+ element.appendChild(createScreenReaderCopy(originalHTML));
1477
+ } else if (useAriaLabel) {
1478
+ if (originalAriaLabel === null) {
1479
+ element.setAttribute("aria-label", text);
1480
+ }
1481
+ } else {
1482
+ injectSrOnlyStyles();
1483
+ const visualWrapper = document.createElement("span");
1484
+ visualWrapper.setAttribute("aria-hidden", "true");
1485
+ visualWrapper.dataset.fettaVisual = "true";
1486
+ while (element.firstChild) {
1487
+ visualWrapper.appendChild(element.firstChild);
1488
+ }
1489
+ element.appendChild(visualWrapper);
1490
+ element.appendChild(createScreenReaderCopy(originalHTML));
1491
+ }
1492
+ if (splitChars) {
1493
+ element.style.fontVariantLigatures = "none";
1494
+ }
1495
+ };
1496
+ applyDirectSplit();
1497
+ const supportsKerningUpkeep = !disableKerning && (splitChars || splitWords);
1498
+ const kerningOnlyUpdate = supportsKerningUpkeep && !splitLines;
1499
+ if (supportsKerningUpkeep) {
1500
+ lastKerningStyleKey = buildKerningStyleKey(getComputedStyle(element));
1501
+ }
1502
+ const resolveLineMeasureWidth = (fallbackWidth) => {
1503
+ const safeFallbackWidth = Number.isFinite(fallbackWidth) && fallbackWidth > 0 ? fallbackWidth : 1;
1504
+ const elementWidth = element.getBoundingClientRect().width;
1505
+ if (Number.isFinite(elementWidth) && elementWidth > 0) {
1506
+ return Math.max(elementWidth, safeFallbackWidth);
1507
+ }
1508
+ return safeFallbackWidth;
1509
+ };
1510
+ const measureLineFingerprintForWidth = (width) => {
1511
+ if (!splitLines) return null;
1512
+ if (!element.parentElement) return null;
1513
+ const ownerDocument = element.ownerDocument;
1514
+ const probeHost = ownerDocument.createElement("div");
1515
+ probeHost.setAttribute("data-fetta-auto-split-probe", "true");
1516
+ probeHost.style.position = "fixed";
1517
+ probeHost.style.left = "-99999px";
1518
+ probeHost.style.top = "0";
1519
+ probeHost.style.visibility = "hidden";
1520
+ probeHost.style.pointerEvents = "none";
1521
+ probeHost.style.contain = "layout style paint";
1522
+ probeHost.style.width = `${Math.max(1, width)}px`;
1523
+ const probeElement = element.cloneNode(false);
1524
+ probeElement.innerHTML = originalHTML;
1525
+ probeHost.appendChild(probeElement);
1526
+ element.parentElement.appendChild(probeHost);
1527
+ try {
1528
+ const probeData = splitTextData(probeElement, {
1529
+ type,
1530
+ charClass,
1531
+ wordClass,
1532
+ lineClass,
1533
+ mask,
1534
+ propIndex,
1535
+ disableKerning,
1536
+ isolateKerningMeasurement,
1537
+ initialStyles,
1538
+ initialClasses
1539
+ });
1540
+ return buildLineFingerprintFromData(probeData);
1541
+ } finally {
1542
+ probeHost.remove();
1543
+ }
1544
+ };
1545
+ const runFullResplit = () => {
1546
+ if (!isActive) return;
1547
+ const previousChars = currentChars;
1548
+ const previousWords = currentWords;
1549
+ const previousLines = currentLines;
1550
+ element.innerHTML = originalHTML;
1551
+ applyDirectSplit();
1552
+ if (supportsKerningUpkeep) {
1553
+ lastKerningStyleKey = buildKerningStyleKey(getComputedStyle(element));
1554
+ }
1555
+ const replacedSplitOutput = previousChars !== currentChars || previousWords !== currentWords || previousLines !== currentLines;
1556
+ if (onResplit && replacedSplitOutput) {
1557
+ onResplit({
1558
+ chars: currentChars,
1559
+ words: currentWords,
1560
+ lines: currentLines
1561
+ });
1562
+ }
1563
+ };
1564
+ const dispose = () => {
1565
+ if (!isActive) return;
1566
+ if (autoSplitObserver) {
1567
+ autoSplitObserver.disconnect();
1568
+ autoSplitObserver = null;
1569
+ }
1570
+ autoSplitWidthByTarget = /* @__PURE__ */ new Map();
1571
+ if (kerningObserver) {
1572
+ kerningObserver.disconnect();
1573
+ kerningObserver = null;
1574
+ }
1575
+ if (autoSplitDebounceTimer) {
1576
+ clearTimeout(autoSplitDebounceTimer);
1577
+ autoSplitDebounceTimer = null;
1578
+ }
1579
+ if (kerningAnimationFrame !== null) {
1580
+ const ownerWindow = element.ownerDocument.defaultView;
1581
+ if (ownerWindow) {
1582
+ ownerWindow.cancelAnimationFrame(kerningAnimationFrame);
1583
+ }
1584
+ kerningAnimationFrame = null;
1585
+ }
1586
+ if (removeKerningResizeListener) {
1587
+ removeKerningResizeListener();
1588
+ removeKerningResizeListener = null;
1589
+ }
1590
+ isActive = false;
1591
+ };
1592
+ const revert = () => {
1593
+ if (!isActive) return;
1594
+ element.innerHTML = originalHTML;
1595
+ if (useAriaLabel) {
1596
+ if (originalAriaLabel !== null) {
1597
+ element.setAttribute("aria-label", originalAriaLabel);
1598
+ } else {
1599
+ element.removeAttribute("aria-label");
1600
+ }
1601
+ }
1602
+ if (splitChars) {
1603
+ element.style.fontVariantLigatures = "none";
1604
+ }
1605
+ dispose();
1606
+ };
1607
+ if (supportsKerningUpkeep) {
1608
+ const runKerningUpkeep = () => {
1609
+ if (!isActive) return;
1610
+ if (!element.isConnected) {
1611
+ dispose();
1612
+ return;
1613
+ }
1614
+ const nextKerningStyleKey = buildKerningStyleKey(getComputedStyle(element));
1615
+ if (nextKerningStyleKey === lastKerningStyleKey) return;
1616
+ lastKerningStyleKey = nextKerningStyleKey;
1617
+ if (!kerningOnlyUpdate) {
1618
+ if (!autoSplit) {
1619
+ return;
1620
+ }
1621
+ runFullResplit();
1622
+ return;
1623
+ }
1624
+ const wordsForKerning = querySplitWords(element, wordClass);
1625
+ if (wordsForKerning.length === 0) return;
1626
+ clearKerningCompensation(
1627
+ wordsForKerning,
1628
+ charClass,
1629
+ splitChars,
1630
+ splitWords,
1631
+ mask
1632
+ );
1633
+ applyKerningCompensation(
1634
+ element,
1635
+ wordsForKerning,
1636
+ charClass,
1637
+ splitChars,
1638
+ splitWords,
1639
+ {
1640
+ disableKerning,
1641
+ isolateKerningMeasurement,
1642
+ mask
1643
+ }
1644
+ );
1645
+ };
1646
+ const scheduleKerningUpkeep = () => {
1647
+ if (kerningAnimationFrame !== null) {
1648
+ return;
1649
+ }
1650
+ const ownerWindow2 = element.ownerDocument.defaultView;
1651
+ if (!ownerWindow2) {
1652
+ runKerningUpkeep();
1653
+ return;
1654
+ }
1655
+ kerningAnimationFrame = ownerWindow2.requestAnimationFrame(() => {
1656
+ kerningAnimationFrame = null;
1657
+ runKerningUpkeep();
1658
+ });
1659
+ };
1660
+ kerningObserver = new ResizeObserver(() => {
1661
+ scheduleKerningUpkeep();
1662
+ });
1663
+ kerningObserver.observe(element);
1664
+ const ownerWindow = element.ownerDocument.defaultView;
1665
+ if (ownerWindow) {
1666
+ const handleWindowResize = () => {
1667
+ scheduleKerningUpkeep();
1668
+ };
1669
+ ownerWindow.addEventListener("resize", handleWindowResize);
1670
+ removeKerningResizeListener = () => {
1671
+ ownerWindow.removeEventListener("resize", handleWindowResize);
1672
+ };
1673
+ }
1674
+ }
1675
+ if (autoSplit) {
1676
+ const targets = resolveAutoSplitTargets(element);
1677
+ let lastChangedTarget = null;
1678
+ if (targets.length === 0) {
1679
+ console.warn(
1680
+ "SplitText: autoSplit enabled but no parent element found. AutoSplit will not work."
1681
+ );
1682
+ } else {
1683
+ const handleResize = () => {
1684
+ if (!isActive) return;
1685
+ if (!element.isConnected) {
1686
+ dispose();
1687
+ return;
1688
+ }
1689
+ const currentWidth = resolveAutoSplitWidth(
1690
+ targets,
1691
+ autoSplitWidthByTarget,
1692
+ lastChangedTarget
1693
+ );
1694
+ lastChangedTarget = null;
1695
+ if (splitLines) {
1696
+ const lineMeasureWidth = resolveLineMeasureWidth(currentWidth);
1697
+ const nextLineFingerprint = measureLineFingerprintForWidth(
1698
+ lineMeasureWidth
1699
+ );
1700
+ if (nextLineFingerprint !== null && nextLineFingerprint === currentLineFingerprint) {
1701
+ return;
1702
+ }
1703
+ }
1704
+ runFullResplit();
1705
+ };
1706
+ autoSplitObserver = new ResizeObserver((entries) => {
1707
+ let changed = false;
1708
+ let changedTarget = null;
1709
+ entries.forEach((entry) => {
1710
+ if (!(entry.target instanceof HTMLElement)) return;
1711
+ const nextWidth = getObservedWidth(entry, entry.target);
1712
+ if (nextWidth === null) return;
1713
+ const didChange = recordWidthChange(
1714
+ autoSplitWidthByTarget,
1715
+ entry.target,
1716
+ nextWidth
1717
+ );
1718
+ if (didChange) {
1719
+ changed = true;
1720
+ changedTarget = entry.target;
1721
+ }
1722
+ });
1723
+ if (!changed) return;
1724
+ lastChangedTarget = changedTarget;
1725
+ if (autoSplitDebounceTimer) {
1726
+ clearTimeout(autoSplitDebounceTimer);
1727
+ }
1728
+ autoSplitDebounceTimer = setTimeout(handleResize, 100);
1729
+ });
1730
+ targets.forEach((target) => {
1731
+ autoSplitObserver.observe(target);
1732
+ });
1733
+ }
1734
+ }
1735
+ if (onSplit) {
1736
+ const animationResult = onSplit({
1737
+ chars: currentChars,
1738
+ words: currentWords,
1739
+ lines: currentLines
1740
+ });
1741
+ if (revertOnComplete) {
1742
+ const promise = normalizeToPromise(animationResult);
1743
+ if (promise) {
1744
+ promise.then(() => {
1745
+ if (isActive) {
1746
+ revert();
1747
+ }
1748
+ }).catch(() => {
1749
+ console.warn("[fetta] Animation rejected, text not reverted");
1750
+ });
1751
+ }
1752
+ }
1753
+ }
1754
+ return {
1755
+ chars: currentChars,
1756
+ words: currentWords,
1757
+ lines: currentLines,
1758
+ revert
1759
+ };
1760
+ }
1761
+
1762
+ export { applyKerningCompensation, buildKerningStyleKey, clearKerningCompensation, getObservedWidth, normalizeToPromise, querySplitWords, recordWidthChange, resolveAutoSplitTargets, resolveAutoSplitWidth, splitText, splitTextData };