ugcinc 4.5.72 → 4.5.74

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.
@@ -15,12 +15,16 @@ export interface TextElementProps {
15
15
  * - Font family, size, weight, color
16
16
  * - Letter spacing, line height
17
17
  * - Horizontal and vertical alignment
18
- * - Text stroke/outline
18
+ * - Text stroke/outline (via -webkit-text-stroke + paint-order)
19
19
  * - Background with opacity and border radius
20
20
  * - Padding (uniform and individual)
21
21
  * - Auto-width with box alignment
22
22
  * - Rotation
23
23
  *
24
+ * Stroke rendering uses -webkit-text-stroke with paint-order: stroke fill.
25
+ * This naturally excludes emoji bitmaps from the outline (only text glyphs
26
+ * are stroked) and avoids the layout issues of a separate stroke layer.
27
+ *
24
28
  * When autoWidth is enabled:
25
29
  * - Text wraps at maxWidth (the element's width)
26
30
  * - Background shrinks to the widest line
@@ -45,7 +45,6 @@ const react_1 = __importStar(require("react"));
45
45
  const defaults_1 = require("../utils/defaults");
46
46
  const fonts_1 = require("../utils/fonts");
47
47
  const text_1 = require("../utils/text");
48
- const emoji_1 = require("../utils/emoji");
49
48
  /**
50
49
  * Calculate the actual width for auto-width text AND the line breaks.
51
50
  * Uses DOM-based measurement to ensure the same fonts are used as CSS rendering.
@@ -162,41 +161,21 @@ function calculateAutoWidthAndLines({ text, maxWidth, paddingLeft, paddingRight,
162
161
  const calculatedWidth = Math.min(widestLineWidth + paddingLeft + paddingRight, maxWidth);
163
162
  return { width: calculatedWidth, lines };
164
163
  }
165
- /** Shared inline style for emoji spans — prevents line breaks */
166
- const emojiSpanStyle = {
167
- display: 'inline',
168
- whiteSpace: 'pre-wrap',
169
- };
170
- /** Stroke layer emoji span — same structure as fill but invisible to the SVG filter */
171
- const emojiSpanStrokeStyle = {
172
- ...emojiSpanStyle,
173
- opacity: 0,
174
- };
175
- /**
176
- * Render text with emojis wrapped in spans.
177
- * Both fill and stroke divs use this so they have identical DOM structure
178
- * (matching vertical metrics). The only difference is the emoji span style.
179
- */
180
- function renderTextWithEmojiSpans(text, forStroke) {
181
- if (!(0, emoji_1.hasEmoji)(text))
182
- return text;
183
- const parts = (0, emoji_1.splitTextAndEmojis)(text);
184
- const style = forStroke ? emojiSpanStrokeStyle : emojiSpanStyle;
185
- return parts.map((part, i) => part.type === 'emoji'
186
- ? (0, jsx_runtime_1.jsx)("span", { style: style, children: part.content }, i)
187
- : part.content);
188
- }
189
164
  /**
190
165
  * TextElement renders text with full styling support including:
191
166
  * - Font family, size, weight, color
192
167
  * - Letter spacing, line height
193
168
  * - Horizontal and vertical alignment
194
- * - Text stroke/outline
169
+ * - Text stroke/outline (via -webkit-text-stroke + paint-order)
195
170
  * - Background with opacity and border radius
196
171
  * - Padding (uniform and individual)
197
172
  * - Auto-width with box alignment
198
173
  * - Rotation
199
174
  *
175
+ * Stroke rendering uses -webkit-text-stroke with paint-order: stroke fill.
176
+ * This naturally excludes emoji bitmaps from the outline (only text glyphs
177
+ * are stroked) and avoids the layout issues of a separate stroke layer.
178
+ *
200
179
  * When autoWidth is enabled:
201
180
  * - Text wraps at maxWidth (the element's width)
202
181
  * - Background shrinks to the widest line
@@ -344,8 +323,7 @@ function TextElement({ segment, scale = 1 }) {
344
323
  };
345
324
  }, [autoWidth, calculatedWidth, width, backgroundColor, backgroundOpacity, borderRadiusStyle]);
346
325
  const hasStroke = strokeWidth > 0 && !!strokeColor;
347
- const filterId = `te-stroke-${react_1.default.useId().replace(/:/g, '')}`;
348
- // Text style (fill layer)
326
+ // Text style — single div handles both fill and stroke via paint-order
349
327
  const textStyle = (0, react_1.useMemo)(() => ({
350
328
  fontFamily,
351
329
  fontSize,
@@ -364,45 +342,27 @@ function TextElement({ segment, scale = 1 }) {
364
342
  flex: 1,
365
343
  minHeight: 0,
366
344
  }),
367
- ...(hasStroke && { position: 'relative', zIndex: 1 }),
345
+ ...(hasStroke && {
346
+ WebkitTextStroke: `${strokeWidth}px ${strokeColor}`,
347
+ paintOrder: 'stroke fill',
348
+ }),
368
349
  }), [
369
350
  fontFamily, fontSize, fontWeight, color, lineHeight, letterSpacing, alignment,
370
351
  paddingTop, paddingRight, paddingBottom, paddingLeft,
371
- autoWidth, verticalAlign, hasStroke
352
+ autoWidth, verticalAlign, hasStroke, strokeWidth, strokeColor
372
353
  ]);
373
- // Stroke underlay style — same size as fill div, SVG filter handles overflow.
374
- const strokeTextStyle = (0, react_1.useMemo)(() => {
375
- if (!hasStroke)
376
- return undefined;
377
- return {
378
- ...textStyle,
379
- color: strokeColor,
380
- filter: `url(#${filterId})`,
381
- position: 'absolute',
382
- zIndex: 0,
383
- top: 0,
384
- left: 0,
385
- right: 0,
386
- bottom: 0,
387
- };
388
- }, [hasStroke, textStyle, strokeColor, filterId]);
389
- // SVG filter for smooth text outline (avoids miter spikes from -webkit-text-stroke)
390
- const strokePadPct = Math.max(50, Math.ceil((strokeWidth / fontSize) * 200));
391
- const strokeFilterSvg = hasStroke ? ((0, jsx_runtime_1.jsx)("svg", { style: { position: 'absolute', width: 0, height: 0, overflow: 'hidden' }, "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)("defs", { children: (0, jsx_runtime_1.jsxs)("filter", { id: filterId, x: `-${strokePadPct}%`, y: `-${strokePadPct}%`, width: `${100 + 2 * strokePadPct}%`, height: `${100 + 2 * strokePadPct}%`, children: [(0, jsx_runtime_1.jsx)("feMorphology", { in: "SourceAlpha", operator: "dilate", radius: strokeWidth, result: "dilated" }), (0, jsx_runtime_1.jsx)("feFlood", { floodColor: strokeColor, result: "color" }), (0, jsx_runtime_1.jsx)("feComposite", { in: "color", in2: "dilated", operator: "in" })] }) }) })) : null;
392
354
  if (autoWidth) {
393
- const textContent = calculatedLines.map((line, index) => ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [renderTextWithEmojiSpans(line, false), index < calculatedLines.length - 1 && (0, jsx_runtime_1.jsx)("br", {})] }, index)));
394
- const strokeContent = calculatedLines.map((line, index) => ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [renderTextWithEmojiSpans(line, true), index < calculatedLines.length - 1 && (0, jsx_runtime_1.jsx)("br", {})] }, index)));
355
+ const textContent = calculatedLines.map((line, index) => ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [line, index < calculatedLines.length - 1 && (0, jsx_runtime_1.jsx)("br", {})] }, index)));
395
356
  if (backgroundColor) {
396
- return ((0, jsx_runtime_1.jsx)("div", { style: positioningContainerStyle, children: (0, jsx_runtime_1.jsxs)("div", { style: {
357
+ return ((0, jsx_runtime_1.jsx)("div", { style: positioningContainerStyle, children: (0, jsx_runtime_1.jsx)("div", { style: {
397
358
  width: calculatedWidth,
398
359
  maxWidth: width,
399
360
  backgroundColor: (0, text_1.hexToRgba)(backgroundColor, backgroundOpacity),
400
361
  borderRadius: borderRadiusStyle,
401
- ...(hasStroke && { position: 'relative' }),
402
- }, children: [strokeFilterSvg, strokeTextStyle && (0, jsx_runtime_1.jsx)("div", { style: strokeTextStyle, "aria-hidden": "true", children: strokeContent }), (0, jsx_runtime_1.jsx)("div", { style: textStyle, children: textContent })] }) }));
362
+ }, children: (0, jsx_runtime_1.jsx)("div", { style: textStyle, children: textContent }) }) }));
403
363
  }
404
- return ((0, jsx_runtime_1.jsx)("div", { style: positioningContainerStyle, children: (0, jsx_runtime_1.jsxs)("div", { style: { width: calculatedWidth, maxWidth: width, ...(hasStroke && { position: 'relative' }) }, children: [strokeFilterSvg, strokeTextStyle && (0, jsx_runtime_1.jsx)("div", { style: strokeTextStyle, "aria-hidden": "true", children: strokeContent }), (0, jsx_runtime_1.jsx)("div", { style: textStyle, children: textContent })] }) }));
364
+ return ((0, jsx_runtime_1.jsx)("div", { style: positioningContainerStyle, children: (0, jsx_runtime_1.jsx)("div", { style: { width: calculatedWidth, maxWidth: width }, children: (0, jsx_runtime_1.jsx)("div", { style: textStyle, children: textContent }) }) }));
405
365
  }
406
- return ((0, jsx_runtime_1.jsx)("div", { style: positioningContainerStyle, children: (0, jsx_runtime_1.jsxs)("div", { style: { ...backgroundBoxStyle, ...(hasStroke && { position: 'relative' }) }, children: [strokeFilterSvg, strokeTextStyle && (0, jsx_runtime_1.jsx)("div", { style: strokeTextStyle, "aria-hidden": "true", children: renderTextWithEmojiSpans(segment.text, true) }), (0, jsx_runtime_1.jsx)("div", { style: textStyle, children: renderTextWithEmojiSpans(segment.text, false) })] }) }));
366
+ return ((0, jsx_runtime_1.jsx)("div", { style: positioningContainerStyle, children: (0, jsx_runtime_1.jsx)("div", { style: backgroundBoxStyle, children: (0, jsx_runtime_1.jsx)("div", { style: textStyle, children: segment.text }) }) }));
407
367
  }
408
368
  exports.default = TextElement;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugcinc",
3
- "version": "4.5.72",
3
+ "version": "4.5.74",
4
4
  "description": "TypeScript/JavaScript client for the UGC Inc API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",