@antv/infographic 0.2.16 → 0.2.18

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 (131) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/infographic.min.js +121 -120
  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 +7 -4
  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 +229 -3
  32. package/esm/options/parser.js +8 -6
  33. package/esm/options/types.d.ts +3 -3
  34. package/esm/renderer/renderer.js +1 -1
  35. package/esm/resource/loaders/search.js +2 -6
  36. package/esm/runtime/options.js +1 -1
  37. package/esm/syntax/index.js +56 -10
  38. package/esm/syntax/mapper.js +20 -6
  39. package/esm/syntax/parser.js +9 -0
  40. package/esm/syntax/types.d.ts +1 -1
  41. package/esm/templates/registry.d.ts +1 -0
  42. package/esm/templates/registry.js +6 -0
  43. package/esm/templates/utils.d.ts +1 -0
  44. package/esm/templates/utils.js +68 -0
  45. package/esm/themes/built-in.js +3 -0
  46. package/esm/utils/padding.js +1 -1
  47. package/esm/utils/style.d.ts +3 -1
  48. package/esm/utils/style.js +27 -4
  49. package/esm/version.d.ts +1 -1
  50. package/esm/version.js +1 -1
  51. package/lib/constants/service.d.ts +1 -1
  52. package/lib/constants/service.js +1 -1
  53. package/lib/designs/structures/chart-line.js +7 -4
  54. package/lib/editor/interactions/dblclick-edit-text.js +3 -3
  55. package/lib/editor/managers/interaction.js +7 -5
  56. package/lib/editor/plugins/components/button.d.ts +2 -1
  57. package/lib/editor/plugins/components/button.js +4 -4
  58. package/lib/editor/plugins/components/color-picker.d.ts +1 -0
  59. package/lib/editor/plugins/components/color-picker.js +3 -3
  60. package/lib/editor/plugins/components/popover.d.ts +3 -1
  61. package/lib/editor/plugins/components/popover.js +32 -12
  62. package/lib/editor/plugins/edit-bar/edit-bar.d.ts +3 -1
  63. package/lib/editor/plugins/edit-bar/edit-bar.js +17 -7
  64. package/lib/editor/plugins/edit-bar/edit-items/align-elements.js +6 -4
  65. package/lib/editor/plugins/edit-bar/edit-items/font-align.js +8 -5
  66. package/lib/editor/plugins/edit-bar/edit-items/font-color.js +7 -4
  67. package/lib/editor/plugins/edit-bar/edit-items/font-family.js +11 -9
  68. package/lib/editor/plugins/edit-bar/edit-items/font-size.js +8 -5
  69. package/lib/editor/plugins/edit-bar/edit-items/icon-color.js +7 -4
  70. package/lib/editor/plugins/edit-bar/edit-items/types.d.ts +5 -1
  71. package/lib/editor/plugins/reset-viewbox.d.ts +4 -1
  72. package/lib/editor/plugins/reset-viewbox.js +12 -6
  73. package/lib/editor/utils/index.d.ts +1 -0
  74. package/lib/editor/utils/index.js +1 -0
  75. package/lib/editor/utils/root.d.ts +3 -0
  76. package/lib/editor/utils/root.js +22 -0
  77. package/lib/exporter/svg.js +229 -3
  78. package/lib/options/parser.js +7 -5
  79. package/lib/options/types.d.ts +3 -3
  80. package/lib/renderer/renderer.js +1 -1
  81. package/lib/resource/loaders/search.js +2 -6
  82. package/lib/runtime/options.js +1 -1
  83. package/lib/syntax/index.js +56 -10
  84. package/lib/syntax/mapper.js +20 -6
  85. package/lib/syntax/parser.js +9 -0
  86. package/lib/syntax/types.d.ts +1 -1
  87. package/lib/templates/registry.d.ts +1 -0
  88. package/lib/templates/registry.js +7 -0
  89. package/lib/templates/utils.d.ts +1 -0
  90. package/lib/templates/utils.js +71 -0
  91. package/lib/themes/built-in.js +3 -0
  92. package/lib/utils/padding.js +1 -1
  93. package/lib/utils/style.d.ts +3 -1
  94. package/lib/utils/style.js +27 -4
  95. package/lib/version.d.ts +1 -1
  96. package/lib/version.js +1 -1
  97. package/package.json +1 -1
  98. package/src/constants/service.ts +1 -1
  99. package/src/designs/structures/chart-line.tsx +8 -4
  100. package/src/editor/interactions/dblclick-edit-text.ts +3 -2
  101. package/src/editor/managers/interaction.ts +9 -7
  102. package/src/editor/plugins/components/button.ts +5 -2
  103. package/src/editor/plugins/components/color-picker.ts +4 -2
  104. package/src/editor/plugins/components/popover.ts +31 -12
  105. package/src/editor/plugins/edit-bar/edit-bar.ts +26 -11
  106. package/src/editor/plugins/edit-bar/edit-items/align-elements.ts +7 -2
  107. package/src/editor/plugins/edit-bar/edit-items/font-align.ts +8 -3
  108. package/src/editor/plugins/edit-bar/edit-items/font-color.ts +7 -2
  109. package/src/editor/plugins/edit-bar/edit-items/font-family.ts +11 -7
  110. package/src/editor/plugins/edit-bar/edit-items/font-size.ts +8 -3
  111. package/src/editor/plugins/edit-bar/edit-items/icon-color.ts +7 -2
  112. package/src/editor/plugins/edit-bar/edit-items/types.ts +6 -1
  113. package/src/editor/plugins/reset-viewbox.ts +17 -8
  114. package/src/editor/utils/index.ts +1 -0
  115. package/src/editor/utils/root.ts +26 -0
  116. package/src/exporter/svg.ts +274 -3
  117. package/src/options/parser.ts +7 -6
  118. package/src/options/types.ts +3 -3
  119. package/src/renderer/renderer.ts +1 -1
  120. package/src/resource/loaders/search.ts +2 -5
  121. package/src/runtime/options.ts +1 -1
  122. package/src/syntax/index.ts +71 -10
  123. package/src/syntax/mapper.ts +20 -6
  124. package/src/syntax/parser.ts +10 -0
  125. package/src/syntax/types.ts +1 -0
  126. package/src/templates/registry.ts +6 -0
  127. package/src/templates/utils.ts +111 -0
  128. package/src/themes/built-in.ts +4 -0
  129. package/src/utils/padding.ts +1 -1
  130. package/src/utils/style.ts +31 -4
  131. package/src/version.ts +1 -1
@@ -3,8 +3,9 @@ import { Plugin } from './base';
3
3
  export interface ResetViewBoxOptions {
4
4
  style?: Partial<CSSStyleDeclaration>;
5
5
  className?: string;
6
- getContainer?: HTMLElement | (() => HTMLElement);
6
+ getContainer?: OverlayRoot | (() => OverlayRoot);
7
7
  }
8
+ type OverlayRoot = HTMLElement | ShadowRoot;
8
9
  export declare class ResetViewBox extends Plugin implements IPlugin {
9
10
  private options?;
10
11
  name: string;
@@ -29,5 +30,7 @@ export declare class ResetViewBox extends Plugin implements IPlugin {
29
30
  private showButton;
30
31
  private hideButton;
31
32
  private removeButton;
33
+ private resolveOverlayRoot;
32
34
  private ensureButtonStyle;
33
35
  }
36
+ export {};
@@ -1,6 +1,7 @@
1
1
  import { COMPONENT_ROLE } from '../../constants/index.js';
2
2
  import { getBoundViewBox, getViewBox, injectStyleOnce, parsePadding, setElementRole, viewBoxToString, } from '../../utils/index.js';
3
3
  import { UpdateOptionsCommand } from '../commands/index.js';
4
+ import { getOverlayContainer } from '../utils/index.js';
4
5
  import { Plugin } from './base.js';
5
6
  import { IconButton } from './components/index.js';
6
7
  import { RESET_ICON } from './components/icons.js';
@@ -33,9 +34,12 @@ export class ResetViewBox extends Plugin {
33
34
  if (this.resetButton)
34
35
  return this.resetButton;
35
36
  const { style, className } = this.options || {};
37
+ const containerParent = this.resolveOverlayRoot();
38
+ this.ensureButtonStyle(containerParent);
36
39
  const button = IconButton({
37
40
  icon: RESET_ICON,
38
41
  onClick: this.resetViewBox,
42
+ root: containerParent,
39
43
  });
40
44
  button.classList.add(RESET_BUTTON_CLASS);
41
45
  if (className) {
@@ -46,9 +50,6 @@ export class ResetViewBox extends Plugin {
46
50
  }
47
51
  setElementRole(button, COMPONENT_ROLE);
48
52
  this.resetButton = button;
49
- const { getContainer } = this.options || {};
50
- const resolvedContainer = typeof getContainer === 'function' ? getContainer() : getContainer;
51
- const containerParent = resolvedContainer !== null && resolvedContainer !== void 0 ? resolvedContainer : document.body;
52
53
  containerParent === null || containerParent === void 0 ? void 0 : containerParent.appendChild(button);
53
54
  return button;
54
55
  };
@@ -139,7 +140,7 @@ export class ResetViewBox extends Plugin {
139
140
  init(options) {
140
141
  super.init(options);
141
142
  // Initialize originViewBox
142
- this.ensureButtonStyle();
143
+ this.ensureButtonStyle(this.resolveOverlayRoot());
143
144
  this.updateOriginViewBox();
144
145
  this.unregisterSync = this.editor.registerSync('viewBox', this.handleViewBoxChange);
145
146
  window.addEventListener('resize', this.handleResize);
@@ -161,7 +162,12 @@ export class ResetViewBox extends Plugin {
161
162
  const { padding } = this.state.getOptions();
162
163
  this.originViewBox = getBoundViewBox(svg, parsePadding(padding));
163
164
  }
164
- ensureButtonStyle() {
165
+ resolveOverlayRoot() {
166
+ const { getContainer } = this.options || {};
167
+ const resolvedContainer = typeof getContainer === 'function' ? getContainer() : getContainer;
168
+ return resolvedContainer !== null && resolvedContainer !== void 0 ? resolvedContainer : getOverlayContainer(this.editor.getDocument());
169
+ }
170
+ ensureButtonStyle(target) {
165
171
  injectStyleOnce(RESET_BUTTON_STYLE_ID, `
166
172
  button.${RESET_BUTTON_CLASS} {
167
173
  visibility: hidden;
@@ -179,7 +185,7 @@ export class ResetViewBox extends Plugin {
179
185
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
180
186
  cursor: pointer;
181
187
  }
182
- `);
188
+ `, target);
183
189
  }
184
190
  }
185
191
  const RESET_BUTTON_CLASS = 'infographic-reset-viewbox-btn';
@@ -6,3 +6,4 @@ export * from './event';
6
6
  export * from './extension';
7
7
  export * from './hotkey';
8
8
  export * from './object';
9
+ export * from './root';
@@ -6,3 +6,4 @@ export * from './event.js';
6
6
  export * from './extension.js';
7
7
  export * from './hotkey.js';
8
8
  export * from './object.js';
9
+ export * from './root.js';
@@ -0,0 +1,3 @@
1
+ export type OverlayContainer = HTMLElement | ShadowRoot;
2
+ export declare function getOverlayContainer(node?: Node | null): OverlayContainer;
3
+ export declare function eventPathContains(event: Event, node: Node): boolean;
@@ -0,0 +1,18 @@
1
+ function getConnectedRoot(node) {
2
+ if (!(node === null || node === void 0 ? void 0 : node.isConnected))
3
+ return document;
4
+ const root = node.getRootNode();
5
+ return root instanceof ShadowRoot ? root : document;
6
+ }
7
+ export function getOverlayContainer(node) {
8
+ const root = getConnectedRoot(node);
9
+ return root instanceof ShadowRoot ? root : document.body;
10
+ }
11
+ export function eventPathContains(event, node) {
12
+ const path = typeof event.composedPath === 'function' ? event.composedPath() : [];
13
+ if (path.length > 0) {
14
+ return path.some((current) => current === node || (current instanceof Node && node.contains(current)));
15
+ }
16
+ const target = event.target;
17
+ return target instanceof Node && (target === node || node.contains(target));
18
+ }
@@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { createElement, getElementByRole, getViewBox, setAttributes, setElementRole, traverse, } from '../utils/index.js';
11
11
  import { embedFonts } from './font.js';
12
+ const VIEWBOX_CHANGE_TOLERANCE = 0.5;
12
13
  export function exportToSVGString(svg_1) {
13
14
  return __awaiter(this, arguments, void 0, function* (svg, options = {}) {
14
15
  const node = yield exportToSVG(svg, options);
@@ -16,11 +17,179 @@ export function exportToSVGString(svg_1) {
16
17
  return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(str);
17
18
  });
18
19
  }
20
+ function getExportViewBox(svg) {
21
+ if (svg.hasAttribute('viewBox'))
22
+ return getViewBox(svg);
23
+ const width = parseAbsoluteLength(svg.getAttribute('width'));
24
+ const height = parseAbsoluteLength(svg.getAttribute('height'));
25
+ if (!Number.isNaN(width) &&
26
+ width > 0 &&
27
+ !Number.isNaN(height) &&
28
+ height > 0) {
29
+ return { x: 0, y: 0, width, height };
30
+ }
31
+ const rect = svg.getBoundingClientRect();
32
+ if (rect.width > 0 && rect.height > 0) {
33
+ return { x: 0, y: 0, width: rect.width, height: rect.height };
34
+ }
35
+ return null;
36
+ }
37
+ function parseAbsoluteLength(value) {
38
+ if (!value)
39
+ return Number.NaN;
40
+ const trimmed = value.trim();
41
+ if (!trimmed)
42
+ return Number.NaN;
43
+ if (!/^[-+]?(?:\d+\.?\d*|\.\d+)(?:px)?$/.test(trimmed))
44
+ return Number.NaN;
45
+ return Number.parseFloat(trimmed);
46
+ }
47
+ function measureSpanContentHeight(span) {
48
+ const prevHeight = span.style.height;
49
+ const prevOverflow = span.style.overflow;
50
+ try {
51
+ span.style.height = 'max-content';
52
+ span.style.overflow = 'hidden';
53
+ void span.offsetHeight; // force reflow
54
+ return span.scrollHeight;
55
+ }
56
+ finally {
57
+ span.style.height = prevHeight;
58
+ span.style.overflow = prevOverflow;
59
+ }
60
+ }
61
+ function measureSpanContentWidth(span) {
62
+ const prevWidth = span.style.width;
63
+ const prevOverflow = span.style.overflow;
64
+ try {
65
+ span.style.width = 'max-content';
66
+ span.style.overflow = 'hidden';
67
+ void span.offsetWidth; // force reflow
68
+ return span.scrollWidth;
69
+ }
70
+ finally {
71
+ span.style.width = prevWidth;
72
+ span.style.overflow = prevOverflow;
73
+ }
74
+ }
75
+ // Returns [left, top, right, bottom] in SVG coordinates for a foreignObject,
76
+ // accounting for flex alignment: bottom/center-aligned content can overflow,
77
+ // and horizontally aligned content can overflow as well.
78
+ function getFOContentBoundsInSVG(fo, content, toSVGCoord) {
79
+ const foRect = fo.getBoundingClientRect();
80
+ const foTopLeft = toSVGCoord(foRect.left, foRect.top);
81
+ const foBottomRight = toSVGCoord(foRect.right, foRect.bottom);
82
+ const foLeftSVG = foTopLeft.x;
83
+ const foTopSVG = foTopLeft.y;
84
+ const foRightSVG = foBottomRight.x;
85
+ const foBottomSVG = foBottomRight.y;
86
+ const foWidthSVG = foRightSVG - foLeftSVG;
87
+ const foHeightSVG = foBottomSVG - foTopSVG;
88
+ const svgUnitsPerClientPxY = foRect.height > 0 ? foHeightSVG / foRect.height : 1;
89
+ const svgUnitsPerClientPxX = foRect.width > 0 ? foWidthSVG / foRect.width : 1;
90
+ // Measure actual content dimensions
91
+ const realScrollHeight = measureSpanContentHeight(content);
92
+ const contentHeightSVG = realScrollHeight > 0
93
+ ? realScrollHeight * svgUnitsPerClientPxY
94
+ : foHeightSVG;
95
+ const realScrollWidth = measureSpanContentWidth(content);
96
+ const contentWidthSVG = realScrollWidth > 0 ? realScrollWidth * svgUnitsPerClientPxX : foWidthSVG;
97
+ const computedStyle = window.getComputedStyle(content);
98
+ const alignItems = computedStyle.alignItems;
99
+ const justifyContent = computedStyle.justifyContent;
100
+ // Calculate vertical bounds
101
+ let top, bottom;
102
+ if (alignItems === 'flex-end' || alignItems === 'end') {
103
+ top = foBottomSVG - contentHeightSVG;
104
+ bottom = foBottomSVG;
105
+ }
106
+ else if (alignItems === 'center') {
107
+ const overflowY = contentHeightSVG - foHeightSVG;
108
+ top = foTopSVG - overflowY / 2;
109
+ bottom = foBottomSVG + overflowY / 2;
110
+ }
111
+ else {
112
+ top = foTopSVG;
113
+ bottom = foTopSVG + contentHeightSVG;
114
+ }
115
+ // Calculate horizontal bounds
116
+ let left, right;
117
+ if (justifyContent === 'flex-end' ||
118
+ justifyContent === 'end' ||
119
+ justifyContent === 'right') {
120
+ left = foRightSVG - contentWidthSVG;
121
+ right = foRightSVG;
122
+ }
123
+ else if (justifyContent === 'center') {
124
+ const overflowX = contentWidthSVG - foWidthSVG;
125
+ left = foLeftSVG - overflowX / 2;
126
+ right = foRightSVG + overflowX / 2;
127
+ }
128
+ else {
129
+ left = foLeftSVG;
130
+ right = foLeftSVG + contentWidthSVG;
131
+ }
132
+ return [left, top, right, bottom];
133
+ }
134
+ /**
135
+ * Computes a viewBox that fully covers all foreignObject text content,
136
+ * accounting for overflow caused by flex alignment (bottom/center align
137
+ * can push content outside the foreignObject bounds).
138
+ */
139
+ function computeFullViewBox(svg) {
140
+ const viewBox = getExportViewBox(svg);
141
+ if (!viewBox)
142
+ return null;
143
+ if (typeof svg.getScreenCTM !== 'function')
144
+ return null;
145
+ const screenCTM = svg.getScreenCTM();
146
+ if (!screenCTM)
147
+ return null;
148
+ const inverseCTM = screenCTM.inverse();
149
+ const toSVGCoord = (clientX, clientY) => {
150
+ const pt = svg.createSVGPoint();
151
+ pt.x = clientX;
152
+ pt.y = clientY;
153
+ return pt.matrixTransform(inverseCTM);
154
+ };
155
+ let minX = viewBox.x;
156
+ let minY = viewBox.y;
157
+ let maxX = viewBox.x + viewBox.width;
158
+ let maxY = viewBox.y + viewBox.height;
159
+ svg
160
+ .querySelectorAll('foreignObject')
161
+ .forEach((fo) => {
162
+ const content = fo.firstElementChild;
163
+ if (!content)
164
+ return;
165
+ const [left, top, right, bottom] = getFOContentBoundsInSVG(fo, content, toSVGCoord);
166
+ minX = Math.min(minX, left);
167
+ minY = Math.min(minY, top);
168
+ maxX = Math.max(maxX, right);
169
+ maxY = Math.max(maxY, bottom);
170
+ });
171
+ const newX = minX;
172
+ const newY = minY;
173
+ const newWidth = maxX - newX;
174
+ const newHeight = maxY - newY;
175
+ if (newWidth <= viewBox.width + VIEWBOX_CHANGE_TOLERANCE &&
176
+ newHeight <= viewBox.height + VIEWBOX_CHANGE_TOLERANCE &&
177
+ newX >= viewBox.x - VIEWBOX_CHANGE_TOLERANCE &&
178
+ newY >= viewBox.y - VIEWBOX_CHANGE_TOLERANCE)
179
+ return null;
180
+ return `${newX} ${newY} ${newWidth} ${newHeight}`;
181
+ }
19
182
  export function exportToSVG(svg_1) {
20
183
  return __awaiter(this, arguments, void 0, function* (svg, options = {}) {
21
184
  const { removeBackground = false, embedResources = true, removeIds = false, } = options;
22
185
  const clonedSVG = svg.cloneNode(true);
23
- const { width, height } = getViewBox(svg);
186
+ if (typeof document !== 'undefined') {
187
+ const fullViewBox = computeFullViewBox(svg);
188
+ if (fullViewBox) {
189
+ clonedSVG.setAttribute('viewBox', fullViewBox);
190
+ }
191
+ }
192
+ const { width, height } = getViewBox(clonedSVG);
24
193
  setAttributes(clonedSVG, { width, height });
25
194
  if (removeIds) {
26
195
  inlineUseElements(clonedSVG);
@@ -48,8 +217,9 @@ function embedIcons(svg) {
48
217
  const existsSymbol = svg.querySelector(href);
49
218
  if (!existsSymbol) {
50
219
  const symbolElement = document.querySelector(href);
51
- if (symbolElement)
220
+ if (symbolElement) {
52
221
  defs.appendChild(symbolElement.cloneNode(true));
222
+ }
53
223
  }
54
224
  });
55
225
  });
@@ -257,6 +427,8 @@ function collectDefElements(svg, ids) {
257
427
  };
258
428
  while (queue.length) {
259
429
  const id = queue.shift();
430
+ if (!id)
431
+ continue;
260
432
  if (visited.has(id))
261
433
  continue;
262
434
  visited.add(id);
@@ -271,11 +443,65 @@ function collectDefElements(svg, ids) {
271
443
  }
272
444
  return collected;
273
445
  }
446
+ // Fallback implementation based on the CSS.escape algorithm
447
+ function cssEscape(value) {
448
+ const string = String(value);
449
+ const length = string.length;
450
+ let result = '';
451
+ if (length === 0) {
452
+ return '';
453
+ }
454
+ for (let i = 0; i < length; i++) {
455
+ const codeUnit = string.charCodeAt(i);
456
+ // Null character
457
+ if (codeUnit === 0x0000) {
458
+ result += '\uFFFD';
459
+ continue;
460
+ }
461
+ // Control characters or DEL
462
+ if ((codeUnit >= 0x0001 && codeUnit <= 0x001f) || codeUnit === 0x007f) {
463
+ result += '\\' + codeUnit.toString(16) + ' ';
464
+ continue;
465
+ }
466
+ // Escape if first character is a digit
467
+ if (i === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) {
468
+ result += '\\' + codeUnit.toString(16) + ' ';
469
+ continue;
470
+ }
471
+ // Escape if second character is a digit and first is a hyphen
472
+ if (i === 1 &&
473
+ codeUnit >= 0x0030 &&
474
+ codeUnit <= 0x0039 &&
475
+ string.charCodeAt(0) === 0x002d) {
476
+ result += '\\' + codeUnit.toString(16) + ' ';
477
+ continue;
478
+ }
479
+ // If the character is the first and is a hyphen followed by end of string, escape it
480
+ if (i === 0 && length === 1 && codeUnit === 0x002d) {
481
+ result += '\\' + string.charAt(i);
482
+ continue;
483
+ }
484
+ // Characters that are safe to use unescaped
485
+ if (codeUnit >= 0x0080 ||
486
+ (codeUnit >= 0x0030 && codeUnit <= 0x0039) || // 0-9
487
+ (codeUnit >= 0x0041 && codeUnit <= 0x005a) || // A-Z
488
+ (codeUnit >= 0x0061 && codeUnit <= 0x007a) || // a-z
489
+ codeUnit === 0x002d || // -
490
+ codeUnit === 0x005f // _
491
+ ) {
492
+ result += string.charAt(i);
493
+ continue;
494
+ }
495
+ // All other characters
496
+ result += '\\' + string.charAt(i);
497
+ }
498
+ return result;
499
+ }
274
500
  function escapeCssId(id) {
275
501
  if (globalThis.CSS && typeof globalThis.CSS.escape === 'function') {
276
502
  return globalThis.CSS.escape(id);
277
503
  }
278
- return id.replace(/([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g, '\\$1');
504
+ return cssEscape(id);
279
505
  }
280
506
  function removeDefs(svg) {
281
507
  const defsList = Array.from(svg.querySelectorAll('defs'));
@@ -10,17 +10,19 @@ var __rest = (this && this.__rest) || function (s, e) {
10
10
  return t;
11
11
  };
12
12
  import { merge } from 'lodash-es';
13
- import { getItem, getStructure, getTemplate, Title, } from '../designs/index.js';
13
+ import { getItem, getStructure, Title, } from '../designs/index.js';
14
14
  import { getPaletteColor } from '../renderer/index.js';
15
+ import { getTemplate, resolveTemplateKey } from '../templates/registry.js';
15
16
  import { generateThemeColors, getTheme } from '../themes/index.js';
16
17
  import { isDarkColor, isNonNullableParsedDesignsOptions, parsePadding, } from '../utils/index.js';
17
18
  export function parseOptions(options) {
18
19
  const { container = '#container', padding = 0, template, design, theme, themeConfig, data } = options, restOptions = __rest(options, ["container", "padding", "template", "design", "theme", "themeConfig", "data"]);
20
+ const resolvedTemplate = template ? resolveTemplateKey(template) : undefined;
19
21
  const parsedContainer = typeof container === 'string'
20
22
  ? document.querySelector(container) || document.createElement('div')
21
23
  : container;
22
- const templateOptions = template
23
- ? getTemplate(template)
24
+ const templateOptions = resolvedTemplate
25
+ ? getTemplate(resolvedTemplate)
24
26
  : undefined;
25
27
  const mergedThemeConfig = merge({}, templateOptions === null || templateOptions === void 0 ? void 0 : templateOptions.themeConfig, themeConfig);
26
28
  const resolvedThemeConfig = theme || themeConfig || (templateOptions === null || templateOptions === void 0 ? void 0 : templateOptions.themeConfig)
@@ -35,11 +37,11 @@ export function parseOptions(options) {
35
37
  Object.assign(parsed, restTemplateOptions);
36
38
  }
37
39
  Object.assign(parsed, restOptions);
38
- const parsedData = parseData(data, options.template);
40
+ const parsedData = parseData(data, resolvedTemplate);
39
41
  if (parsedData)
40
42
  parsed.data = parsedData;
41
- if (template)
42
- parsed.template = template;
43
+ if (resolvedTemplate)
44
+ parsed.template = resolvedTemplate;
43
45
  if ((templateOptions === null || templateOptions === void 0 ? void 0 : templateOptions.design) || design) {
44
46
  const designOptions = Object.assign(Object.assign({}, (resolvedThemeConfig
45
47
  ? Object.assign(Object.assign({}, options), { themeConfig: resolvedThemeConfig }) : options)), { data: parsedData || data });
@@ -4,8 +4,8 @@ import type { ThemeConfig } from '../themes';
4
4
  import type { Data, Padding, ParsedData } from '../types';
5
5
  import type { Path } from '../utils';
6
6
  export interface InfographicOptions {
7
- /** 容器,可以是选择器或者 HTMLElement */
8
- container?: string | HTMLElement;
7
+ /** 容器,可以是选择器、Element ShadowRoot */
8
+ container?: string | Element | ShadowRoot;
9
9
  /** 宽度 */
10
10
  width?: number | string;
11
11
  /** 高度 */
@@ -34,7 +34,7 @@ export interface InfographicOptions {
34
34
  elements?: ElementProps[];
35
35
  }
36
36
  export interface ParsedInfographicOptions {
37
- container: HTMLElement;
37
+ container: Element | ShadowRoot;
38
38
  width?: number | string;
39
39
  height?: number | string;
40
40
  padding?: Padding;
@@ -47,7 +47,7 @@ export class Renderer {
47
47
  });
48
48
  });
49
49
  try {
50
- observer.observe(document, {
50
+ observer.observe(this.options.container, {
51
51
  childList: true,
52
52
  subtree: true,
53
53
  });
@@ -13,7 +13,6 @@ import { loadImageBase64Resource } from './image.js';
13
13
  import { loadRemoteResource } from './remote.js';
14
14
  import { loadSVGResource } from './svg.js';
15
15
  const queryIcon = (query) => __awaiter(void 0, void 0, void 0, function* () {
16
- var _a;
17
16
  try {
18
17
  const params = new URLSearchParams({ text: query, topK: '1' });
19
18
  const url = `${ICON_SERVICE_URL}?${params.toString()}`;
@@ -21,9 +20,9 @@ const queryIcon = (query) => __awaiter(void 0, void 0, void 0, function* () {
21
20
  if (!response.ok)
22
21
  return null;
23
22
  const result = yield response.json();
24
- if (!(result === null || result === void 0 ? void 0 : result.status) || !Array.isArray((_a = result.data) === null || _a === void 0 ? void 0 : _a.data))
23
+ if (!(result === null || result === void 0 ? void 0 : result.success) || !Array.isArray(result.data))
25
24
  return null;
26
- return result.data.data[0] || null;
25
+ return result.data[0] || null;
27
26
  }
28
27
  catch (error) {
29
28
  console.error(`Failed to query icon for "${query}":`, error);
@@ -55,9 +54,6 @@ export function loadSearchResource(query, format) {
55
54
  const svgText = commaIndex >= 0 ? result.slice(commaIndex + 1) : result;
56
55
  return loadSVGResource(svgText);
57
56
  }
58
- if (mimeType === 'image/svg+xml' && format === 'svg' && isBase64) {
59
- return loadImageBase64Resource(result);
60
- }
61
57
  return loadImageBase64Resource(result);
62
58
  }
63
59
  return loadRemoteResource(result, format);
@@ -16,7 +16,7 @@ const createDefaultInteractions = () => [
16
16
  ];
17
17
  export const DEFAULT_OPTIONS = {
18
18
  padding: 20,
19
- theme: 'light',
19
+ theme: 'default',
20
20
  themeConfig: {
21
21
  palette: 'antv',
22
22
  },
@@ -13,6 +13,15 @@ import { mapWithSchema } from './mapper.js';
13
13
  import { parseSyntaxToAst } from './parser.js';
14
14
  import { parseRelationsNode } from './relations.js';
15
15
  import { DataSchema, DesignSchema, RootSchema, TemplateSchema, ThemeSchema, } from './schema.js';
16
+ const ALLOWED_ROOT_KEYS = new Set([
17
+ 'infographic',
18
+ 'template',
19
+ 'design',
20
+ 'data',
21
+ 'theme',
22
+ 'width',
23
+ 'height',
24
+ ]);
16
25
  function normalizeItems(items) {
17
26
  var _a;
18
27
  const seen = new Set();
@@ -33,6 +42,13 @@ function normalizeItems(items) {
33
42
  }
34
43
  return normalized.reverse();
35
44
  }
45
+ function assignMissingNodeIds(items) {
46
+ items.forEach((item) => {
47
+ if (!item.id && item.label) {
48
+ item.id = item.label;
49
+ }
50
+ });
51
+ }
36
52
  function resolveTemplate(node, errors) {
37
53
  if (!node)
38
54
  return undefined;
@@ -46,12 +62,42 @@ function resolveTemplate(node, errors) {
46
62
  }
47
63
  return undefined;
48
64
  }
65
+ function inferTemplateFromBareFirstLine(entries, warnings) {
66
+ if ('infographic' in entries || 'template' in entries) {
67
+ return undefined;
68
+ }
69
+ const [firstEntry] = Object.entries(entries);
70
+ if (!firstEntry)
71
+ return undefined;
72
+ const [key, node] = firstEntry;
73
+ if (ALLOWED_ROOT_KEYS.has(key) || node.kind !== 'object') {
74
+ return undefined;
75
+ }
76
+ if (node.value !== undefined || Object.keys(node.entries).length > 0) {
77
+ return undefined;
78
+ }
79
+ warnings.push({
80
+ path: 'template',
81
+ line: node.line,
82
+ code: 'implicit_template',
83
+ message: 'Inferred template from a bare first line. Prefix it with "infographic" or "template" to make the syntax explicit.',
84
+ raw: key,
85
+ });
86
+ return {
87
+ template: key,
88
+ inferredKey: key,
89
+ };
90
+ }
49
91
  export function parseSyntax(input) {
50
92
  var _a;
51
93
  const { ast, errors } = parseSyntaxToAst(input);
52
94
  const warnings = [];
53
95
  const options = {};
54
96
  const mergedEntries = Object.assign({}, ast.entries);
97
+ const inferredTemplate = inferTemplateFromBareFirstLine(ast.entries, warnings);
98
+ if (inferredTemplate) {
99
+ delete mergedEntries[inferredTemplate.inferredKey];
100
+ }
55
101
  const infographicNode = ast.entries.infographic;
56
102
  let templateFromInfographic;
57
103
  if (infographicNode && infographicNode.kind === 'object') {
@@ -62,17 +108,8 @@ export function parseSyntax(input) {
62
108
  mergedEntries[key] = value;
63
109
  });
64
110
  }
65
- const allowedRootKeys = new Set([
66
- 'infographic',
67
- 'template',
68
- 'design',
69
- 'data',
70
- 'theme',
71
- 'width',
72
- 'height',
73
- ]);
74
111
  Object.keys(mergedEntries).forEach((key) => {
75
- if (!allowedRootKeys.has(key)) {
112
+ if (!ALLOWED_ROOT_KEYS.has(key)) {
76
113
  errors.push({
77
114
  path: key,
78
115
  line: mergedEntries[key].line,
@@ -89,6 +126,9 @@ export function parseSyntax(input) {
89
126
  if (!options.template && templateFromInfographic) {
90
127
  options.template = templateFromInfographic;
91
128
  }
129
+ if (!options.template && inferredTemplate) {
130
+ options.template = inferredTemplate.template;
131
+ }
92
132
  const designNode = mergedEntries.design;
93
133
  if (designNode) {
94
134
  const design = mapWithSchema(designNode, DesignSchema, 'design', errors);
@@ -111,6 +151,12 @@ export function parseSyntax(input) {
111
151
  const parsed = parseRelationsNode(relationsNode, errors, 'data.relations');
112
152
  if (parsed.relations.length > 0 || parsed.items.length > 0) {
113
153
  const current = ((_a = options.data) !== null && _a !== void 0 ? _a : {});
154
+ if (Array.isArray(current.items)) {
155
+ assignMissingNodeIds(current.items);
156
+ }
157
+ if (Array.isArray(current.nodes)) {
158
+ assignMissingNodeIds(current.nodes);
159
+ }
114
160
  // 优先使用已存在的数据列表 (sequences, lists, etc.)
115
161
  const dataKeys = Object.keys(DataSchema.fields).filter((key) => key !== 'items' && key !== 'relations');
116
162
  let hasStructuredData = false;
@@ -5,11 +5,20 @@ function createValueNode(value, line) {
5
5
  }
6
6
  const HEX_COLOR_PATTERN = /^(#[0-9a-f]{8}|#[0-9a-f]{6}|#[0-9a-f]{4}|#[0-9a-f]{3})/i;
7
7
  const FUNCTION_COLOR_PATTERN = /^((?:rgb|rgba|hsl|hsla)\([^)]*\))/i;
8
+ function normalizeBooleanLiteral(value) {
9
+ const trimmed = value.trim();
10
+ if (/^true$/i.test(trimmed))
11
+ return 'true';
12
+ if (/^false$/i.test(trimmed))
13
+ return 'false';
14
+ return undefined;
15
+ }
8
16
  function parseScalar(value) {
9
17
  const trimmed = value.trim();
10
- if (trimmed === 'true')
18
+ const normalizedBoolean = normalizeBooleanLiteral(trimmed);
19
+ if (normalizedBoolean === 'true')
11
20
  return true;
12
- if (trimmed === 'false')
21
+ if (normalizedBoolean === 'false')
13
22
  return false;
14
23
  if (/^-?\d+(\.\d+)?$/.test(trimmed))
15
24
  return parseFloat(trimmed);
@@ -175,11 +184,12 @@ export function mapWithSchema(node, schema, path, errors) {
175
184
  addError(errors, node, path, 'schema_mismatch', 'Expected boolean value.');
176
185
  return undefined;
177
186
  }
178
- if (value !== 'true' && value !== 'false') {
187
+ const normalizedBoolean = normalizeBooleanLiteral(value);
188
+ if (!normalizedBoolean) {
179
189
  addError(errors, node, path, 'invalid_value', 'Invalid boolean value.', value);
180
190
  return undefined;
181
191
  }
182
- return value === 'true';
192
+ return normalizedBoolean === 'true';
183
193
  }
184
194
  case 'enum': {
185
195
  const value = readScalar(node);
@@ -187,11 +197,15 @@ export function mapWithSchema(node, schema, path, errors) {
187
197
  addError(errors, node, path, 'schema_mismatch', 'Expected enum value.');
188
198
  return undefined;
189
199
  }
190
- if (!schema.values.includes(value)) {
200
+ const normalizedBoolean = normalizeBooleanLiteral(value);
201
+ const enumValue = normalizedBoolean && schema.values.includes(normalizedBoolean)
202
+ ? normalizedBoolean
203
+ : value;
204
+ if (!schema.values.includes(enumValue)) {
191
205
  addError(errors, node, path, 'invalid_value', 'Invalid enum value.', value);
192
206
  return undefined;
193
207
  }
194
- return value;
208
+ return enumValue;
195
209
  }
196
210
  case 'array': {
197
211
  if (node.kind === 'array') {