vue-echarts 8.2.0 → 8.3.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/README.md CHANGED
@@ -122,7 +122,7 @@ Drop `<script>` inside your HTML file and access the component via `window.VueEC
122
122
  ```html
123
123
  <script src="https://cdn.jsdelivr.net/npm/echarts@6.1.0"></script>
124
124
  <script src="https://cdn.jsdelivr.net/npm/vue@3.5.41"></script>
125
- <script src="https://cdn.jsdelivr.net/npm/vue-echarts@8.2.0"></script>
125
+ <script src="https://cdn.jsdelivr.net/npm/vue-echarts@8.3.0"></script>
126
126
  ```
127
127
 
128
128
  <!-- scripts:end -->
@@ -152,6 +152,8 @@ See more examples [here](https://github.com/ecomfe/vue-echarts/tree/main/demo).
152
152
 
153
153
  Pass an empty string to use ECharts' default theme while overriding an injected theme.
154
154
 
155
+ ECharts recreates its model when changing themes. Vue ECharts reapplies the latest automatic option, but uncontrolled interaction state (such as legend selection or data zoom) may reset. Keep state that must survive a rebuild in `option`.
156
+
155
157
  Injection key: `THEME_KEY`.
156
158
 
157
159
  - `option: object`
@@ -160,9 +162,14 @@ See more examples [here](https://github.com/ecomfe/vue-echarts/tree/main/demo).
160
162
  Temporarily removing the prop pauses automatic updates without letting a later theme change roll the chart back to its initial option.
161
163
 
162
164
  #### Smart update
163
- - If you supply `update-options` (via prop or injection), Vue ECharts forwards it directly to `setOption` and skips the planner. After you remove it, the first smart update rebuilds once to establish a safe structural baseline.
165
+
166
+ Reactive updates describe the complete configuration. Vue ECharts preserves existing models where merging can apply that configuration, and rebuilds when necessary to remove stale settings. A rebuild can reset interaction state such as legend selections and data zoom.
167
+
168
+ - If you supply `update-options` (via prop or injection), Vue ECharts forwards it directly to `setOption` and skips the planner. After you remove it, the first smart source-option update rebuilds once to establish a safe structural baseline.
169
+ - A failed option or theme submission invalidates that baseline. The next smart update rebuilds instead of trusting a potentially partial update.
170
+ - Automatic option, theme, and slot changes are batched after Vue updates. `clear()` takes effect immediately and cancels already queued automatic work; later changes can populate the chart again.
164
171
  - Manual `setOption` calls (only available when `manual-update` is `true`) behave like native ECharts, honouring only the per-call override you pass in and are not carried across re-initializations.
165
- - Updates containing a graphic element `$action` keep normal merge for `graphic` so the command can target the existing element tree; safe removals of unrelated components still use `replaceMerge`.
172
+ - Updates containing a graphic element `$action` keep normal merge for `graphic` so the command can target the existing element tree; safe removals of unrelated components still use `replaceMerge`. Changes requiring a full rebuild cannot be applied together with these commands. For complete snapshot semantics, describe the resulting graphic tree without `$action` commands.
166
173
  - Otherwise, Vue ECharts analyses the change: component removals, reordering, and deletions inside anonymous components use `replaceMerge` when it can reproduce the requested order; deletions inside ID-matched components, identity ordering that `replaceMerge` cannot reproduce, newly introduced or shrinking non-component arrays, first-time ARIA configuration, and other risky changes fall back to `notMerge: true`.
167
174
 
168
175
  - `update-options: object`
@@ -542,6 +549,8 @@ Read more at [ECharts `option.graphic` →](https://echarts.apache.org/en/option
542
549
  > - Path components accept `auto-batch` to opt into ZRender's Canvas path batching.
543
550
  > - The `option` prop may be omitted for graphic-only charts.
544
551
  > - `#graphic` overrides `option.graphic`. In `manual-update` mode, call `chartRef.setOption(...)` to apply changes.
552
+ > - Wrapper components and Fragments preserve their rendered graphic order. Compatible property changes update only changed elements, preserving unchanged elements and their running animations. Removing fields, changing types, or changing tree structure rebuilds the graphic component.
553
+ > - Graphic-only changes omit unrelated source options when safe. Explicit `notMerge` or `replaceMerge` targeting other components still submits the full source option.
545
554
 
546
555
  <details>
547
556
  <summary>Usage</summary>
package/README.zh-Hans.md CHANGED
@@ -122,7 +122,7 @@ import "echarts";
122
122
  ```html
123
123
  <script src="https://cdn.jsdelivr.net/npm/echarts@6.1.0"></script>
124
124
  <script src="https://cdn.jsdelivr.net/npm/vue@3.5.41"></script>
125
- <script src="https://cdn.jsdelivr.net/npm/vue-echarts@8.2.0"></script>
125
+ <script src="https://cdn.jsdelivr.net/npm/vue-echarts@8.3.0"></script>
126
126
  ```
127
127
 
128
128
  <!-- scripts:end -->
@@ -150,6 +150,8 @@ app.component('VChart', VueECharts)
150
150
 
151
151
  要应用的主题。请参考 `echarts.init` 的 `theme` 参数。[前往 →](https://echarts.apache.org/zh/api.html#echarts.init)
152
152
 
153
+ ECharts 在切换主题时会重建模型。Vue ECharts 会重新应用最新的自动更新配置,但未受控的交互状态(如图例选择、数据缩放)可能重置。需要跨重建保留的状态应维护在 `option` 中。
154
+
153
155
  传入空字符串可以在覆盖注入主题的同时使用 ECharts 默认主题。
154
156
 
155
157
  Inject 键名:`THEME_KEY`。
@@ -160,9 +162,15 @@ app.component('VChart', VueECharts)
160
162
  暂时移除该 prop 会暂停自动更新,之后的主题变更也不会让图表回退到初始 option。
161
163
 
162
164
  #### 智能更新
163
- - 如果提供了 `update-options`(或通过 inject 注入),Vue ECharts 会直接把它传给 `setOption`,不会执行智能计划。移除它之后,首次智能更新会重建一次,以建立可靠的结构基线。
165
+
166
+ 自动 option、theme 和插槽变化会在 Vue 更新后合并处理。`clear()` 立即生效并取消已经排队的自动更新,后续变化仍可重新填充图表。
167
+
168
+ 响应式更新描述完整配置。Vue ECharts 会在合并能够正确应用配置时保留现有模型,并在必要时通过重建清除旧配置。重建可能重置图例选择、数据缩放等交互状态。
169
+
170
+ - 如果提供了 `update-options`(或通过 inject 注入),Vue ECharts 会直接把它传给 `setOption`,不会执行智能计划。移除它之后,首次源 option 智能更新会重建一次,以建立可靠的结构基线。
171
+ - option 或主题提交失败会使结构基线失效。下一次智能更新会重建,避免依赖可能只完成了一部分的更新。
164
172
  - 手动调用 `setOption`(仅当 `manual-update` 为 `true` 时可用)与原生 ECharts 保持一致,只使用本次调用传入的参数,重新初始化后不会保留这些调用的效果。
165
- - 如果更新中有 graphic 元素使用 `$action`,`graphic` 会保留普通合并,让命令能够作用于现有元素树;无关组件的安全删除仍会使用 `replaceMerge`。
173
+ - 如果更新中有 graphic 元素使用 `$action`,`graphic` 会保留普通合并,让命令能够作用于现有元素树;无关组件的安全删除仍会使用 `replaceMerge`。需要完整重建的变更无法与这些命令一起应用。若需要完整快照语义,请直接描述更新后的 graphic 元素树,不使用 `$action` 命令。
166
174
  - 其他情况下,Vue ECharts 会分析差异:组件删除、重排及匿名组件内部的属性删除会在 `replaceMerge` 能还原目标顺序时使用它;已按 ID 匹配的组件内部属性删除、`replaceMerge` 无法还原的身份顺序变化、新增或缩短的非组件数组、首次 ARIA 配置及其他高风险变更会退回 `notMerge: true`。
167
175
 
168
176
  - `update-options: object`
@@ -540,6 +548,8 @@ import { GGroup, GRect, GText } from "vue-echarts/graphic";
540
548
  > - 路径组件支持通过 `auto-batch` 启用 ZRender 的 Canvas 路径批处理。
541
549
  > - 仅使用 graphic 插槽时可以省略 `option` prop。
542
550
  > - `#graphic` 会覆盖 `option.graphic`。`manual-update` 模式下需调用 `chartRef.setOption(...)` 提交变更。
551
+ > - 包装组件和 Fragment 中的图形遵循实际渲染顺序。可合并的属性变化只更新变更节点,保留未变更元素及其正在运行的动画。删除字段、改变类型或树结构时,会重建 graphic 组件。
552
+ > - 仅 graphic 变化时,会在安全的情况下省略无关的源配置。显式指定 `notMerge` 或针对其他组件的 `replaceMerge` 时,仍会提交完整源配置。
543
553
 
544
554
  <details>
545
555
  <summary>用法示例</summary>
package/dist/graphic.d.ts CHANGED
@@ -234,7 +234,7 @@ declare const shapeProps: {
234
234
  };
235
235
  type GraphicShapeProps = ExtractPublicPropTypes<typeof shapeProps>;
236
236
  //#endregion
237
- //#region src/graphic/marker.d.ts
237
+ //#region src/graphic/component-type.d.ts
238
238
  type GraphicComponentType = "group" | "rect" | "circle" | "ellipse" | "text" | "line" | "polyline" | "polygon" | "image" | "sector" | "ring" | "arc" | "bezierCurve";
239
239
  //#endregion
240
240
  //#region src/graphic/component-factory.d.ts
@@ -406,8 +406,7 @@ declare const componentProps: {
406
406
  };
407
407
  type NestedShapePropKey = "shape" | "shapeTransition";
408
408
  type NestedStylePropKey = "style" | "styleTransition";
409
- type SpecializedPropKey = GraphicCommonStyleKey | GraphicDisplayablePropKey | GraphicPathPropKey | GraphicPathStyleKey | GraphicTextStyleKey | GraphicImageStyleKey | NestedShapePropKey | NestedStylePropKey | keyof typeof shapeProps;
410
- type SharedPropKey<T extends GraphicComponentType> = Exclude<Exclude<keyof typeof componentProps, SpecializedPropKey> | GraphicCommonPropKey, T extends "text" ? GraphicTextAttachmentPropKey : never>;
409
+ type SharedPropKey<T extends GraphicComponentType> = Exclude<GraphicCommonPropKey, T extends "text" ? GraphicTextAttachmentPropKey : never>;
411
410
  type StylePropKey<T extends GraphicComponentType> = T extends "group" ? never : NestedStylePropKey | (T extends "text" ? GraphicTextCommonStyleKey : GraphicCommonStyleKey) | (T extends keyof typeof SHAPE_KEYS_BY_TYPE ? GraphicPathStyleKey : T extends "text" ? GraphicTextStyleKey : T extends "image" ? GraphicImageStyleKey : never);
412
411
  type PathPropKey<T extends GraphicComponentType> = T extends keyof typeof SHAPE_KEYS_BY_TYPE ? NestedShapePropKey | GraphicPathPropKey | (typeof SHAPE_KEYS_BY_TYPE)[T][number] : never;
413
412
  type GroupPropKey<T extends GraphicComponentType> = T extends "group" ? GraphicGroupPropKey : never;
package/dist/graphic.js CHANGED
@@ -1,5 +1,5 @@
1
- import { d as parseOnEvent, f as warn$1, i as hasEventHandler, o as isBrowser, r as createEventInvoker, t as registerRuntime } from "./runtime-BSiQKM1W.js";
2
- import { Teleport, defineComponent, getCurrentInstance, h, inject, onBeforeMount, onBeforeUnmount, onMounted, onScopeDispose, onUpdated, provide, shallowRef, watch } from "vue";
1
+ import { a as hasEventHandler, c as isIgnorableWatchChange, f as parseOnEvent, i as createEventInvoker, n as registerRuntime, p as warn$1, s as isBrowser, t as planUpdate } from "./update-CWPvYl2M.js";
2
+ import { Teleport, defineComponent, getCurrentInstance, h, inject, onBeforeMount, onBeforeUnmount, onMounted, onScopeDispose, onUpdated, provide, queuePostFlushCb, shallowRef, watch } from "vue";
3
3
  import { use } from "echarts/core";
4
4
  import { GraphicComponent } from "echarts/components";
5
5
  const COMMON_PROP_KEYS = [
@@ -497,7 +497,6 @@ function buildOption(nodes, rootId) {
497
497
  return { graphic: { elements: [{
498
498
  type: "group",
499
499
  id: rootId,
500
- $action: "replace",
501
500
  children: childrenOf(null) ?? []
502
501
  }] } };
503
502
  }
@@ -508,7 +507,41 @@ function createCollector(onFlush) {
508
507
  const seenInPass = /* @__PURE__ */ new Map();
509
508
  const warnedKeys = /* @__PURE__ */ new Set();
510
509
  let order = 0;
510
+ let version = 0;
511
511
  let pending = false;
512
+ let root;
513
+ let observer;
514
+ const byElement = /* @__PURE__ */ new WeakMap();
515
+ let orderedIds = [];
516
+ function syncOrder() {
517
+ if (!root) return false;
518
+ const ids = [];
519
+ const walker = root.ownerDocument.createTreeWalker(root, 1);
520
+ for (let element = walker.nextNode(); element; element = walker.nextNode()) {
521
+ const node = byElement.get(element);
522
+ if (node && nodes.get(node.id) === node) {
523
+ node.order = ids.length;
524
+ ids.push(node.id);
525
+ }
526
+ }
527
+ const changed = ids.length !== orderedIds.length || ids.some((id, index) => id !== orderedIds[index]);
528
+ orderedIds = ids;
529
+ return changed;
530
+ }
531
+ function setRoot(value) {
532
+ observer?.disconnect();
533
+ root = value;
534
+ orderedIds = [];
535
+ if (root) {
536
+ observer = new MutationObserver(() => {
537
+ if (syncOrder()) requestFlush();
538
+ });
539
+ observer.observe(root, {
540
+ childList: true,
541
+ subtree: true
542
+ });
543
+ }
544
+ }
512
545
  function beginPass() {
513
546
  order = 0;
514
547
  seenInPass.clear();
@@ -525,14 +558,17 @@ function createCollector(onFlush) {
525
558
  order = Math.max(order, nextOrder + 1);
526
559
  const existing = nodes.get(node.id);
527
560
  const sameSource = existing?.sourceId === node.sourceId;
528
- const unchanged = sameSource && existing.type === node.type && existing.parentId === node.parentId && existing.props === node.props && existing.handlers === node.handlers && existing.order === nextOrder;
561
+ const unchanged = sameSource && existing.type === node.type && existing.parentId === node.parentId && existing.props === node.props && isIgnorableWatchChange(existing.handlers, node.handlers) && existing.element === node.element && (node.element !== void 0 || existing.order === nextOrder);
529
562
  seenInPass.set(node.id, node.sourceId);
530
563
  if (unchanged) return;
531
- nodes.set(node.id, {
564
+ const registered = {
532
565
  ...node,
533
566
  handlerCache: sameSource ? existing?.handlerCache : void 0,
534
- order: nextOrder
535
- });
567
+ order: nextOrder,
568
+ version: ++version
569
+ };
570
+ nodes.set(node.id, registered);
571
+ if (node.element) byElement.set(node.element, registered);
536
572
  requestFlush();
537
573
  }
538
574
  function unregister(id, sourceId) {
@@ -541,10 +577,12 @@ function createCollector(onFlush) {
541
577
  nodes.delete(id);
542
578
  requestFlush();
543
579
  }
544
- function requestFlush() {
580
+ function requestFlush(id, sourceId) {
581
+ const node = id === void 0 ? void 0 : nodes.get(id);
582
+ if (node && node.sourceId === sourceId) node.version = ++version;
545
583
  if (pending) return;
546
584
  pending = true;
547
- queueMicrotask(() => {
585
+ queuePostFlushCb(() => {
548
586
  if (!pending) return;
549
587
  pending = false;
550
588
  beginPass();
@@ -552,10 +590,17 @@ function createCollector(onFlush) {
552
590
  });
553
591
  }
554
592
  function getNodes() {
555
- return nodes.values();
593
+ syncOrder();
594
+ return root ? orderedIds.map((id) => nodes.get(id)) : nodes.values();
595
+ }
596
+ function dispose() {
597
+ cancelPendingFlush();
598
+ setRoot(void 0);
599
+ nodes.clear();
556
600
  }
557
601
  function cancelPendingFlush() {
558
602
  pending = false;
603
+ syncOrder();
559
604
  beginPass();
560
605
  }
561
606
  return {
@@ -565,87 +610,15 @@ function createCollector(onFlush) {
565
610
  warn,
566
611
  getNodes,
567
612
  requestFlush,
568
- cancelPendingFlush
613
+ cancelPendingFlush,
614
+ setRoot,
615
+ dispose
569
616
  };
570
617
  }
571
618
  //#endregion
572
619
  //#region src/graphic/context.ts
573
620
  const GRAPHIC_COLLECTOR_KEY = Symbol("vue-echarts:graphic-collector");
574
621
  const GRAPHIC_PARENT_ID_KEY = Symbol("vue-echarts:graphic-parent-id");
575
- const GRAPHIC_ORDER_KEY = Symbol("vue-echarts:graphic-order");
576
- //#endregion
577
- //#region src/graphic/identity.ts
578
- function toOrderKey(key) {
579
- return typeof key === "string" ? `key:${key}` : key ?? void 0;
580
- }
581
- function resolveIdentity(propsId, vnodeKey, uid) {
582
- if (propsId != null) {
583
- const id = String(propsId);
584
- return {
585
- id,
586
- orderKey: `id:${id}`
587
- };
588
- }
589
- const orderKey = toOrderKey(vnodeKey);
590
- if (orderKey !== void 0) return {
591
- id: typeof vnodeKey === "string" ? vnodeKey : `__ve_graphic_${uid}`,
592
- orderKey
593
- };
594
- return { id: `__ve_graphic_${uid}` };
595
- }
596
- function resolveOrderKey(propsId, vnodeKey) {
597
- if (propsId != null) return `id:${String(propsId)}`;
598
- return toOrderKey(vnodeKey) ?? null;
599
- }
600
- //#endregion
601
- //#region src/graphic/marker.ts
602
- const GRAPHIC_COMPONENT_MARKER = Symbol("vue-echarts:graphic-component");
603
- //#endregion
604
- //#region src/graphic/order.ts
605
- function isGraphic(vnode) {
606
- const type = vnode.type;
607
- if (!type || typeof type !== "object") return false;
608
- return typeof type[GRAPHIC_COMPONENT_MARKER] === "string";
609
- }
610
- function collect(value, orderMap, order) {
611
- if (Array.isArray(value)) {
612
- for (const child of value) order = collect(child, orderMap, order);
613
- return order;
614
- }
615
- if (!value || typeof value !== "object") return order;
616
- const vnode = value;
617
- const children = vnode.children;
618
- const graphic = isGraphic(vnode);
619
- const type = typeof vnode.type;
620
- const opaqueComponent = !Array.isArray(children) && (type === "object" || type === "function");
621
- const props = vnode.props;
622
- const identity = graphic || opaqueComponent ? resolveOrderKey(props?.id, vnode.key) : null;
623
- if (identity !== null) orderMap.set(identity, order);
624
- if (graphic) return order + 1;
625
- if (identity !== null) order++;
626
- if (Array.isArray(children)) for (const child of children) order = collect(child, orderMap, order);
627
- return order;
628
- }
629
- function isSameOrder(current, next) {
630
- if (current.size !== next.size) return false;
631
- for (const [key, order] of current) if (next.get(key) !== order) return false;
632
- return true;
633
- }
634
- function createOrderTracker() {
635
- let current = /* @__PURE__ */ new Map();
636
- const ref = shallowRef(current);
637
- return {
638
- ref,
639
- update(value) {
640
- const next = /* @__PURE__ */ new Map();
641
- collect(value, next, 0);
642
- if (!isSameOrder(current, next)) {
643
- current = next;
644
- ref.value = current;
645
- }
646
- }
647
- };
648
- }
649
622
  //#endregion
650
623
  //#region src/graphic/mount.ts
651
624
  const GraphicMount = defineComponent({
@@ -661,29 +634,30 @@ const GraphicMount = defineComponent({
661
634
  const detachedRoot = shallowRef();
662
635
  const contentReady = shallowRef(isBrowser() && !instance.vnode.el);
663
636
  const parentId = shallowRef(null);
664
- const order = createOrderTracker();
665
637
  onBeforeMount(() => {
666
638
  const host = instance.parent?.subTree.el;
667
639
  if (host) {
668
640
  const target = host.ownerDocument.createElement("div");
669
641
  host.appendChild(target);
670
642
  detachedRoot.value = target;
643
+ collector.setRoot(target);
671
644
  }
672
645
  });
673
646
  onMounted(() => {
674
647
  const ownerDocument = instance.vnode.el.ownerDocument;
675
648
  const target = detachedRoot.value;
676
649
  target?.remove();
677
- if (target?.ownerDocument !== ownerDocument) detachedRoot.value = ownerDocument.createElement("div");
650
+ if (target?.ownerDocument !== ownerDocument) {
651
+ detachedRoot.value = ownerDocument.createElement("div");
652
+ collector.setRoot(detachedRoot.value);
653
+ }
678
654
  contentReady.value = true;
679
655
  });
680
656
  provide(GRAPHIC_COLLECTOR_KEY, collector);
681
657
  provide(GRAPHIC_PARENT_ID_KEY, parentId);
682
- provide(GRAPHIC_ORDER_KEY, order.ref);
683
658
  return () => {
684
659
  beginPass();
685
660
  const content = slots.default();
686
- order.update(content);
687
661
  const target = detachedRoot.value;
688
662
  return h(Teleport, {
689
663
  to: target ?? "body",
@@ -701,14 +675,19 @@ function registerExtension() {
701
675
  const { slots, manualUpdate, requestUpdate } = ctx;
702
676
  const collector = createCollector(handleFlush);
703
677
  let hasGraphicSlot = Boolean(slots.graphic);
678
+ let signature;
679
+ let versions = /* @__PURE__ */ new Map();
704
680
  function handleFlush() {
705
681
  if (manualUpdate.value) {
682
+ const nodes = Array.from(collector.getNodes());
683
+ const changed = !slots.graphic || nodes.length !== versions.size || nodes.some((node) => versions.get(node.id) !== node.version);
684
+ if (!signature || !changed) return;
706
685
  collector.warn("`#graphic` slot updates are ignored when `manual-update` is `true`.", "manual-update-graphic");
707
686
  return;
708
687
  }
709
688
  requestUpdate();
710
689
  }
711
- onScopeDispose(collector.cancelPendingFlush);
690
+ onScopeDispose(collector.dispose);
712
691
  onUpdated(() => {
713
692
  const nextHasGraphicSlot = Boolean(slots.graphic);
714
693
  if (nextHasGraphicSlot === hasGraphicSlot) return;
@@ -717,15 +696,44 @@ function registerExtension() {
717
696
  handleFlush();
718
697
  });
719
698
  return {
720
- patchOption(option) {
699
+ cancelPendingFlush: collector.cancelPendingFlush,
700
+ prepare(option, reset) {
721
701
  hasGraphicSlot = Boolean(slots.graphic);
722
- if (!hasGraphicSlot) return option;
723
- if (option.graphic) collector.warn("`#graphic` slot is provided, so `option.graphic` is ignored. Remove one of them to avoid ambiguity.", "option-graphic-override");
724
- const nextOption = buildOption(collector.getNodes(), ROOT_ID);
725
702
  collector.cancelPendingFlush();
703
+ if (!hasGraphicSlot) return {
704
+ option,
705
+ replace: false,
706
+ commit: () => {
707
+ signature = void 0;
708
+ versions.clear();
709
+ }
710
+ };
711
+ if (option.graphic) collector.warn("`#graphic` slot is provided, so `option.graphic` is ignored. Remove one of them to avoid ambiguity.", "option-graphic-override");
712
+ const nodes = Array.from(collector.getNodes());
713
+ const nextOption = buildOption(nodes, ROOT_ID);
714
+ const planned = planUpdate(signature, nextOption);
715
+ const replace = reset || !signature || planned.plan.notMerge || Boolean(planned.plan.replaceMerge?.length);
716
+ const nextVersions = new Map(nodes.map((node) => [node.id, node.version]));
717
+ const elements = [];
718
+ function collectChanges(element, parentId) {
719
+ const { children, ...props } = element;
720
+ if (nextVersions.has(element.id) && nextVersions.get(element.id) !== versions.get(element.id)) elements.push({
721
+ ...props,
722
+ parentId
723
+ });
724
+ children?.forEach((child) => collectChanges(child, element.id));
725
+ }
726
+ if (!replace) nextOption.graphic.elements.forEach((element) => collectChanges(element));
727
+ const patched = { ...option };
728
+ delete patched.graphic;
729
+ if (replace || elements.length) patched.graphic = replace ? nextOption.graphic : { elements };
726
730
  return {
727
- ...option,
728
- graphic: nextOption.graphic
731
+ option: patched,
732
+ replace,
733
+ commit: () => {
734
+ signature = planned.signature;
735
+ versions = nextVersions;
736
+ }
729
737
  };
730
738
  },
731
739
  render() {
@@ -736,67 +744,91 @@ function registerExtension() {
736
744
  });
737
745
  }
738
746
  //#endregion
747
+ //#region src/graphic/identity.ts
748
+ function resolveIdentity(propsId, vnodeKey, uid) {
749
+ if (propsId != null) return String(propsId);
750
+ return typeof vnodeKey === "string" ? vnodeKey : `__ve_graphic_${uid}`;
751
+ }
752
+ //#endregion
739
753
  //#region src/graphic/component-factory.ts
740
- const runtimeProps = {
754
+ const componentProps = {
741
755
  ...commonProps,
742
- ...shapeProps,
743
- width: textPropOverrides.width
756
+ ...shapeProps
744
757
  };
758
+ function getRuntimeProps(type) {
759
+ const keys = COMMON_PROP_KEYS.filter((key) => type !== "text" || key !== "textContent" && key !== "textConfig");
760
+ if (type === "group") keys.push(...GROUP_PROP_KEYS);
761
+ else {
762
+ const shapeKeys = SHAPE_KEYS_BY_TYPE[type];
763
+ const styleKeys = shapeKeys ? PATH_STYLE_KEYS : STYLE_KEYS_BY_TYPE[type];
764
+ const commonStyleKeys = type === "text" ? TEXT_COMMON_STYLE_KEYS : COMMON_STYLE_KEYS;
765
+ keys.push("style", "styleTransition", ...DISPLAYABLE_PROP_KEYS, ...commonStyleKeys, ...styleKeys);
766
+ if (shapeKeys) keys.push("shape", "shapeTransition", ...PATH_PROP_KEYS, ...shapeKeys);
767
+ }
768
+ const definitions = type === "text" ? {
769
+ ...componentProps,
770
+ ...textPropOverrides
771
+ } : {
772
+ ...componentProps,
773
+ r: type === "rect" ? componentProps.r : Number
774
+ };
775
+ return Object.fromEntries(keys.map((key) => [key, definitions[key]]));
776
+ }
745
777
  /* @__NO_SIDE_EFFECTS__ */
746
778
  function createComponent(name, type) {
747
- const component = defineComponent({
779
+ return defineComponent({
748
780
  name,
749
781
  inheritAttrs: false,
750
- props: runtimeProps,
782
+ props: getRuntimeProps(type),
751
783
  emits: {},
752
784
  setup(props, { attrs, slots }) {
753
785
  const instance = getCurrentInstance();
754
786
  const collector = inject(GRAPHIC_COLLECTOR_KEY, null);
755
787
  const parentIdRef = inject(GRAPHIC_PARENT_ID_KEY, null);
756
- const parentOrderRef = inject(GRAPHIC_ORDER_KEY, null);
757
788
  if (!collector) {
758
789
  warn$1(`\`${name}\` must be used inside \`#graphic\` slot.`);
759
790
  return () => null;
760
791
  }
761
792
  const { register: registerNode, unregister, requestFlush } = collector;
762
793
  let currentId = null;
763
- watch([props, () => attrs], requestFlush, { deep: true });
794
+ let element;
795
+ watch([props, () => attrs], () => requestFlush(currentId ?? void 0, instance.uid), { deep: true });
764
796
  function register() {
765
- const { id, orderKey } = resolveIdentity(props.id, instance.vnode.key, instance.uid);
797
+ const id = resolveIdentity(props.id, instance.vnode.key, instance.uid);
766
798
  if (currentId !== null && currentId !== id) unregister(currentId, instance.uid);
767
799
  currentId = id;
768
- registerNode({
800
+ if (element) registerNode({
769
801
  id,
770
802
  type,
771
803
  parentId: parentIdRef.value,
772
- order: orderKey !== void 0 ? parentOrderRef?.value.get(orderKey) : void 0,
804
+ element,
773
805
  props,
774
- handlers: attrs,
806
+ handlers: { ...attrs },
775
807
  sourceId: instance.uid
776
808
  });
777
809
  return id;
778
810
  }
811
+ const setElement = (value) => {
812
+ if (value === element) return;
813
+ element = value ?? void 0;
814
+ if (element) register();
815
+ };
779
816
  onBeforeUnmount(() => unregister(currentId, instance.uid));
780
817
  if (type === "group") {
781
818
  const providedParent = shallowRef(null);
782
- const childOrder = createOrderTracker();
783
819
  provide(GRAPHIC_PARENT_ID_KEY, providedParent);
784
- provide(GRAPHIC_ORDER_KEY, childOrder.ref);
785
820
  return () => {
786
821
  providedParent.value = register();
787
- const content = slots.default?.() ?? null;
788
- childOrder.update(content);
789
- return content;
822
+ const content = slots.default?.() ?? [];
823
+ return h("div", { ref: setElement }, content);
790
824
  };
791
825
  }
792
826
  return () => {
793
827
  register();
794
- return null;
828
+ return h("div", { ref: setElement });
795
829
  };
796
830
  }
797
831
  });
798
- component[GRAPHIC_COMPONENT_MARKER] = type;
799
- return component;
800
832
  }
801
833
  //#endregion
802
834
  //#region src/graphic/components.ts