@antv/infographic 0.2.17 → 0.2.19

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.
Files changed (97) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/infographic.min.js +110 -110
  4. package/dist/infographic.min.js.map +1 -1
  5. package/esm/constants/service.d.ts +1 -1
  6. package/esm/constants/service.js +1 -1
  7. package/esm/designs/structures/chart-line.js +5 -3
  8. package/esm/editor/interactions/dblclick-edit-text.js +3 -3
  9. package/esm/editor/managers/interaction.js +6 -4
  10. package/esm/editor/plugins/components/button.d.ts +2 -1
  11. package/esm/editor/plugins/components/button.js +4 -4
  12. package/esm/editor/plugins/components/color-picker.d.ts +1 -0
  13. package/esm/editor/plugins/components/color-picker.js +3 -3
  14. package/esm/editor/plugins/components/popover.d.ts +3 -1
  15. package/esm/editor/plugins/components/popover.js +29 -9
  16. package/esm/editor/plugins/edit-bar/edit-bar.d.ts +3 -1
  17. package/esm/editor/plugins/edit-bar/edit-bar.js +17 -7
  18. package/esm/editor/plugins/edit-bar/edit-items/align-elements.js +6 -4
  19. package/esm/editor/plugins/edit-bar/edit-items/font-align.js +8 -5
  20. package/esm/editor/plugins/edit-bar/edit-items/font-color.js +7 -4
  21. package/esm/editor/plugins/edit-bar/edit-items/font-family.js +11 -9
  22. package/esm/editor/plugins/edit-bar/edit-items/font-size.js +8 -5
  23. package/esm/editor/plugins/edit-bar/edit-items/icon-color.js +7 -4
  24. package/esm/editor/plugins/edit-bar/edit-items/types.d.ts +5 -1
  25. package/esm/editor/plugins/reset-viewbox.d.ts +4 -1
  26. package/esm/editor/plugins/reset-viewbox.js +12 -6
  27. package/esm/editor/utils/index.d.ts +1 -0
  28. package/esm/editor/utils/index.js +1 -0
  29. package/esm/editor/utils/root.d.ts +3 -0
  30. package/esm/editor/utils/root.js +18 -0
  31. package/esm/exporter/svg.js +192 -52
  32. package/esm/resource/loaders/search.js +0 -3
  33. package/esm/templates/utils.js +11 -6
  34. package/esm/utils/padding.js +1 -1
  35. package/esm/utils/style.d.ts +3 -1
  36. package/esm/utils/style.js +27 -4
  37. package/esm/version.d.ts +1 -1
  38. package/esm/version.js +1 -1
  39. package/lib/constants/service.d.ts +1 -1
  40. package/lib/constants/service.js +1 -1
  41. package/lib/designs/structures/chart-line.js +5 -3
  42. package/lib/editor/interactions/dblclick-edit-text.js +3 -3
  43. package/lib/editor/managers/interaction.js +7 -5
  44. package/lib/editor/plugins/components/button.d.ts +2 -1
  45. package/lib/editor/plugins/components/button.js +4 -4
  46. package/lib/editor/plugins/components/color-picker.d.ts +1 -0
  47. package/lib/editor/plugins/components/color-picker.js +3 -3
  48. package/lib/editor/plugins/components/popover.d.ts +3 -1
  49. package/lib/editor/plugins/components/popover.js +32 -12
  50. package/lib/editor/plugins/edit-bar/edit-bar.d.ts +3 -1
  51. package/lib/editor/plugins/edit-bar/edit-bar.js +17 -7
  52. package/lib/editor/plugins/edit-bar/edit-items/align-elements.js +6 -4
  53. package/lib/editor/plugins/edit-bar/edit-items/font-align.js +8 -5
  54. package/lib/editor/plugins/edit-bar/edit-items/font-color.js +7 -4
  55. package/lib/editor/plugins/edit-bar/edit-items/font-family.js +11 -9
  56. package/lib/editor/plugins/edit-bar/edit-items/font-size.js +8 -5
  57. package/lib/editor/plugins/edit-bar/edit-items/icon-color.js +7 -4
  58. package/lib/editor/plugins/edit-bar/edit-items/types.d.ts +5 -1
  59. package/lib/editor/plugins/reset-viewbox.d.ts +4 -1
  60. package/lib/editor/plugins/reset-viewbox.js +12 -6
  61. package/lib/editor/utils/index.d.ts +1 -0
  62. package/lib/editor/utils/index.js +1 -0
  63. package/lib/editor/utils/root.d.ts +3 -0
  64. package/lib/editor/utils/root.js +22 -0
  65. package/lib/exporter/svg.js +192 -52
  66. package/lib/resource/loaders/search.js +0 -3
  67. package/lib/templates/utils.js +11 -6
  68. package/lib/utils/padding.js +1 -1
  69. package/lib/utils/style.d.ts +3 -1
  70. package/lib/utils/style.js +27 -4
  71. package/lib/version.d.ts +1 -1
  72. package/lib/version.js +1 -1
  73. package/package.json +1 -1
  74. package/src/constants/service.ts +1 -1
  75. package/src/designs/structures/chart-line.tsx +5 -3
  76. package/src/editor/interactions/dblclick-edit-text.ts +3 -2
  77. package/src/editor/managers/interaction.ts +9 -7
  78. package/src/editor/plugins/components/button.ts +5 -2
  79. package/src/editor/plugins/components/color-picker.ts +4 -2
  80. package/src/editor/plugins/components/popover.ts +31 -12
  81. package/src/editor/plugins/edit-bar/edit-bar.ts +26 -11
  82. package/src/editor/plugins/edit-bar/edit-items/align-elements.ts +7 -2
  83. package/src/editor/plugins/edit-bar/edit-items/font-align.ts +8 -3
  84. package/src/editor/plugins/edit-bar/edit-items/font-color.ts +7 -2
  85. package/src/editor/plugins/edit-bar/edit-items/font-family.ts +11 -7
  86. package/src/editor/plugins/edit-bar/edit-items/font-size.ts +8 -3
  87. package/src/editor/plugins/edit-bar/edit-items/icon-color.ts +7 -2
  88. package/src/editor/plugins/edit-bar/edit-items/types.ts +6 -1
  89. package/src/editor/plugins/reset-viewbox.ts +17 -8
  90. package/src/editor/utils/index.ts +1 -0
  91. package/src/editor/utils/root.ts +26 -0
  92. package/src/exporter/svg.ts +267 -62
  93. package/src/resource/loaders/search.ts +0 -3
  94. package/src/templates/utils.ts +30 -6
  95. package/src/utils/padding.ts +1 -1
  96. package/src/utils/style.ts +31 -4
  97. package/src/version.ts +1 -1
@@ -11,6 +11,17 @@ import { embedFonts } from './font';
11
11
  import type { SVGExportOptions } from './types';
12
12
 
13
13
  const VIEWBOX_CHANGE_TOLERANCE = 0.5;
14
+ type BoundsTuple = [number, number, number, number];
15
+
16
+ interface ForeignObjectExportAdjustment {
17
+ rootBounds: BoundsTuple;
18
+ exportBounds: {
19
+ x: number;
20
+ y: number;
21
+ width: number;
22
+ height: number;
23
+ };
24
+ }
14
25
 
15
26
  export async function exportToSVGString(
16
27
  svg: SVGSVGElement,
@@ -26,7 +37,12 @@ function getExportViewBox(svg: SVGSVGElement) {
26
37
 
27
38
  const width = parseAbsoluteLength(svg.getAttribute('width'));
28
39
  const height = parseAbsoluteLength(svg.getAttribute('height'));
29
- if (width > 0 && height > 0) {
40
+ if (
41
+ !Number.isNaN(width) &&
42
+ width > 0 &&
43
+ !Number.isNaN(height) &&
44
+ height > 0
45
+ ) {
30
46
  return { x: 0, y: 0, width, height };
31
47
  }
32
48
 
@@ -46,41 +62,98 @@ function parseAbsoluteLength(value: string | null): number {
46
62
  return Number.parseFloat(trimmed);
47
63
  }
48
64
 
49
- function measureSpanContentHeight(span: HTMLElement): number {
65
+ function parseCoordinate(value: string | null): number {
66
+ const parsed = parseAbsoluteLength(value);
67
+ return Number.isNaN(parsed) ? 0 : parsed;
68
+ }
69
+
70
+ interface MeasuredSpanContentDimensions {
71
+ height: number;
72
+ width: number;
73
+ }
74
+
75
+ function measureSpanContentDimensions(
76
+ span: HTMLElement,
77
+ measureWidth: boolean,
78
+ ): MeasuredSpanContentDimensions {
50
79
  const prevHeight = span.style.height;
80
+ const prevWidth = span.style.width;
51
81
  const prevOverflow = span.style.overflow;
52
82
  try {
53
83
  span.style.height = 'max-content';
54
84
  span.style.overflow = 'hidden';
55
85
  void span.offsetHeight; // force reflow
56
- return span.scrollHeight;
86
+ const scrollHeight = span.scrollHeight;
87
+ const rectHeight = span.getBoundingClientRect().height;
88
+ let width = span.scrollWidth;
89
+
90
+ if (measureWidth) {
91
+ span.style.width = 'max-content';
92
+ void span.offsetWidth; // force reflow
93
+ width = span.scrollWidth;
94
+ }
95
+
96
+ return {
97
+ height: Math.max(scrollHeight, rectHeight),
98
+ width,
99
+ };
57
100
  } finally {
58
101
  span.style.height = prevHeight;
102
+ span.style.width = prevWidth;
59
103
  span.style.overflow = prevOverflow;
60
104
  }
61
105
  }
62
106
 
63
- function measureSpanContentWidth(span: HTMLElement): number {
64
- const prevWidth = span.style.width;
65
- const prevOverflow = span.style.overflow;
66
- try {
67
- span.style.width = 'max-content';
68
- span.style.overflow = 'hidden';
69
- void span.offsetWidth; // force reflow
70
- return span.scrollWidth;
71
- } finally {
72
- span.style.width = prevWidth;
73
- span.style.overflow = prevOverflow;
74
- }
107
+ function shouldKeepForeignObjectWidth(style: CSSStyleDeclaration): boolean {
108
+ const whiteSpace = style.whiteSpace;
109
+ const flexWrap = style.flexWrap;
110
+ const wordBreak = style.wordBreak;
111
+ const overflowWrap = style.overflowWrap;
112
+
113
+ return (
114
+ flexWrap === 'wrap' ||
115
+ flexWrap === 'wrap-reverse' ||
116
+ whiteSpace === 'pre-wrap' ||
117
+ whiteSpace === 'pre-line' ||
118
+ whiteSpace === 'normal' ||
119
+ overflowWrap === 'break-word' ||
120
+ wordBreak === 'break-word' ||
121
+ wordBreak === 'break-all'
122
+ );
75
123
  }
76
124
 
77
- // Returns [left, top, right, bottom] in SVG coordinates for a foreignObject,
125
+ function createCoordConverter(
126
+ svg: SVGSVGElement,
127
+ element: SVGGraphicsElement,
128
+ ): ((x: number, y: number) => SVGPoint) | null {
129
+ if (typeof element.getScreenCTM !== 'function') return null;
130
+ const screenCTM = element.getScreenCTM();
131
+ if (!screenCTM) return null;
132
+ const inverseCTM = screenCTM.inverse();
133
+
134
+ return (clientX: number, clientY: number) => {
135
+ const pt = svg.createSVGPoint();
136
+ pt.x = clientX;
137
+ pt.y = clientY;
138
+ return pt.matrixTransform(inverseCTM);
139
+ };
140
+ }
141
+
142
+ // Returns [left, top, right, bottom] in target coordinates for a foreignObject,
78
143
  // accounting for flex alignment: bottom/center-aligned content can overflow,
79
144
  // and horizontally aligned content can overflow as well.
80
145
  function getFOContentBoundsInSVG(
81
146
  fo: SVGForeignObjectElement,
82
- content: HTMLElement,
83
147
  toSVGCoord: (x: number, y: number) => SVGPoint,
148
+ {
149
+ contentHeight,
150
+ contentWidth,
151
+ keepForeignObjectWidth,
152
+ }: {
153
+ contentHeight: number;
154
+ contentWidth: number;
155
+ keepForeignObjectWidth: boolean;
156
+ },
84
157
  ): [number, number, number, number] {
85
158
  const foRect = fo.getBoundingClientRect();
86
159
  const foTopLeft = toSVGCoord(foRect.left, foRect.top);
@@ -98,20 +171,17 @@ function getFOContentBoundsInSVG(
98
171
  foRect.height > 0 ? foHeightSVG / foRect.height : 1;
99
172
  const svgUnitsPerClientPxX = foRect.width > 0 ? foWidthSVG / foRect.width : 1;
100
173
 
101
- // Measure actual content dimensions
102
- const realScrollHeight = measureSpanContentHeight(content);
103
174
  const contentHeightSVG =
104
- realScrollHeight > 0
105
- ? realScrollHeight * svgUnitsPerClientPxY
175
+ contentHeight > 0
176
+ ? contentHeight * svgUnitsPerClientPxY
106
177
  : foHeightSVG;
107
178
 
108
- const realScrollWidth = measureSpanContentWidth(content);
109
- const contentWidthSVG =
110
- realScrollWidth > 0 ? realScrollWidth * svgUnitsPerClientPxX : foWidthSVG;
111
-
112
- const computedStyle = window.getComputedStyle(content);
179
+ const computedStyle = window.getComputedStyle(fo.firstElementChild as Element);
113
180
  const alignItems = computedStyle.alignItems;
114
181
  const justifyContent = computedStyle.justifyContent;
182
+ const contentWidthSVG = keepForeignObjectWidth
183
+ ? foWidthSVG
184
+ : Math.max(foWidthSVG, contentWidth * svgUnitsPerClientPxX);
115
185
 
116
186
  // Calculate vertical bounds
117
187
  let top: number, bottom: number;
@@ -148,47 +218,91 @@ function getFOContentBoundsInSVG(
148
218
  return [left, top, right, bottom];
149
219
  }
150
220
 
151
- /**
152
- * Computes a viewBox that fully covers all foreignObject text content,
153
- * accounting for overflow caused by flex alignment (bottom/center align
154
- * can push content outside the foreignObject bounds).
155
- */
156
- function computeFullViewBox(svg: SVGSVGElement): string | null {
221
+ function collectForeignObjectExportAdjustments(svg: SVGSVGElement) {
222
+ const toSVGCoord = createCoordConverter(svg, svg);
223
+ if (!toSVGCoord) return [];
224
+
225
+ return Array.from(
226
+ svg.querySelectorAll<SVGForeignObjectElement>('foreignObject'),
227
+ ).map((fo) => {
228
+ const content = fo.firstElementChild as HTMLElement | null;
229
+ if (!content) return null;
230
+ const computedStyle = window.getComputedStyle(content);
231
+ const keepForeignObjectWidth = shouldKeepForeignObjectWidth(computedStyle);
232
+ const measuredContent = measureSpanContentDimensions(
233
+ content,
234
+ !keepForeignObjectWidth,
235
+ );
236
+
237
+ const parent =
238
+ fo.parentElement instanceof SVGGraphicsElement ? fo.parentElement : svg;
239
+ const toParentCoord = createCoordConverter(svg, parent);
240
+ const toLocalCoord = createCoordConverter(svg, fo);
241
+ if (!toParentCoord) return null;
242
+
243
+ const parentBounds = getFOContentBoundsInSVG(fo, toParentCoord, {
244
+ contentHeight: measuredContent.height,
245
+ contentWidth: measuredContent.width,
246
+ keepForeignObjectWidth,
247
+ });
248
+ const originalX = parseCoordinate(fo.getAttribute('x'));
249
+ const originalY = parseCoordinate(fo.getAttribute('y'));
250
+ const localBounds = toLocalCoord
251
+ ? getFOContentBoundsInSVG(fo, toLocalCoord, {
252
+ contentHeight: measuredContent.height,
253
+ contentWidth: measuredContent.width,
254
+ keepForeignObjectWidth,
255
+ })
256
+ : null;
257
+ const hasTransform = fo.hasAttribute('transform');
258
+ if (hasTransform && !localBounds) return null;
259
+
260
+ const exportBounds = localBounds
261
+ ? {
262
+ x: originalX + localBounds[0],
263
+ y: originalY + localBounds[1],
264
+ width: localBounds[2] - localBounds[0],
265
+ height: localBounds[3] - localBounds[1],
266
+ }
267
+ : {
268
+ x: parentBounds[0],
269
+ y: parentBounds[1],
270
+ width: parentBounds[2] - parentBounds[0],
271
+ height: parentBounds[3] - parentBounds[1],
272
+ };
273
+
274
+ return {
275
+ rootBounds: getFOContentBoundsInSVG(fo, toSVGCoord, {
276
+ contentHeight: measuredContent.height,
277
+ contentWidth: measuredContent.width,
278
+ keepForeignObjectWidth,
279
+ }),
280
+ exportBounds,
281
+ } satisfies ForeignObjectExportAdjustment;
282
+ });
283
+ }
284
+
285
+ function computeFullViewBox(
286
+ svg: SVGSVGElement,
287
+ adjustments: Array<ForeignObjectExportAdjustment | null>,
288
+ ): string | null {
157
289
  const viewBox = getExportViewBox(svg);
158
290
  if (!viewBox) return null;
159
291
 
160
- if (typeof svg.getScreenCTM !== 'function') return null;
161
- const screenCTM = svg.getScreenCTM();
162
- if (!screenCTM) return null;
163
- const inverseCTM = screenCTM.inverse();
164
-
165
- const toSVGCoord = (clientX: number, clientY: number) => {
166
- const pt = svg.createSVGPoint();
167
- pt.x = clientX;
168
- pt.y = clientY;
169
- return pt.matrixTransform(inverseCTM);
170
- };
171
-
172
292
  let minX = viewBox.x;
173
293
  let minY = viewBox.y;
174
294
  let maxX = viewBox.x + viewBox.width;
175
295
  let maxY = viewBox.y + viewBox.height;
176
296
 
177
- svg
178
- .querySelectorAll<SVGForeignObjectElement>('foreignObject')
179
- .forEach((fo) => {
180
- const content = fo.firstElementChild as HTMLElement;
181
- if (!content) return;
182
- const [left, top, right, bottom] = getFOContentBoundsInSVG(
183
- fo,
184
- content,
185
- toSVGCoord,
186
- );
187
- minX = Math.min(minX, left);
188
- minY = Math.min(minY, top);
189
- maxX = Math.max(maxX, right);
190
- maxY = Math.max(maxY, bottom);
191
- });
297
+ adjustments.forEach((adjustment) => {
298
+ if (!adjustment) return;
299
+ const { rootBounds } = adjustment;
300
+ const [left, top, right, bottom] = rootBounds;
301
+ minX = Math.min(minX, left);
302
+ minY = Math.min(minY, top);
303
+ maxX = Math.max(maxX, right);
304
+ maxY = Math.max(maxY, bottom);
305
+ });
192
306
 
193
307
  const newX = minX;
194
308
  const newY = minY;
@@ -205,6 +319,23 @@ function computeFullViewBox(svg: SVGSVGElement): string | null {
205
319
  return `${newX} ${newY} ${newWidth} ${newHeight}`;
206
320
  }
207
321
 
322
+ function applyForeignObjectExportAdjustments(
323
+ svg: SVGSVGElement,
324
+ adjustments: Array<ForeignObjectExportAdjustment | null>,
325
+ ) {
326
+ const clonedForeignObjects = Array.from(
327
+ svg.querySelectorAll<SVGForeignObjectElement>('foreignObject'),
328
+ );
329
+
330
+ adjustments.forEach((adjustment, index) => {
331
+ if (!adjustment) return;
332
+ const clonedForeignObject = clonedForeignObjects[index];
333
+ if (!clonedForeignObject) return;
334
+
335
+ setAttributes(clonedForeignObject, adjustment.exportBounds);
336
+ });
337
+ }
338
+
208
339
  export async function exportToSVG(
209
340
  svg: SVGSVGElement,
210
341
  options: Omit<SVGExportOptions, 'type'> = {},
@@ -217,7 +348,9 @@ export async function exportToSVG(
217
348
  const clonedSVG = svg.cloneNode(true) as SVGSVGElement;
218
349
 
219
350
  if (typeof document !== 'undefined') {
220
- const fullViewBox = computeFullViewBox(svg);
351
+ const adjustments = collectForeignObjectExportAdjustments(svg);
352
+ applyForeignObjectExportAdjustments(clonedSVG, adjustments);
353
+ const fullViewBox = computeFullViewBox(svg, adjustments);
221
354
  if (fullViewBox) {
222
355
  clonedSVG.setAttribute('viewBox', fullViewBox);
223
356
  }
@@ -253,7 +386,9 @@ async function embedIcons(svg: SVGSVGElement) {
253
386
 
254
387
  if (!existsSymbol) {
255
388
  const symbolElement = document.querySelector(href);
256
- if (symbolElement) defs.appendChild(symbolElement.cloneNode(true));
389
+ if (symbolElement) {
390
+ defs.appendChild(symbolElement.cloneNode(true));
391
+ }
257
392
  }
258
393
  });
259
394
  }
@@ -487,6 +622,7 @@ function collectDefElements(svg: SVGSVGElement, ids: Set<string>) {
487
622
 
488
623
  while (queue.length) {
489
624
  const id = queue.shift()!;
625
+ if (!id) continue;
490
626
  if (visited.has(id)) continue;
491
627
  visited.add(id);
492
628
 
@@ -503,11 +639,80 @@ function collectDefElements(svg: SVGSVGElement, ids: Set<string>) {
503
639
  return collected;
504
640
  }
505
641
 
642
+ // Fallback implementation based on the CSS.escape algorithm
643
+ function cssEscape(value: string): string {
644
+ const string = String(value);
645
+ const length = string.length;
646
+ let result = '';
647
+
648
+ if (length === 0) {
649
+ return '';
650
+ }
651
+
652
+ for (let i = 0; i < length; i++) {
653
+ const codeUnit = string.charCodeAt(i);
654
+
655
+ // Null character
656
+ if (codeUnit === 0x0000) {
657
+ result += '\uFFFD';
658
+ continue;
659
+ }
660
+
661
+ // Control characters or DEL
662
+ if ((codeUnit >= 0x0001 && codeUnit <= 0x001f) || codeUnit === 0x007f) {
663
+ result += '\\' + codeUnit.toString(16) + ' ';
664
+ continue;
665
+ }
666
+
667
+ // Escape if first character is a digit
668
+ if (i === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) {
669
+ result += '\\' + codeUnit.toString(16) + ' ';
670
+ continue;
671
+ }
672
+
673
+ // Escape if second character is a digit and first is a hyphen
674
+ if (
675
+ i === 1 &&
676
+ codeUnit >= 0x0030 &&
677
+ codeUnit <= 0x0039 &&
678
+ string.charCodeAt(0) === 0x002d
679
+ ) {
680
+ result += '\\' + codeUnit.toString(16) + ' ';
681
+ continue;
682
+ }
683
+
684
+ // If the character is the first and is a hyphen followed by end of string, escape it
685
+ if (i === 0 && length === 1 && codeUnit === 0x002d) {
686
+ result += '\\' + string.charAt(i);
687
+ continue;
688
+ }
689
+
690
+ // Characters that are safe to use unescaped
691
+ if (
692
+ codeUnit >= 0x0080 ||
693
+ (codeUnit >= 0x0030 && codeUnit <= 0x0039) || // 0-9
694
+ (codeUnit >= 0x0041 && codeUnit <= 0x005a) || // A-Z
695
+ (codeUnit >= 0x0061 && codeUnit <= 0x007a) || // a-z
696
+ codeUnit === 0x002d || // -
697
+ codeUnit === 0x005f // _
698
+ ) {
699
+ result += string.charAt(i);
700
+ continue;
701
+ }
702
+
703
+ // All other characters
704
+ result += '\\' + string.charAt(i);
705
+ }
706
+
707
+ return result;
708
+ }
709
+
506
710
  function escapeCssId(id: string) {
507
711
  if (globalThis.CSS && typeof globalThis.CSS.escape === 'function') {
508
712
  return globalThis.CSS.escape(id);
509
713
  }
510
- return id.replace(/([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g, '\\$1');
714
+
715
+ return cssEscape(id);
511
716
  }
512
717
 
513
718
  function removeDefs(svg: SVGSVGElement) {
@@ -43,9 +43,6 @@ export async function loadSearchResource(query: string, format?: string) {
43
43
  const svgText = commaIndex >= 0 ? result.slice(commaIndex + 1) : result;
44
44
  return loadSVGResource(svgText);
45
45
  }
46
- if (mimeType === 'image/svg+xml' && format === 'svg' && isBase64) {
47
- return loadImageBase64Resource(result);
48
- }
49
46
  return loadImageBase64Resource(result);
50
47
  }
51
48
 
@@ -43,13 +43,34 @@ function getCommonPrefixLength(source: string, target: string): number {
43
43
  const limit = Math.min(source.length, target.length);
44
44
  let index = 0;
45
45
 
46
- while (index < limit && source[index] === target[index]) {
46
+ while (
47
+ index < limit &&
48
+ source.charCodeAt(index) === target.charCodeAt(index)
49
+ ) {
47
50
  index += 1;
48
51
  }
49
52
 
50
53
  return index;
51
54
  }
52
55
 
56
+ function isBetterMatch(
57
+ bestMatch: string | undefined,
58
+ bestDistance: number,
59
+ bestPrefixLength: number,
60
+ candidateKey: string,
61
+ candidateDistance: number,
62
+ candidatePrefixLength: number,
63
+ ): boolean {
64
+ return (
65
+ candidateDistance < bestDistance ||
66
+ (candidateDistance === bestDistance &&
67
+ candidatePrefixLength > bestPrefixLength) ||
68
+ (candidateDistance === bestDistance &&
69
+ candidatePrefixLength === bestPrefixLength &&
70
+ (!bestMatch || candidateKey < bestMatch))
71
+ );
72
+ }
73
+
53
74
  export function findClosestTemplateKey(
54
75
  type: string,
55
76
  keys: Iterable<string>,
@@ -71,11 +92,14 @@ export function findClosestTemplateKey(
71
92
  const prefixLength = getCommonPrefixLength(normalizedType, normalizedKey);
72
93
 
73
94
  if (
74
- distance < bestDistance ||
75
- (distance === bestDistance && prefixLength > bestPrefixLength) ||
76
- (distance === bestDistance &&
77
- prefixLength === bestPrefixLength &&
78
- (!bestMatch || key < bestMatch))
95
+ isBetterMatch(
96
+ bestMatch,
97
+ bestDistance,
98
+ bestPrefixLength,
99
+ key,
100
+ distance,
101
+ prefixLength,
102
+ )
79
103
  ) {
80
104
  bestMatch = key;
81
105
  bestDistance = distance;
@@ -27,7 +27,7 @@ export function setSVGPadding(svg: SVGSVGElement, padding: ParsedPadding) {
27
27
  if (isNode) {
28
28
  setSVGPaddingInNode(svg, padding);
29
29
  } else {
30
- if (document.contains(svg)) {
30
+ if (svg.isConnected) {
31
31
  setSVGPaddingInBrowser(svg, padding);
32
32
  } else {
33
33
  try {
@@ -1,8 +1,35 @@
1
- export function injectStyleOnce(id: string, styles: string) {
2
- if (document.getElementById(id)) return;
1
+ type StyleTarget = Document | ShadowRoot | Node | null | undefined;
3
2
 
4
- const style = document.createElement('style');
3
+ function resolveStyleRoot(target?: StyleTarget): Document | ShadowRoot {
4
+ if (!target) return document;
5
+ if (target instanceof Document || target instanceof ShadowRoot) return target;
6
+ if (!target.isConnected) return document;
7
+
8
+ const root = target.getRootNode();
9
+ return root instanceof ShadowRoot ? root : document;
10
+ }
11
+
12
+ function hasStyle(root: Document | ShadowRoot, id: string) {
13
+ if (root instanceof Document) return Boolean(root.getElementById(id));
14
+ return Boolean(root.querySelector(`#${id}`));
15
+ }
16
+
17
+ export function injectStyleOnce(
18
+ id: string,
19
+ styles: string,
20
+ target?: StyleTarget,
21
+ ) {
22
+ const root = resolveStyleRoot(target);
23
+ if (hasStyle(root, id)) return;
24
+
25
+ const doc = root instanceof Document ? root : (root.ownerDocument ?? document);
26
+ const style = doc.createElement('style');
5
27
  style.id = id;
6
28
  style.textContent = styles;
7
- document.head.appendChild(style);
29
+
30
+ if (root instanceof Document) {
31
+ root.head.appendChild(style);
32
+ } else {
33
+ root.appendChild(style);
34
+ }
8
35
  }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.2.17';
1
+ export const VERSION = '0.2.19';