fetta 1.0.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/LICENSE +21 -0
- package/README.md +267 -0
- package/dist/chunk-KGMU2B53.js +764 -0
- package/dist/index.d.ts +115 -0
- package/dist/index.js +1 -0
- package/dist/react.d.ts +121 -0
- package/dist/react.js +181 -0
- package/package.json +85 -0
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
|
|
21
|
+
// src/core/splitText.ts
|
|
22
|
+
var BREAK_CHARS = /* @__PURE__ */ new Set([
|
|
23
|
+
"\u2014",
|
|
24
|
+
// em-dash
|
|
25
|
+
"\u2013",
|
|
26
|
+
// en-dash
|
|
27
|
+
"-",
|
|
28
|
+
// hyphen
|
|
29
|
+
"/",
|
|
30
|
+
// slash
|
|
31
|
+
"\u2012",
|
|
32
|
+
// figure dash (U+2012)
|
|
33
|
+
"\u2015"
|
|
34
|
+
// horizontal bar (U+2015)
|
|
35
|
+
]);
|
|
36
|
+
var INLINE_ELEMENTS = /* @__PURE__ */ new Set([
|
|
37
|
+
"a",
|
|
38
|
+
"abbr",
|
|
39
|
+
"acronym",
|
|
40
|
+
"b",
|
|
41
|
+
"bdi",
|
|
42
|
+
"bdo",
|
|
43
|
+
"big",
|
|
44
|
+
"cite",
|
|
45
|
+
"code",
|
|
46
|
+
"data",
|
|
47
|
+
"del",
|
|
48
|
+
"dfn",
|
|
49
|
+
"em",
|
|
50
|
+
"i",
|
|
51
|
+
"ins",
|
|
52
|
+
"kbd",
|
|
53
|
+
"mark",
|
|
54
|
+
"q",
|
|
55
|
+
"s",
|
|
56
|
+
"samp",
|
|
57
|
+
"small",
|
|
58
|
+
"span",
|
|
59
|
+
"strong",
|
|
60
|
+
"sub",
|
|
61
|
+
"sup",
|
|
62
|
+
"time",
|
|
63
|
+
"u",
|
|
64
|
+
"var"
|
|
65
|
+
]);
|
|
66
|
+
function hasInlineDescendants(element) {
|
|
67
|
+
const walker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT);
|
|
68
|
+
let node;
|
|
69
|
+
while (node = walker.nextNode()) {
|
|
70
|
+
if (INLINE_ELEMENTS.has(node.tagName.toLowerCase())) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
function ancestorChainsEqual(a, b) {
|
|
77
|
+
if (a.length !== b.length) return false;
|
|
78
|
+
for (let i = 0; i < a.length; i++) {
|
|
79
|
+
if (a[i].instanceId !== b[i].instanceId) return false;
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
function groupCharsByAncestors(chars) {
|
|
84
|
+
if (chars.length === 0) return [];
|
|
85
|
+
const groups = [];
|
|
86
|
+
let currentGroup = [chars[0]];
|
|
87
|
+
let currentAncestors = chars[0].ancestors;
|
|
88
|
+
for (let i = 1; i < chars.length; i++) {
|
|
89
|
+
const char = chars[i];
|
|
90
|
+
if (ancestorChainsEqual(char.ancestors, currentAncestors)) {
|
|
91
|
+
currentGroup.push(char);
|
|
92
|
+
} else {
|
|
93
|
+
groups.push({ ancestors: currentAncestors, chars: currentGroup });
|
|
94
|
+
currentGroup = [char];
|
|
95
|
+
currentAncestors = char.ancestors;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
groups.push({ ancestors: currentAncestors, chars: currentGroup });
|
|
99
|
+
return groups;
|
|
100
|
+
}
|
|
101
|
+
function cloneAncestorAsWrapper(info) {
|
|
102
|
+
const el = document.createElement(info.tagName);
|
|
103
|
+
info.attributes.forEach((value, key) => {
|
|
104
|
+
el.setAttribute(key, value);
|
|
105
|
+
});
|
|
106
|
+
return el;
|
|
107
|
+
}
|
|
108
|
+
function wrapInAncestors(content, ancestors) {
|
|
109
|
+
if (ancestors.length === 0) return content;
|
|
110
|
+
let wrapped = content;
|
|
111
|
+
for (let i = 0; i < ancestors.length; i++) {
|
|
112
|
+
const wrapper = cloneAncestorAsWrapper(ancestors[i]);
|
|
113
|
+
wrapper.appendChild(wrapped);
|
|
114
|
+
wrapped = wrapper;
|
|
115
|
+
}
|
|
116
|
+
return wrapped;
|
|
117
|
+
}
|
|
118
|
+
function normalizeToPromise(value) {
|
|
119
|
+
if (!value) return null;
|
|
120
|
+
if (value instanceof Promise) return value;
|
|
121
|
+
if (typeof value === "object") {
|
|
122
|
+
if ("finished" in value) {
|
|
123
|
+
return value.finished;
|
|
124
|
+
}
|
|
125
|
+
if ("then" in value && typeof value.then === "function") {
|
|
126
|
+
return Promise.resolve(value);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (Array.isArray(value)) {
|
|
130
|
+
const promises = value.map(normalizeToPromise).filter((p) => p !== null);
|
|
131
|
+
return promises.length ? Promise.all(promises) : null;
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
var segmenterCache = null;
|
|
136
|
+
function segmentGraphemes(text) {
|
|
137
|
+
if (!segmenterCache) {
|
|
138
|
+
segmenterCache = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
139
|
+
}
|
|
140
|
+
return [...segmenterCache.segment(text)].map((s) => s.segment);
|
|
141
|
+
}
|
|
142
|
+
function buildAncestorChain(textNode, rootElement, ancestorCache) {
|
|
143
|
+
const ancestors = [];
|
|
144
|
+
let current = textNode.parentNode;
|
|
145
|
+
while (current && current !== rootElement && current instanceof Element) {
|
|
146
|
+
const tagName = current.tagName.toLowerCase();
|
|
147
|
+
if (INLINE_ELEMENTS.has(tagName)) {
|
|
148
|
+
let info = ancestorCache.get(current);
|
|
149
|
+
if (!info) {
|
|
150
|
+
const attributes = /* @__PURE__ */ new Map();
|
|
151
|
+
for (const attr of current.attributes) {
|
|
152
|
+
attributes.set(attr.name, attr.value);
|
|
153
|
+
}
|
|
154
|
+
info = {
|
|
155
|
+
tagName,
|
|
156
|
+
attributes,
|
|
157
|
+
instanceId: /* @__PURE__ */ Symbol()
|
|
158
|
+
};
|
|
159
|
+
ancestorCache.set(current, info);
|
|
160
|
+
}
|
|
161
|
+
ancestors.push(info);
|
|
162
|
+
}
|
|
163
|
+
current = current.parentNode;
|
|
164
|
+
}
|
|
165
|
+
return ancestors;
|
|
166
|
+
}
|
|
167
|
+
function measureOriginalText(element, splitChars, trackAncestors) {
|
|
168
|
+
const range = document.createRange();
|
|
169
|
+
const words = [];
|
|
170
|
+
const ancestorCache = trackAncestors ? /* @__PURE__ */ new WeakMap() : null;
|
|
171
|
+
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
|
|
172
|
+
let node;
|
|
173
|
+
let currentWord = [];
|
|
174
|
+
let wordStartLeft = null;
|
|
175
|
+
let noSpaceBeforeNext = false;
|
|
176
|
+
const pushWord = () => {
|
|
177
|
+
if (currentWord.length > 0) {
|
|
178
|
+
words.push({
|
|
179
|
+
chars: currentWord,
|
|
180
|
+
startLeft: wordStartLeft != null ? wordStartLeft : 0,
|
|
181
|
+
noSpaceBefore: noSpaceBeforeNext
|
|
182
|
+
});
|
|
183
|
+
currentWord = [];
|
|
184
|
+
wordStartLeft = null;
|
|
185
|
+
noSpaceBeforeNext = false;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
const emptyAncestors = [];
|
|
189
|
+
while (node = walker.nextNode()) {
|
|
190
|
+
const text = node.textContent || "";
|
|
191
|
+
const ancestors = trackAncestors ? buildAncestorChain(node, element, ancestorCache) : emptyAncestors;
|
|
192
|
+
const graphemes = segmentGraphemes(text);
|
|
193
|
+
let charOffset = 0;
|
|
194
|
+
for (const grapheme of graphemes) {
|
|
195
|
+
if (grapheme === " " || grapheme === "\n" || grapheme === " ") {
|
|
196
|
+
pushWord();
|
|
197
|
+
charOffset += grapheme.length;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (splitChars) {
|
|
201
|
+
range.setStart(node, charOffset);
|
|
202
|
+
range.setEnd(node, charOffset + grapheme.length);
|
|
203
|
+
const rect = range.getBoundingClientRect();
|
|
204
|
+
if (wordStartLeft === null) {
|
|
205
|
+
wordStartLeft = rect.left;
|
|
206
|
+
}
|
|
207
|
+
currentWord.push({ char: grapheme, left: rect.left, ancestors });
|
|
208
|
+
} else {
|
|
209
|
+
currentWord.push({ char: grapheme, left: 0, ancestors });
|
|
210
|
+
}
|
|
211
|
+
if (BREAK_CHARS.has(grapheme)) {
|
|
212
|
+
pushWord();
|
|
213
|
+
noSpaceBeforeNext = true;
|
|
214
|
+
}
|
|
215
|
+
charOffset += grapheme.length;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
pushWord();
|
|
219
|
+
return words;
|
|
220
|
+
}
|
|
221
|
+
function createSpan(className, index, display = "inline-block", options) {
|
|
222
|
+
const span = document.createElement("span");
|
|
223
|
+
if (className) {
|
|
224
|
+
span.className = className;
|
|
225
|
+
}
|
|
226
|
+
if (index !== void 0) {
|
|
227
|
+
span.dataset.index = index.toString();
|
|
228
|
+
if ((options == null ? void 0 : options.propIndex) && (options == null ? void 0 : options.propName)) {
|
|
229
|
+
span.style.setProperty(`--${options.propName}-index`, index.toString());
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
span.style.display = display;
|
|
233
|
+
span.style.position = "relative";
|
|
234
|
+
span.style.textDecoration = "inherit";
|
|
235
|
+
if (options == null ? void 0 : options.willChange) {
|
|
236
|
+
span.style.willChange = "transform, opacity";
|
|
237
|
+
}
|
|
238
|
+
return span;
|
|
239
|
+
}
|
|
240
|
+
function createMaskWrapper(display = "inline-block") {
|
|
241
|
+
const wrapper = document.createElement("span");
|
|
242
|
+
wrapper.style.display = display;
|
|
243
|
+
wrapper.style.position = "relative";
|
|
244
|
+
wrapper.style.overflow = "clip";
|
|
245
|
+
return wrapper;
|
|
246
|
+
}
|
|
247
|
+
function groupIntoLines(elements, element) {
|
|
248
|
+
const fontSize = parseFloat(getComputedStyle(element).fontSize);
|
|
249
|
+
const tolerance = Math.max(5, fontSize * 0.3);
|
|
250
|
+
const lineGroups = [];
|
|
251
|
+
let currentLine = [];
|
|
252
|
+
let currentY = null;
|
|
253
|
+
elements.forEach((el) => {
|
|
254
|
+
const rect = el instanceof HTMLElement ? el.getBoundingClientRect() : el.parentElement.getBoundingClientRect();
|
|
255
|
+
const y = Math.round(rect.top);
|
|
256
|
+
if (currentY === null) {
|
|
257
|
+
currentY = y;
|
|
258
|
+
currentLine.push(el);
|
|
259
|
+
} else if (Math.abs(y - currentY) < tolerance) {
|
|
260
|
+
currentLine.push(el);
|
|
261
|
+
} else {
|
|
262
|
+
lineGroups.push(currentLine);
|
|
263
|
+
currentLine = [el];
|
|
264
|
+
currentY = y;
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
if (currentLine.length > 0) {
|
|
268
|
+
lineGroups.push(currentLine);
|
|
269
|
+
}
|
|
270
|
+
return lineGroups;
|
|
271
|
+
}
|
|
272
|
+
function performSplit(element, measuredWords, charClass, wordClass, lineClass, splitChars, splitWords, splitLines, options) {
|
|
273
|
+
element.textContent = "";
|
|
274
|
+
const allChars = [];
|
|
275
|
+
const allWords = [];
|
|
276
|
+
const needWordWrappers = splitChars || splitWords;
|
|
277
|
+
if (needWordWrappers) {
|
|
278
|
+
const noSpaceBeforeSet = /* @__PURE__ */ new Set();
|
|
279
|
+
const wordLevelAncestors = /* @__PURE__ */ new Map();
|
|
280
|
+
let globalCharIndex = 0;
|
|
281
|
+
measuredWords.forEach((measuredWord, wordIndex) => {
|
|
282
|
+
const wordSpan = createSpan(wordClass, wordIndex, "inline-block", {
|
|
283
|
+
propIndex: options == null ? void 0 : options.propIndex,
|
|
284
|
+
willChange: options == null ? void 0 : options.willChange,
|
|
285
|
+
propName: "word"
|
|
286
|
+
});
|
|
287
|
+
if (measuredWord.noSpaceBefore) {
|
|
288
|
+
noSpaceBeforeSet.add(wordSpan);
|
|
289
|
+
}
|
|
290
|
+
if (splitChars) {
|
|
291
|
+
const hasAnyAncestors = measuredWord.chars.some((c) => c.ancestors.length > 0);
|
|
292
|
+
if (!hasAnyAncestors) {
|
|
293
|
+
measuredWord.chars.forEach((measuredChar, charIndexInWord) => {
|
|
294
|
+
const charSpan = createSpan(charClass, globalCharIndex, "inline-block", {
|
|
295
|
+
propIndex: options == null ? void 0 : options.propIndex,
|
|
296
|
+
willChange: options == null ? void 0 : options.willChange,
|
|
297
|
+
propName: "char"
|
|
298
|
+
});
|
|
299
|
+
charSpan.textContent = measuredChar.char;
|
|
300
|
+
globalCharIndex++;
|
|
301
|
+
if (charIndexInWord > 0) {
|
|
302
|
+
const prevCharLeft = measuredWord.chars[charIndexInWord - 1].left;
|
|
303
|
+
const gap = measuredChar.left - prevCharLeft;
|
|
304
|
+
charSpan.dataset.expectedGap = gap.toString();
|
|
305
|
+
}
|
|
306
|
+
if ((options == null ? void 0 : options.mask) === "chars") {
|
|
307
|
+
const charWrapper = createMaskWrapper("inline-block");
|
|
308
|
+
charWrapper.appendChild(charSpan);
|
|
309
|
+
wordSpan.appendChild(charWrapper);
|
|
310
|
+
} else {
|
|
311
|
+
wordSpan.appendChild(charSpan);
|
|
312
|
+
}
|
|
313
|
+
allChars.push(charSpan);
|
|
314
|
+
});
|
|
315
|
+
} else {
|
|
316
|
+
const charGroups = groupCharsByAncestors(measuredWord.chars);
|
|
317
|
+
const hasWordLevelAncestors = charGroups.length === 1 && charGroups[0].ancestors.length > 0;
|
|
318
|
+
if (hasWordLevelAncestors) {
|
|
319
|
+
wordLevelAncestors.set(wordSpan, charGroups[0].ancestors);
|
|
320
|
+
}
|
|
321
|
+
charGroups.forEach((group) => {
|
|
322
|
+
group.chars.forEach((measuredChar) => {
|
|
323
|
+
const charIndexInWord = measuredWord.chars.indexOf(measuredChar);
|
|
324
|
+
const charSpan = createSpan(charClass, globalCharIndex, "inline-block", {
|
|
325
|
+
propIndex: options == null ? void 0 : options.propIndex,
|
|
326
|
+
willChange: options == null ? void 0 : options.willChange,
|
|
327
|
+
propName: "char"
|
|
328
|
+
});
|
|
329
|
+
charSpan.textContent = measuredChar.char;
|
|
330
|
+
globalCharIndex++;
|
|
331
|
+
if (charIndexInWord > 0) {
|
|
332
|
+
const prevCharLeft = measuredWord.chars[charIndexInWord - 1].left;
|
|
333
|
+
const gap = measuredChar.left - prevCharLeft;
|
|
334
|
+
charSpan.dataset.expectedGap = gap.toString();
|
|
335
|
+
}
|
|
336
|
+
if ((options == null ? void 0 : options.mask) === "chars") {
|
|
337
|
+
const charWrapper = createMaskWrapper("inline-block");
|
|
338
|
+
charWrapper.appendChild(charSpan);
|
|
339
|
+
wordSpan.appendChild(charWrapper);
|
|
340
|
+
} else {
|
|
341
|
+
wordSpan.appendChild(charSpan);
|
|
342
|
+
}
|
|
343
|
+
allChars.push(charSpan);
|
|
344
|
+
});
|
|
345
|
+
if (!hasWordLevelAncestors && group.ancestors.length > 0) {
|
|
346
|
+
const charsToWrap = Array.from(wordSpan.childNodes);
|
|
347
|
+
const lastNChars = charsToWrap.slice(-group.chars.length);
|
|
348
|
+
lastNChars.forEach((node) => wordSpan.removeChild(node));
|
|
349
|
+
const fragment = document.createDocumentFragment();
|
|
350
|
+
lastNChars.forEach((node) => fragment.appendChild(node));
|
|
351
|
+
const wrapped = wrapInAncestors(fragment, group.ancestors);
|
|
352
|
+
wordSpan.appendChild(wrapped);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
} else {
|
|
357
|
+
const hasAnyAncestors = measuredWord.chars.some((c) => c.ancestors.length > 0);
|
|
358
|
+
if (!hasAnyAncestors) {
|
|
359
|
+
wordSpan.textContent = measuredWord.chars.map((c) => c.char).join("");
|
|
360
|
+
} else {
|
|
361
|
+
const charGroups = groupCharsByAncestors(measuredWord.chars);
|
|
362
|
+
const hasWordLevelAncestors = charGroups.length === 1 && charGroups[0].ancestors.length > 0;
|
|
363
|
+
if (hasWordLevelAncestors) {
|
|
364
|
+
wordLevelAncestors.set(wordSpan, charGroups[0].ancestors);
|
|
365
|
+
wordSpan.textContent = measuredWord.chars.map((c) => c.char).join("");
|
|
366
|
+
} else {
|
|
367
|
+
charGroups.forEach((group) => {
|
|
368
|
+
const text = group.chars.map((c) => c.char).join("");
|
|
369
|
+
const textNode = document.createTextNode(text);
|
|
370
|
+
if (group.ancestors.length > 0) {
|
|
371
|
+
const wrapped = wrapInAncestors(textNode, group.ancestors);
|
|
372
|
+
wordSpan.appendChild(wrapped);
|
|
373
|
+
} else {
|
|
374
|
+
wordSpan.appendChild(textNode);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
allWords.push(wordSpan);
|
|
381
|
+
});
|
|
382
|
+
let i = 0;
|
|
383
|
+
while (i < allWords.length) {
|
|
384
|
+
const wordSpan = allWords[i];
|
|
385
|
+
const ancestors = wordLevelAncestors.get(wordSpan);
|
|
386
|
+
if (ancestors && ancestors.length > 0) {
|
|
387
|
+
const wordGroup = [wordSpan];
|
|
388
|
+
let j = i + 1;
|
|
389
|
+
while (j < allWords.length) {
|
|
390
|
+
const nextWordSpan = allWords[j];
|
|
391
|
+
const nextAncestors = wordLevelAncestors.get(nextWordSpan);
|
|
392
|
+
if (nextAncestors && ancestorChainsEqual(ancestors, nextAncestors)) {
|
|
393
|
+
wordGroup.push(nextWordSpan);
|
|
394
|
+
j++;
|
|
395
|
+
} else {
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const fragment = document.createDocumentFragment();
|
|
400
|
+
wordGroup.forEach((ws, idx) => {
|
|
401
|
+
if ((options == null ? void 0 : options.mask) === "words") {
|
|
402
|
+
const wordWrapper = createMaskWrapper("inline-block");
|
|
403
|
+
wordWrapper.appendChild(ws);
|
|
404
|
+
fragment.appendChild(wordWrapper);
|
|
405
|
+
} else {
|
|
406
|
+
fragment.appendChild(ws);
|
|
407
|
+
}
|
|
408
|
+
if (idx < wordGroup.length - 1 && !noSpaceBeforeSet.has(wordGroup[idx + 1])) {
|
|
409
|
+
fragment.appendChild(document.createTextNode(" "));
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
const wrapped = wrapInAncestors(fragment, ancestors);
|
|
413
|
+
element.appendChild(wrapped);
|
|
414
|
+
if (j < allWords.length && !noSpaceBeforeSet.has(allWords[j])) {
|
|
415
|
+
element.appendChild(document.createTextNode(" "));
|
|
416
|
+
}
|
|
417
|
+
i = j;
|
|
418
|
+
} else {
|
|
419
|
+
if ((options == null ? void 0 : options.mask) === "words") {
|
|
420
|
+
const wordWrapper = createMaskWrapper("inline-block");
|
|
421
|
+
wordWrapper.appendChild(wordSpan);
|
|
422
|
+
element.appendChild(wordWrapper);
|
|
423
|
+
} else {
|
|
424
|
+
element.appendChild(wordSpan);
|
|
425
|
+
}
|
|
426
|
+
if (i < allWords.length - 1 && !noSpaceBeforeSet.has(allWords[i + 1])) {
|
|
427
|
+
element.appendChild(document.createTextNode(" "));
|
|
428
|
+
}
|
|
429
|
+
i++;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (splitChars && allChars.length > 1) {
|
|
433
|
+
const positions = allChars.map((c) => c.getBoundingClientRect().left);
|
|
434
|
+
for (let i2 = 1; i2 < allChars.length; i2++) {
|
|
435
|
+
const charSpan = allChars[i2];
|
|
436
|
+
const expectedGap = charSpan.dataset.expectedGap;
|
|
437
|
+
if (expectedGap !== void 0) {
|
|
438
|
+
const originalGap = parseFloat(expectedGap);
|
|
439
|
+
const currentGap = positions[i2] - positions[i2 - 1];
|
|
440
|
+
const delta = originalGap - currentGap;
|
|
441
|
+
if (Math.abs(delta) < 20) {
|
|
442
|
+
const roundedDelta = Math.round(delta * 100) / 100;
|
|
443
|
+
const targetElement = (options == null ? void 0 : options.mask) === "chars" && charSpan.parentElement ? charSpan.parentElement : charSpan;
|
|
444
|
+
targetElement.style.marginLeft = `${roundedDelta}px`;
|
|
445
|
+
}
|
|
446
|
+
delete charSpan.dataset.expectedGap;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (splitLines) {
|
|
451
|
+
const lineGroups = groupIntoLines(allWords, element);
|
|
452
|
+
element.textContent = "";
|
|
453
|
+
const allLines = [];
|
|
454
|
+
lineGroups.forEach((words, lineIndex) => {
|
|
455
|
+
const lineSpan = createSpan(lineClass, lineIndex, "block", {
|
|
456
|
+
propIndex: options == null ? void 0 : options.propIndex,
|
|
457
|
+
willChange: options == null ? void 0 : options.willChange,
|
|
458
|
+
propName: "line"
|
|
459
|
+
});
|
|
460
|
+
allLines.push(lineSpan);
|
|
461
|
+
let wi = 0;
|
|
462
|
+
while (wi < words.length) {
|
|
463
|
+
const wordSpan = words[wi];
|
|
464
|
+
const ancestors = wordLevelAncestors.get(wordSpan);
|
|
465
|
+
if (ancestors && ancestors.length > 0) {
|
|
466
|
+
const wordGroup = [wordSpan];
|
|
467
|
+
let wj = wi + 1;
|
|
468
|
+
while (wj < words.length) {
|
|
469
|
+
const nextWordSpan = words[wj];
|
|
470
|
+
const nextAncestors = wordLevelAncestors.get(nextWordSpan);
|
|
471
|
+
if (nextAncestors && ancestorChainsEqual(ancestors, nextAncestors)) {
|
|
472
|
+
wordGroup.push(nextWordSpan);
|
|
473
|
+
wj++;
|
|
474
|
+
} else {
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
const fragment = document.createDocumentFragment();
|
|
479
|
+
wordGroup.forEach((ws, idx) => {
|
|
480
|
+
if ((options == null ? void 0 : options.mask) === "words") {
|
|
481
|
+
const wordWrapper = createMaskWrapper("inline-block");
|
|
482
|
+
wordWrapper.appendChild(ws);
|
|
483
|
+
fragment.appendChild(wordWrapper);
|
|
484
|
+
} else {
|
|
485
|
+
fragment.appendChild(ws);
|
|
486
|
+
}
|
|
487
|
+
if (idx < wordGroup.length - 1 && !noSpaceBeforeSet.has(wordGroup[idx + 1])) {
|
|
488
|
+
fragment.appendChild(document.createTextNode(" "));
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
const wrapped = wrapInAncestors(fragment, ancestors);
|
|
492
|
+
lineSpan.appendChild(wrapped);
|
|
493
|
+
if (wj < words.length && !noSpaceBeforeSet.has(words[wj])) {
|
|
494
|
+
lineSpan.appendChild(document.createTextNode(" "));
|
|
495
|
+
}
|
|
496
|
+
wi = wj;
|
|
497
|
+
} else {
|
|
498
|
+
if ((options == null ? void 0 : options.mask) === "words") {
|
|
499
|
+
const wordWrapper = createMaskWrapper("inline-block");
|
|
500
|
+
wordWrapper.appendChild(wordSpan);
|
|
501
|
+
lineSpan.appendChild(wordWrapper);
|
|
502
|
+
} else {
|
|
503
|
+
lineSpan.appendChild(wordSpan);
|
|
504
|
+
}
|
|
505
|
+
if (wi < words.length - 1 && !noSpaceBeforeSet.has(words[wi + 1])) {
|
|
506
|
+
lineSpan.appendChild(document.createTextNode(" "));
|
|
507
|
+
}
|
|
508
|
+
wi++;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if ((options == null ? void 0 : options.mask) === "lines") {
|
|
512
|
+
const lineWrapper = createMaskWrapper("block");
|
|
513
|
+
lineWrapper.appendChild(lineSpan);
|
|
514
|
+
element.appendChild(lineWrapper);
|
|
515
|
+
} else {
|
|
516
|
+
element.appendChild(lineSpan);
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
return {
|
|
520
|
+
chars: allChars,
|
|
521
|
+
words: splitWords ? allWords : [],
|
|
522
|
+
lines: allLines
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
return {
|
|
526
|
+
chars: allChars,
|
|
527
|
+
words: splitWords ? allWords : [],
|
|
528
|
+
lines: []
|
|
529
|
+
};
|
|
530
|
+
} else {
|
|
531
|
+
if (splitLines) {
|
|
532
|
+
const wordWrappers = [];
|
|
533
|
+
measuredWords.forEach((measuredWord, idx) => {
|
|
534
|
+
const textNode = document.createTextNode(
|
|
535
|
+
measuredWord.chars.map((c) => c.char).join("")
|
|
536
|
+
);
|
|
537
|
+
const wrapper = document.createElement("span");
|
|
538
|
+
wrapper.style.display = "inline";
|
|
539
|
+
wrapper.appendChild(textNode);
|
|
540
|
+
element.appendChild(wrapper);
|
|
541
|
+
wordWrappers.push({ wrapper, wordIndex: idx });
|
|
542
|
+
if (idx < measuredWords.length - 1 && !measuredWords[idx + 1].noSpaceBefore) {
|
|
543
|
+
const spaceNode = document.createTextNode(" ");
|
|
544
|
+
element.appendChild(spaceNode);
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
const lineGroups = groupIntoLines(wordWrappers.map((w) => w.wrapper), element);
|
|
548
|
+
element.textContent = "";
|
|
549
|
+
const allLines = [];
|
|
550
|
+
lineGroups.forEach((wrappers, lineIndex) => {
|
|
551
|
+
const lineSpan = createSpan(lineClass, lineIndex, "block", {
|
|
552
|
+
propIndex: options == null ? void 0 : options.propIndex,
|
|
553
|
+
willChange: options == null ? void 0 : options.willChange,
|
|
554
|
+
propName: "line"
|
|
555
|
+
});
|
|
556
|
+
allLines.push(lineSpan);
|
|
557
|
+
wrappers.forEach((wrapper, wrapperIdx) => {
|
|
558
|
+
while (wrapper.firstChild) {
|
|
559
|
+
lineSpan.appendChild(wrapper.firstChild);
|
|
560
|
+
}
|
|
561
|
+
if (wrapperIdx < wrappers.length - 1) {
|
|
562
|
+
const nextWrapper = wrappers[wrapperIdx + 1];
|
|
563
|
+
const nextWordInfo = wordWrappers.find((w) => w.wrapper === nextWrapper);
|
|
564
|
+
if (nextWordInfo && !measuredWords[nextWordInfo.wordIndex].noSpaceBefore) {
|
|
565
|
+
lineSpan.appendChild(document.createTextNode(" "));
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
if ((options == null ? void 0 : options.mask) === "lines") {
|
|
570
|
+
const lineWrapper = createMaskWrapper("block");
|
|
571
|
+
lineWrapper.appendChild(lineSpan);
|
|
572
|
+
element.appendChild(lineWrapper);
|
|
573
|
+
} else {
|
|
574
|
+
element.appendChild(lineSpan);
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
return { chars: [], words: [], lines: allLines };
|
|
578
|
+
} else {
|
|
579
|
+
const fullText = measuredWords.map((w) => w.chars.map((c) => c.char).join("")).join(" ");
|
|
580
|
+
element.textContent = fullText;
|
|
581
|
+
return { chars: [], words: [], lines: [] };
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
function splitText(element, {
|
|
586
|
+
type = "chars,words,lines",
|
|
587
|
+
charClass = "split-char",
|
|
588
|
+
wordClass = "split-word",
|
|
589
|
+
lineClass = "split-line",
|
|
590
|
+
mask,
|
|
591
|
+
autoSplit = false,
|
|
592
|
+
onResize,
|
|
593
|
+
onSplit,
|
|
594
|
+
revertOnComplete = false,
|
|
595
|
+
propIndex = false,
|
|
596
|
+
willChange = false
|
|
597
|
+
} = {}) {
|
|
598
|
+
var _a;
|
|
599
|
+
if (!(element instanceof HTMLElement)) {
|
|
600
|
+
throw new Error("splitText: element must be an HTMLElement");
|
|
601
|
+
}
|
|
602
|
+
const text = (_a = element.textContent) == null ? void 0 : _a.trim();
|
|
603
|
+
if (!text) {
|
|
604
|
+
console.warn("splitText: element has no text content");
|
|
605
|
+
return {
|
|
606
|
+
chars: [],
|
|
607
|
+
words: [],
|
|
608
|
+
lines: [],
|
|
609
|
+
revert: () => {
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
if (autoSplit && !element.parentElement) {
|
|
614
|
+
console.warn(
|
|
615
|
+
"splitText: autoSplit requires a parent element. AutoSplit will not work."
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
const originalHTML = element.innerHTML;
|
|
619
|
+
let splitChars = type.includes("chars");
|
|
620
|
+
let splitWords = type.includes("words");
|
|
621
|
+
let splitLines = type.includes("lines");
|
|
622
|
+
if (!splitChars && !splitWords && !splitLines) {
|
|
623
|
+
console.warn('splitText: type must include at least one of: chars, words, lines. Defaulting to "chars,words,lines".');
|
|
624
|
+
splitChars = splitWords = splitLines = true;
|
|
625
|
+
}
|
|
626
|
+
let isActive = true;
|
|
627
|
+
let resizeObserver = null;
|
|
628
|
+
let debounceTimer = null;
|
|
629
|
+
let lastWidth = null;
|
|
630
|
+
let currentChars = [];
|
|
631
|
+
let currentWords = [];
|
|
632
|
+
let currentLines = [];
|
|
633
|
+
element.setAttribute("aria-label", text);
|
|
634
|
+
if (splitChars) {
|
|
635
|
+
element.style.fontVariantLigatures = "none";
|
|
636
|
+
}
|
|
637
|
+
const trackAncestors = hasInlineDescendants(element);
|
|
638
|
+
const measuredWords = measureOriginalText(element, splitChars, trackAncestors);
|
|
639
|
+
const { chars, words, lines } = performSplit(
|
|
640
|
+
element,
|
|
641
|
+
measuredWords,
|
|
642
|
+
charClass,
|
|
643
|
+
wordClass,
|
|
644
|
+
lineClass,
|
|
645
|
+
splitChars,
|
|
646
|
+
splitWords,
|
|
647
|
+
splitLines,
|
|
648
|
+
{ propIndex, willChange, mask }
|
|
649
|
+
);
|
|
650
|
+
currentChars = chars;
|
|
651
|
+
currentWords = words;
|
|
652
|
+
currentLines = lines;
|
|
653
|
+
const dispose = () => {
|
|
654
|
+
if (!isActive) return;
|
|
655
|
+
if (resizeObserver) {
|
|
656
|
+
resizeObserver.disconnect();
|
|
657
|
+
resizeObserver = null;
|
|
658
|
+
}
|
|
659
|
+
if (debounceTimer) {
|
|
660
|
+
clearTimeout(debounceTimer);
|
|
661
|
+
debounceTimer = null;
|
|
662
|
+
}
|
|
663
|
+
isActive = false;
|
|
664
|
+
};
|
|
665
|
+
const revert = () => {
|
|
666
|
+
if (!isActive) return;
|
|
667
|
+
element.innerHTML = originalHTML;
|
|
668
|
+
element.removeAttribute("aria-label");
|
|
669
|
+
if (splitChars) {
|
|
670
|
+
element.style.fontVariantLigatures = "none";
|
|
671
|
+
}
|
|
672
|
+
dispose();
|
|
673
|
+
};
|
|
674
|
+
if (autoSplit) {
|
|
675
|
+
const target = element.parentElement;
|
|
676
|
+
if (!target) {
|
|
677
|
+
console.warn(
|
|
678
|
+
"SplitText: autoSplit enabled but no parent element found. AutoSplit will not work."
|
|
679
|
+
);
|
|
680
|
+
} else {
|
|
681
|
+
let skipFirst = true;
|
|
682
|
+
const getLineFingerprint = (lines2) => {
|
|
683
|
+
return lines2.map((line) => line.textContent || "").join("\n");
|
|
684
|
+
};
|
|
685
|
+
const handleResize = () => {
|
|
686
|
+
if (!isActive) return;
|
|
687
|
+
if (!element.isConnected) {
|
|
688
|
+
dispose();
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
const currentWidth = target.offsetWidth;
|
|
692
|
+
if (currentWidth === lastWidth) return;
|
|
693
|
+
lastWidth = currentWidth;
|
|
694
|
+
const previousFingerprint = getLineFingerprint(currentLines);
|
|
695
|
+
element.innerHTML = originalHTML;
|
|
696
|
+
requestAnimationFrame(() => {
|
|
697
|
+
if (!isActive) return;
|
|
698
|
+
const newMeasuredWords = measureOriginalText(element, splitChars, trackAncestors);
|
|
699
|
+
const result = performSplit(
|
|
700
|
+
element,
|
|
701
|
+
newMeasuredWords,
|
|
702
|
+
charClass,
|
|
703
|
+
wordClass,
|
|
704
|
+
lineClass,
|
|
705
|
+
splitChars,
|
|
706
|
+
splitWords,
|
|
707
|
+
splitLines,
|
|
708
|
+
{ propIndex, willChange, mask }
|
|
709
|
+
);
|
|
710
|
+
currentChars = result.chars;
|
|
711
|
+
currentWords = result.words;
|
|
712
|
+
currentLines = result.lines;
|
|
713
|
+
const newFingerprint = getLineFingerprint(result.lines);
|
|
714
|
+
if (onResize && newFingerprint !== previousFingerprint) {
|
|
715
|
+
onResize({
|
|
716
|
+
chars: result.chars,
|
|
717
|
+
words: result.words,
|
|
718
|
+
lines: result.lines
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
};
|
|
723
|
+
resizeObserver = new ResizeObserver(() => {
|
|
724
|
+
if (skipFirst) {
|
|
725
|
+
skipFirst = false;
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
if (debounceTimer) {
|
|
729
|
+
clearTimeout(debounceTimer);
|
|
730
|
+
}
|
|
731
|
+
debounceTimer = setTimeout(handleResize, 200);
|
|
732
|
+
});
|
|
733
|
+
resizeObserver.observe(target);
|
|
734
|
+
lastWidth = target.offsetWidth;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (onSplit) {
|
|
738
|
+
const animationResult = onSplit({
|
|
739
|
+
chars: currentChars,
|
|
740
|
+
words: currentWords,
|
|
741
|
+
lines: currentLines
|
|
742
|
+
});
|
|
743
|
+
if (revertOnComplete) {
|
|
744
|
+
const promise = normalizeToPromise(animationResult);
|
|
745
|
+
if (promise) {
|
|
746
|
+
promise.then(() => {
|
|
747
|
+
if (isActive) {
|
|
748
|
+
revert();
|
|
749
|
+
}
|
|
750
|
+
}).catch(() => {
|
|
751
|
+
console.warn("[fetta] Animation rejected, text not reverted");
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return {
|
|
757
|
+
chars: currentChars,
|
|
758
|
+
words: currentWords,
|
|
759
|
+
lines: currentLines,
|
|
760
|
+
revert
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
export { __spreadProps, __spreadValues, normalizeToPromise, splitText };
|