fetta 1.1.2 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -7
- package/dist/{chunk-WGVCUEOU.js → chunk-Q2D5AQBW.js} +191 -62
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/react.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Split text into characters, words, and lines while preserving the original typog
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **Kerning Compensation** — Measures
|
|
9
|
+
- **Kerning Compensation** — Measures kerning between character pairs, applies margin adjustments to maintain original spacing
|
|
10
10
|
- **Nested Elements** — Preserves inline HTML elements (`<a>`, `<em>`, `<strong>`, etc.) with all attributes intact
|
|
11
11
|
- **Line Detection** — Detects lines based on Y-position clustering, works with any container width
|
|
12
12
|
- **Dash Handling** — Allows text to wrap naturally after em-dashes, en-dashes, and hyphens
|
|
@@ -265,12 +265,6 @@ Requires:
|
|
|
265
265
|
- `IntersectionObserver`
|
|
266
266
|
- `Intl.Segmenter`
|
|
267
267
|
|
|
268
|
-
### Safari
|
|
269
|
-
|
|
270
|
-
Kerning compensation is not available in Safari due to its Range API returning integer values instead of sub-pixel precision. Text splitting works normally, just without the margin adjustments.
|
|
271
|
-
|
|
272
|
-
When using `revertOnComplete` with character splitting in Safari, font kerning is automatically disabled to prevent visual shift on revert.
|
|
273
|
-
|
|
274
268
|
## License
|
|
275
269
|
|
|
276
270
|
MIT
|
|
@@ -63,7 +63,159 @@ var INLINE_ELEMENTS = /* @__PURE__ */ new Set([
|
|
|
63
63
|
"u",
|
|
64
64
|
"var"
|
|
65
65
|
]);
|
|
66
|
-
var
|
|
66
|
+
var KERNING_STYLE_PROPS = [
|
|
67
|
+
"font",
|
|
68
|
+
"font-kerning",
|
|
69
|
+
"font-variant-ligatures",
|
|
70
|
+
"font-feature-settings",
|
|
71
|
+
"font-variation-settings",
|
|
72
|
+
"font-optical-sizing",
|
|
73
|
+
"font-size-adjust",
|
|
74
|
+
"font-stretch",
|
|
75
|
+
"font-variant-caps",
|
|
76
|
+
"font-variant-numeric",
|
|
77
|
+
"font-variant-east-asian",
|
|
78
|
+
"font-synthesis",
|
|
79
|
+
"font-synthesis-weight",
|
|
80
|
+
"font-synthesis-style",
|
|
81
|
+
"letter-spacing",
|
|
82
|
+
"word-spacing",
|
|
83
|
+
"text-rendering",
|
|
84
|
+
"text-transform",
|
|
85
|
+
"direction",
|
|
86
|
+
"unicode-bidi"
|
|
87
|
+
];
|
|
88
|
+
function copyKerningStyles(target, styles) {
|
|
89
|
+
KERNING_STYLE_PROPS.forEach((prop) => {
|
|
90
|
+
const value = styles.getPropertyValue(prop);
|
|
91
|
+
if (value) target.style.setProperty(prop, value);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function buildCanvasFontString(styles) {
|
|
95
|
+
const fontStyle = styles.fontStyle || "normal";
|
|
96
|
+
const fontWeight = styles.fontWeight || "normal";
|
|
97
|
+
const fontSize = styles.fontSize || "16px";
|
|
98
|
+
const fontFamily = styles.fontFamily || "sans-serif";
|
|
99
|
+
return [fontStyle, fontWeight, fontSize, fontFamily].filter(Boolean).join(" ");
|
|
100
|
+
}
|
|
101
|
+
function applyKerningStylesToCanvas(ctx, styles) {
|
|
102
|
+
ctx.font = buildCanvasFontString(styles);
|
|
103
|
+
const ctxAny = ctx;
|
|
104
|
+
const setIfExists = (prop, value) => {
|
|
105
|
+
if (!value || value === "normal") return;
|
|
106
|
+
if (prop in ctxAny) ctxAny[prop] = value;
|
|
107
|
+
};
|
|
108
|
+
setIfExists("fontKerning", styles.getPropertyValue("font-kerning"));
|
|
109
|
+
setIfExists("fontVariantLigatures", styles.getPropertyValue("font-variant-ligatures"));
|
|
110
|
+
setIfExists("fontFeatureSettings", styles.getPropertyValue("font-feature-settings"));
|
|
111
|
+
setIfExists("fontVariationSettings", styles.getPropertyValue("font-variation-settings"));
|
|
112
|
+
setIfExists("fontOpticalSizing", styles.getPropertyValue("font-optical-sizing"));
|
|
113
|
+
setIfExists("fontSizeAdjust", styles.getPropertyValue("font-size-adjust"));
|
|
114
|
+
setIfExists("fontStretch", styles.getPropertyValue("font-stretch"));
|
|
115
|
+
setIfExists("fontVariantCaps", styles.getPropertyValue("font-variant-caps"));
|
|
116
|
+
setIfExists("fontVariantNumeric", styles.getPropertyValue("font-variant-numeric"));
|
|
117
|
+
setIfExists("fontVariantEastAsian", styles.getPropertyValue("font-variant-east-asian"));
|
|
118
|
+
setIfExists("fontSynthesis", styles.getPropertyValue("font-synthesis"));
|
|
119
|
+
setIfExists("fontSynthesisWeight", styles.getPropertyValue("font-synthesis-weight"));
|
|
120
|
+
setIfExists("fontSynthesisStyle", styles.getPropertyValue("font-synthesis-style"));
|
|
121
|
+
setIfExists("letterSpacing", styles.getPropertyValue("letter-spacing"));
|
|
122
|
+
setIfExists("wordSpacing", styles.getPropertyValue("word-spacing"));
|
|
123
|
+
setIfExists("textRendering", styles.getPropertyValue("text-rendering"));
|
|
124
|
+
setIfExists("direction", styles.getPropertyValue("direction"));
|
|
125
|
+
}
|
|
126
|
+
function buildKerningStyleKey(styles) {
|
|
127
|
+
return KERNING_STYLE_PROPS.map((prop) => styles.getPropertyValue(prop)).join("|");
|
|
128
|
+
}
|
|
129
|
+
function shouldUseDomKerning(styles) {
|
|
130
|
+
const textTransform = styles.getPropertyValue("text-transform");
|
|
131
|
+
if (textTransform && textTransform !== "none") return true;
|
|
132
|
+
const fontVariant = styles.getPropertyValue("font-variant");
|
|
133
|
+
if (fontVariant && fontVariant !== "normal") return true;
|
|
134
|
+
const fontStretch = styles.getPropertyValue("font-stretch");
|
|
135
|
+
if (fontStretch && fontStretch !== "normal" && fontStretch !== "100%") return true;
|
|
136
|
+
const fontFeatureSettings = styles.getPropertyValue("font-feature-settings");
|
|
137
|
+
if (fontFeatureSettings && fontFeatureSettings !== "normal") return true;
|
|
138
|
+
const fontVariationSettings = styles.getPropertyValue("font-variation-settings");
|
|
139
|
+
if (fontVariationSettings && fontVariationSettings !== "normal") return true;
|
|
140
|
+
const fontOpticalSizing = styles.getPropertyValue("font-optical-sizing");
|
|
141
|
+
if (fontOpticalSizing && fontOpticalSizing !== "auto") return true;
|
|
142
|
+
const fontSizeAdjust = styles.getPropertyValue("font-size-adjust");
|
|
143
|
+
if (fontSizeAdjust && fontSizeAdjust !== "none") return true;
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
var isSafariBrowser = null;
|
|
147
|
+
function isSafari() {
|
|
148
|
+
if (isSafariBrowser !== null) return isSafariBrowser;
|
|
149
|
+
if (typeof navigator === "undefined") return false;
|
|
150
|
+
isSafariBrowser = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
151
|
+
return isSafariBrowser;
|
|
152
|
+
}
|
|
153
|
+
function measureKerningCanvas(styleSource, chars, styles) {
|
|
154
|
+
const kerningMap = /* @__PURE__ */ new Map();
|
|
155
|
+
if (chars.length < 2) return kerningMap;
|
|
156
|
+
const canvas = document.createElement("canvas");
|
|
157
|
+
const ctx = canvas.getContext("2d");
|
|
158
|
+
if (!ctx) return kerningMap;
|
|
159
|
+
const computedStyles = styles != null ? styles : getComputedStyle(styleSource);
|
|
160
|
+
applyKerningStylesToCanvas(ctx, computedStyles);
|
|
161
|
+
const charWidths = /* @__PURE__ */ new Map();
|
|
162
|
+
for (const char of new Set(chars)) {
|
|
163
|
+
charWidths.set(char, ctx.measureText(char).width);
|
|
164
|
+
}
|
|
165
|
+
for (let i = 0; i < chars.length - 1; i++) {
|
|
166
|
+
const char1 = chars[i];
|
|
167
|
+
const char2 = chars[i + 1];
|
|
168
|
+
const pairWidth = ctx.measureText(char1 + char2).width;
|
|
169
|
+
const kerning = pairWidth - charWidths.get(char1) - charWidths.get(char2);
|
|
170
|
+
if (Math.abs(kerning) > 0.01) {
|
|
171
|
+
kerningMap.set(i + 1, kerning);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return kerningMap;
|
|
175
|
+
}
|
|
176
|
+
function measureKerningDOM(container, styleSource, chars, styles) {
|
|
177
|
+
const kerningMap = /* @__PURE__ */ new Map();
|
|
178
|
+
if (chars.length < 2) return kerningMap;
|
|
179
|
+
const measurer = document.createElement("span");
|
|
180
|
+
measurer.style.cssText = `
|
|
181
|
+
position: absolute;
|
|
182
|
+
visibility: hidden;
|
|
183
|
+
white-space: pre;
|
|
184
|
+
`;
|
|
185
|
+
const computedStyles = styles != null ? styles : getComputedStyle(styleSource);
|
|
186
|
+
copyKerningStyles(measurer, computedStyles);
|
|
187
|
+
const webkitSmoothing = computedStyles.webkitFontSmoothing || computedStyles["-webkit-font-smoothing"];
|
|
188
|
+
const mozSmoothing = computedStyles.MozOsxFontSmoothing || computedStyles["-moz-osx-font-smoothing"];
|
|
189
|
+
if (webkitSmoothing) {
|
|
190
|
+
measurer.style.webkitFontSmoothing = webkitSmoothing;
|
|
191
|
+
}
|
|
192
|
+
if (mozSmoothing) {
|
|
193
|
+
measurer.style.MozOsxFontSmoothing = mozSmoothing;
|
|
194
|
+
}
|
|
195
|
+
container.appendChild(measurer);
|
|
196
|
+
const charWidths = /* @__PURE__ */ new Map();
|
|
197
|
+
for (const char of new Set(chars)) {
|
|
198
|
+
measurer.textContent = char;
|
|
199
|
+
charWidths.set(char, measurer.getBoundingClientRect().width);
|
|
200
|
+
}
|
|
201
|
+
for (let i = 0; i < chars.length - 1; i++) {
|
|
202
|
+
const char1 = chars[i];
|
|
203
|
+
const char2 = chars[i + 1];
|
|
204
|
+
measurer.textContent = char1 + char2;
|
|
205
|
+
const pairWidth = measurer.getBoundingClientRect().width;
|
|
206
|
+
const kerning = pairWidth - charWidths.get(char1) - charWidths.get(char2);
|
|
207
|
+
if (Math.abs(kerning) > 0.01) {
|
|
208
|
+
kerningMap.set(i + 1, kerning);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
container.removeChild(measurer);
|
|
212
|
+
return kerningMap;
|
|
213
|
+
}
|
|
214
|
+
function measureKerning(container, styleSource, chars, styles) {
|
|
215
|
+
if (chars.length < 2) return /* @__PURE__ */ new Map();
|
|
216
|
+
const computedStyles = styles != null ? styles : getComputedStyle(styleSource);
|
|
217
|
+
return isSafari() || shouldUseDomKerning(computedStyles) ? measureKerningDOM(container, styleSource, chars, computedStyles) : measureKerningCanvas(styleSource, chars, computedStyles);
|
|
218
|
+
}
|
|
67
219
|
var srOnlyStylesInjected = false;
|
|
68
220
|
function injectSrOnlyStyles() {
|
|
69
221
|
if (srOnlyStylesInjected || typeof document === "undefined") return;
|
|
@@ -191,24 +343,20 @@ function buildAncestorChain(textNode, rootElement, ancestorCache) {
|
|
|
191
343
|
}
|
|
192
344
|
return ancestors;
|
|
193
345
|
}
|
|
194
|
-
function
|
|
195
|
-
const range = document.createRange();
|
|
346
|
+
function collectTextStructure(element, trackAncestors) {
|
|
196
347
|
const words = [];
|
|
197
348
|
const ancestorCache = trackAncestors ? /* @__PURE__ */ new WeakMap() : null;
|
|
198
349
|
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
|
|
199
350
|
let node;
|
|
200
351
|
let currentWord = [];
|
|
201
|
-
let wordStartLeft = null;
|
|
202
352
|
let noSpaceBeforeNext = false;
|
|
203
353
|
const pushWord = () => {
|
|
204
354
|
if (currentWord.length > 0) {
|
|
205
355
|
words.push({
|
|
206
356
|
chars: currentWord,
|
|
207
|
-
startLeft: wordStartLeft != null ? wordStartLeft : 0,
|
|
208
357
|
noSpaceBefore: noSpaceBeforeNext
|
|
209
358
|
});
|
|
210
359
|
currentWord = [];
|
|
211
|
-
wordStartLeft = null;
|
|
212
360
|
noSpaceBeforeNext = false;
|
|
213
361
|
}
|
|
214
362
|
};
|
|
@@ -217,29 +365,16 @@ function measureOriginalText(element, splitChars, trackAncestors) {
|
|
|
217
365
|
const text = node.textContent || "";
|
|
218
366
|
const ancestors = trackAncestors ? buildAncestorChain(node, element, ancestorCache) : emptyAncestors;
|
|
219
367
|
const graphemes = segmentGraphemes(text);
|
|
220
|
-
let charOffset = 0;
|
|
221
368
|
for (const grapheme of graphemes) {
|
|
222
369
|
if (grapheme === " " || grapheme === "\n" || grapheme === " ") {
|
|
223
370
|
pushWord();
|
|
224
|
-
charOffset += grapheme.length;
|
|
225
371
|
continue;
|
|
226
372
|
}
|
|
227
|
-
|
|
228
|
-
range.setStart(node, charOffset);
|
|
229
|
-
range.setEnd(node, charOffset + grapheme.length);
|
|
230
|
-
const rect = range.getBoundingClientRect();
|
|
231
|
-
if (wordStartLeft === null) {
|
|
232
|
-
wordStartLeft = rect.left;
|
|
233
|
-
}
|
|
234
|
-
currentWord.push({ char: grapheme, left: rect.left, ancestors });
|
|
235
|
-
} else {
|
|
236
|
-
currentWord.push({ char: grapheme, left: 0, ancestors });
|
|
237
|
-
}
|
|
373
|
+
currentWord.push({ char: grapheme, ancestors });
|
|
238
374
|
if (BREAK_CHARS.has(grapheme)) {
|
|
239
375
|
pushWord();
|
|
240
376
|
noSpaceBeforeNext = true;
|
|
241
377
|
}
|
|
242
|
-
charOffset += grapheme.length;
|
|
243
378
|
}
|
|
244
379
|
}
|
|
245
380
|
pushWord();
|
|
@@ -325,11 +460,6 @@ function performSplit(element, measuredWords, charClass, wordClass, lineClass, s
|
|
|
325
460
|
});
|
|
326
461
|
charSpan.textContent = measuredChar.char;
|
|
327
462
|
globalCharIndex++;
|
|
328
|
-
if (charIndexInWord > 0) {
|
|
329
|
-
const prevCharLeft = measuredWord.chars[charIndexInWord - 1].left;
|
|
330
|
-
const gap = measuredChar.left - prevCharLeft;
|
|
331
|
-
charSpan.dataset.expectedGap = gap.toString();
|
|
332
|
-
}
|
|
333
463
|
if ((options == null ? void 0 : options.mask) === "chars") {
|
|
334
464
|
const charWrapper = createMaskWrapper("inline-block");
|
|
335
465
|
charWrapper.appendChild(charSpan);
|
|
@@ -347,18 +477,12 @@ function performSplit(element, measuredWords, charClass, wordClass, lineClass, s
|
|
|
347
477
|
}
|
|
348
478
|
charGroups.forEach((group) => {
|
|
349
479
|
group.chars.forEach((measuredChar) => {
|
|
350
|
-
const charIndexInWord = measuredWord.chars.indexOf(measuredChar);
|
|
351
480
|
const charSpan = createSpan(charClass, globalCharIndex, "inline-block", {
|
|
352
481
|
propIndex: options == null ? void 0 : options.propIndex,
|
|
353
482
|
propName: "char"
|
|
354
483
|
});
|
|
355
484
|
charSpan.textContent = measuredChar.char;
|
|
356
485
|
globalCharIndex++;
|
|
357
|
-
if (charIndexInWord > 0) {
|
|
358
|
-
const prevCharLeft = measuredWord.chars[charIndexInWord - 1].left;
|
|
359
|
-
const gap = measuredChar.left - prevCharLeft;
|
|
360
|
-
charSpan.dataset.expectedGap = gap.toString();
|
|
361
|
-
}
|
|
362
486
|
if ((options == null ? void 0 : options.mask) === "chars") {
|
|
363
487
|
const charWrapper = createMaskWrapper("inline-block");
|
|
364
488
|
charWrapper.appendChild(charSpan);
|
|
@@ -455,30 +579,42 @@ function performSplit(element, measuredWords, charClass, wordClass, lineClass, s
|
|
|
455
579
|
i++;
|
|
456
580
|
}
|
|
457
581
|
}
|
|
458
|
-
if (splitChars &&
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
582
|
+
if (splitChars && allWords.length > 0) {
|
|
583
|
+
for (const wordSpan of allWords) {
|
|
584
|
+
const wordChars = Array.from(wordSpan.querySelectorAll(`.${charClass}`));
|
|
585
|
+
if (wordChars.length < 2) continue;
|
|
586
|
+
const styleGroups = [];
|
|
587
|
+
const firstCharStyles = getComputedStyle(wordChars[0]);
|
|
588
|
+
let currentKey = buildKerningStyleKey(firstCharStyles);
|
|
589
|
+
let currentGroup = {
|
|
590
|
+
chars: [wordChars[0]],
|
|
591
|
+
styleSource: wordChars[0],
|
|
592
|
+
styles: firstCharStyles
|
|
593
|
+
};
|
|
594
|
+
for (let i2 = 1; i2 < wordChars.length; i2++) {
|
|
595
|
+
const char = wordChars[i2];
|
|
596
|
+
const charStyles = getComputedStyle(char);
|
|
597
|
+
const key = buildKerningStyleKey(charStyles);
|
|
598
|
+
if (key === currentKey) {
|
|
599
|
+
currentGroup.chars.push(char);
|
|
600
|
+
} else {
|
|
601
|
+
styleGroups.push(currentGroup);
|
|
602
|
+
currentKey = key;
|
|
603
|
+
currentGroup = { chars: [char], styleSource: char, styles: charStyles };
|
|
604
|
+
}
|
|
467
605
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
targetElement.style.marginLeft = `${delta}px`;
|
|
606
|
+
styleGroups.push(currentGroup);
|
|
607
|
+
for (const group of styleGroups) {
|
|
608
|
+
if (group.chars.length < 2) continue;
|
|
609
|
+
const charStrings = group.chars.map((c) => c.textContent || "");
|
|
610
|
+
const kerningMap = measureKerning(element, group.styleSource, charStrings, group.styles);
|
|
611
|
+
for (const [charIndex, kerning] of kerningMap) {
|
|
612
|
+
const charSpan = group.chars[charIndex];
|
|
613
|
+
if (charSpan && Math.abs(kerning) < 20) {
|
|
614
|
+
const targetElement = (options == null ? void 0 : options.mask) === "chars" && charSpan.parentElement ? charSpan.parentElement : charSpan;
|
|
615
|
+
targetElement.style.marginLeft = `${kerning}px`;
|
|
616
|
+
}
|
|
480
617
|
}
|
|
481
|
-
delete charSpan.dataset.expectedGap;
|
|
482
618
|
}
|
|
483
619
|
}
|
|
484
620
|
}
|
|
@@ -666,12 +802,8 @@ function splitText(element, {
|
|
|
666
802
|
if (splitChars) {
|
|
667
803
|
element.style.fontVariantLigatures = "none";
|
|
668
804
|
}
|
|
669
|
-
if (isSafari && splitChars && revertOnComplete) {
|
|
670
|
-
element.style.fontKerning = "none";
|
|
671
|
-
}
|
|
672
805
|
const trackAncestors = hasInlineDescendants(element);
|
|
673
|
-
const
|
|
674
|
-
const measuredWords = measureOriginalText(element, measureChars, trackAncestors);
|
|
806
|
+
const measuredWords = collectTextStructure(element, trackAncestors);
|
|
675
807
|
const { chars, words, lines } = performSplit(
|
|
676
808
|
element,
|
|
677
809
|
measuredWords,
|
|
@@ -720,9 +852,6 @@ function splitText(element, {
|
|
|
720
852
|
if (splitChars) {
|
|
721
853
|
element.style.fontVariantLigatures = "none";
|
|
722
854
|
}
|
|
723
|
-
if (isSafari && splitChars && revertOnComplete) {
|
|
724
|
-
element.style.fontKerning = "none";
|
|
725
|
-
}
|
|
726
855
|
dispose();
|
|
727
856
|
};
|
|
728
857
|
if (autoSplit) {
|
|
@@ -749,7 +878,7 @@ function splitText(element, {
|
|
|
749
878
|
element.innerHTML = originalHTML;
|
|
750
879
|
requestAnimationFrame(() => {
|
|
751
880
|
if (!isActive) return;
|
|
752
|
-
const newMeasuredWords =
|
|
881
|
+
const newMeasuredWords = collectTextStructure(element, trackAncestors);
|
|
753
882
|
const result = performSplit(
|
|
754
883
|
element,
|
|
755
884
|
newMeasuredWords,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Custom splitText implementation with built-in kerning compensation.
|
|
3
|
-
* Measures
|
|
4
|
-
*
|
|
3
|
+
* Measures kerning between character pairs, splits text into spans,
|
|
4
|
+
* applies margin compensation, and detects lines based on rendered positions.
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
7
|
* Configuration options for the splitText function.
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { splitText } from './chunk-
|
|
1
|
+
export { splitText } from './chunk-Q2D5AQBW.js';
|
package/dist/react.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { splitText, __spreadProps, __spreadValues, normalizeToPromise } from './chunk-
|
|
1
|
+
import { splitText, __spreadProps, __spreadValues, normalizeToPromise } from './chunk-Q2D5AQBW.js';
|
|
2
2
|
import { forwardRef, useRef, useCallback, useState, useLayoutEffect, useEffect, isValidElement, cloneElement } from 'react';
|
|
3
3
|
import { jsx } from 'react/jsx-runtime';
|
|
4
4
|
|