From 34e04a7b9877c136b98993f2f8e85c027107df4c Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Mon, 2 Jun 2025 21:33:25 +0800
Subject: [PATCH 01/27] feat: experimental component rendered tooltip

---
 demo/Demo.vue               |   2 +
 demo/examples/LineChart.vue | 106 ++++++++++++++++++++++++++++++++++++
 src/ECharts.ts              |  37 ++++++++++---
 3 files changed, 137 insertions(+), 8 deletions(-)
 create mode 100644 demo/examples/LineChart.vue

diff --git a/demo/Demo.vue b/demo/Demo.vue
index 9270f02..9e33e98 100644
--- a/demo/Demo.vue
+++ b/demo/Demo.vue
@@ -8,6 +8,7 @@ import { track } from "@vercel/analytics";
 
 import LogoChart from "./examples/LogoChart.vue";
 import BarChart from "./examples/BarChart.vue";
+import LineChart from "./examples/LineChart.vue";
 import PieChart from "./examples/PieChart.vue";
 import PolarChart from "./examples/PolarChart.vue";
 import ScatterChart from "./examples/ScatterChart.vue";
@@ -74,6 +75,7 @@ watch(codeOpen, (open) => {
     </p>
 
     <bar-chart />
+    <line-chart />
     <pie-chart />
     <polar-chart />
     <scatter-chart />
diff --git a/demo/examples/LineChart.vue b/demo/examples/LineChart.vue
new file mode 100644
index 0000000..c60ba6d
--- /dev/null
+++ b/demo/examples/LineChart.vue
@@ -0,0 +1,106 @@
+<script setup>
+import { use } from "echarts/core";
+import { LineChart } from "echarts/charts";
+import {
+  GridComponent,
+  DatasetComponent,
+  LegendComponent,
+  TooltipComponent,
+} from "echarts/components";
+import { shallowRef } from "vue";
+import VChart from "../../src/ECharts";
+import VExample from "./Example.vue";
+
+use([
+  DatasetComponent,
+  GridComponent,
+  LegendComponent,
+  LineChart,
+  TooltipComponent,
+]);
+
+const option = shallowRef({
+  legend: { top: 20 },
+  tooltip: {
+    trigger: "axis",
+    showContent: false,
+  },
+  dataset: {
+    source: [
+      ["product", "2012", "2013", "2014", "2015", "2016", "2017"],
+      ["Milk Tea", 56.5, 82.1, 88.7, 70.1, 53.4, 85.1],
+      ["Matcha Latte", 51.1, 51.4, 55.1, 53.3, 73.8, 68.7],
+      ["Cheese Cocoa", 40.1, 62.2, 69.5, 36.4, 45.2, 32.5],
+      ["Walnut Brownie", 25.2, 37.1, 41.2, 18, 33.9, 49.1],
+    ],
+  },
+  xAxis: { type: "category" },
+  yAxis: {},
+  series: [
+    {
+      type: "line",
+      smooth: true,
+      seriesLayoutBy: "row",
+      emphasis: { focus: "series" },
+    },
+    {
+      type: "line",
+      smooth: true,
+      seriesLayoutBy: "row",
+      emphasis: { focus: "series" },
+    },
+    {
+      type: "line",
+      smooth: true,
+      seriesLayoutBy: "row",
+      emphasis: { focus: "series" },
+    },
+    {
+      type: "line",
+      smooth: true,
+      seriesLayoutBy: "row",
+      emphasis: { focus: "series" },
+    },
+  ],
+});
+</script>
+
+<template>
+  <v-example
+    id="line"
+    title="Line chart"
+    desc="(with component rendered tooltip)"
+  >
+    <v-chart :option="option" autoresize>
+      <template #tooltip="{ params, show }">
+        <div
+          v-if="show"
+          :style="{
+            position: 'absolute',
+            top: '0px',
+            left: '0px',
+            transform: `translate3d(${params.x + 20}px, ${params.y + 20}px, 0px)`,
+            zIndex: 1000,
+            pointerEvents: 'none',
+            transition:
+              'opacity 0.2s cubic-bezier(0.23, 1, 0.32, 1), visibility 0.2s cubic-bezier(0.23, 1, 0.32, 1), transform 0.4s cubic-bezier(0.23, 1, 0.32, 1)',
+          }"
+        >
+          <div
+            style="
+              background: rgba(255, 255, 255, 0.2);
+              padding: 10px;
+              border-radius: 4px;
+              border: 1px solid rgb(102, 102, 102);
+              will-change: transform;
+              backdrop-filter: blur(8px);
+              box-shadow: rgba(0, 0, 0, 0.2) 1px 2px 10px;
+            "
+          >
+            {{ params }}
+          </div>
+        </div>
+      </template>
+    </v-chart>
+  </v-example>
+</template>
diff --git a/src/ECharts.ts b/src/ECharts.ts
index 66e2646..b87ac6e 100644
--- a/src/ECharts.ts
+++ b/src/ECharts.ts
@@ -24,7 +24,7 @@ import {
 import { isOn, omitOn, toValue } from "./utils";
 import { register, TAG_NAME } from "./wc";
 
-import type { PropType, InjectionKey } from "vue";
+import type { PropType, InjectionKey, SlotsType } from "vue";
 import type {
   EChartsType,
   SetOptionType,
@@ -65,7 +65,10 @@ export default defineComponent({
   },
   emits: {} as unknown as Emits,
   inheritAttrs: false,
-  setup(props, { attrs, expose }) {
+  slots: Object as SlotsType<{
+    tooltip: { params: any; show: boolean };
+  }>,
+  setup(props, { attrs, expose, slots }) {
     const root = shallowRef<EChartsElement>();
     const chart = shallowRef<EChartsType>();
     const manualOption = shallowRef<Option>();
@@ -93,6 +96,9 @@ export default defineComponent({
     const listeners: Map<{ event: string; once?: boolean; zr?: boolean }, any> =
       new Map();
 
+    const tooltipShow = shallowRef(false);
+    const tooltipParams = shallowRef<any>(null);
+
     // We are converting all `on<Event>` props and collect them into `listeners` so that
     // we can bind them to the chart instance later.
     // For `onNative:<event>` props, we just strip the `Native:` part and collect them into
@@ -178,6 +184,14 @@ export default defineComponent({
         }
       }
 
+      instance.on("showTip", (params) => {
+        tooltipShow.value = true;
+        tooltipParams.value = params;
+      });
+      instance.on("hideTip", () => {
+        tooltipShow.value = false;
+      });
+
       if (autoresize.value) {
         // Try to make chart fit to container in case container size
         // is changed synchronously or in already queued microtasks
@@ -302,11 +316,18 @@ export default defineComponent({
     // This type casting ensures TypeScript correctly types the exposed members
     // that will be available when using this component.
     return (() =>
-      h(TAG_NAME, {
-        ...nonEventAttrs.value,
-        ...nativeListeners,
-        ref: root,
-        class: ["echarts", ...(nonEventAttrs.value.class || [])],
-      })) as unknown as typeof exposed & PublicMethods;
+      h(
+        TAG_NAME,
+        {
+          ...nonEventAttrs.value,
+          ...nativeListeners,
+          ref: root,
+          class: ["echarts", ...(nonEventAttrs.value.class || [])],
+        },
+        slots.tooltip?.({
+          params: tooltipParams.value,
+          show: tooltipShow.value,
+        }),
+      )) as unknown as typeof exposed & PublicMethods;
   },
 });

From 79a1d7d299a4bfe82bac763bb453ea3bb041d9d0 Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Sat, 7 Jun 2025 16:59:28 +0800
Subject: [PATCH 02/27] revert slot in VChart

---
 demo/examples/LineChart.vue | 32 +-------------------------------
 src/ECharts.ts              | 37 ++++++++-----------------------------
 2 files changed, 9 insertions(+), 60 deletions(-)

diff --git a/demo/examples/LineChart.vue b/demo/examples/LineChart.vue
index c60ba6d..b43c2e3 100644
--- a/demo/examples/LineChart.vue
+++ b/demo/examples/LineChart.vue
@@ -71,36 +71,6 @@ const option = shallowRef({
     title="Line chart"
     desc="(with component rendered tooltip)"
   >
-    <v-chart :option="option" autoresize>
-      <template #tooltip="{ params, show }">
-        <div
-          v-if="show"
-          :style="{
-            position: 'absolute',
-            top: '0px',
-            left: '0px',
-            transform: `translate3d(${params.x + 20}px, ${params.y + 20}px, 0px)`,
-            zIndex: 1000,
-            pointerEvents: 'none',
-            transition:
-              'opacity 0.2s cubic-bezier(0.23, 1, 0.32, 1), visibility 0.2s cubic-bezier(0.23, 1, 0.32, 1), transform 0.4s cubic-bezier(0.23, 1, 0.32, 1)',
-          }"
-        >
-          <div
-            style="
-              background: rgba(255, 255, 255, 0.2);
-              padding: 10px;
-              border-radius: 4px;
-              border: 1px solid rgb(102, 102, 102);
-              will-change: transform;
-              backdrop-filter: blur(8px);
-              box-shadow: rgba(0, 0, 0, 0.2) 1px 2px 10px;
-            "
-          >
-            {{ params }}
-          </div>
-        </div>
-      </template>
-    </v-chart>
+    <v-chart :option="option" autoresize />
   </v-example>
 </template>
diff --git a/src/ECharts.ts b/src/ECharts.ts
index b87ac6e..66e2646 100644
--- a/src/ECharts.ts
+++ b/src/ECharts.ts
@@ -24,7 +24,7 @@ import {
 import { isOn, omitOn, toValue } from "./utils";
 import { register, TAG_NAME } from "./wc";
 
-import type { PropType, InjectionKey, SlotsType } from "vue";
+import type { PropType, InjectionKey } from "vue";
 import type {
   EChartsType,
   SetOptionType,
@@ -65,10 +65,7 @@ export default defineComponent({
   },
   emits: {} as unknown as Emits,
   inheritAttrs: false,
-  slots: Object as SlotsType<{
-    tooltip: { params: any; show: boolean };
-  }>,
-  setup(props, { attrs, expose, slots }) {
+  setup(props, { attrs, expose }) {
     const root = shallowRef<EChartsElement>();
     const chart = shallowRef<EChartsType>();
     const manualOption = shallowRef<Option>();
@@ -96,9 +93,6 @@ export default defineComponent({
     const listeners: Map<{ event: string; once?: boolean; zr?: boolean }, any> =
       new Map();
 
-    const tooltipShow = shallowRef(false);
-    const tooltipParams = shallowRef<any>(null);
-
     // We are converting all `on<Event>` props and collect them into `listeners` so that
     // we can bind them to the chart instance later.
     // For `onNative:<event>` props, we just strip the `Native:` part and collect them into
@@ -184,14 +178,6 @@ export default defineComponent({
         }
       }
 
-      instance.on("showTip", (params) => {
-        tooltipShow.value = true;
-        tooltipParams.value = params;
-      });
-      instance.on("hideTip", () => {
-        tooltipShow.value = false;
-      });
-
       if (autoresize.value) {
         // Try to make chart fit to container in case container size
         // is changed synchronously or in already queued microtasks
@@ -316,18 +302,11 @@ export default defineComponent({
     // This type casting ensures TypeScript correctly types the exposed members
     // that will be available when using this component.
     return (() =>
-      h(
-        TAG_NAME,
-        {
-          ...nonEventAttrs.value,
-          ...nativeListeners,
-          ref: root,
-          class: ["echarts", ...(nonEventAttrs.value.class || [])],
-        },
-        slots.tooltip?.({
-          params: tooltipParams.value,
-          show: tooltipShow.value,
-        }),
-      )) as unknown as typeof exposed & PublicMethods;
+      h(TAG_NAME, {
+        ...nonEventAttrs.value,
+        ...nativeListeners,
+        ref: root,
+        class: ["echarts", ...(nonEventAttrs.value.class || [])],
+      })) as unknown as typeof exposed & PublicMethods;
   },
 });

From 42c3569d8a9365b794aa3ec1739e4692cc2ffe55 Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Sat, 7 Jun 2025 17:49:36 +0800
Subject: [PATCH 03/27] feat: use tooltip composable

---
 demo/examples/LineChart.vue | 34 ++++++++++++++++++++++++++++--
 src/composables/tooltip.ts  | 41 +++++++++++++++++++++++++++++++++++++
 2 files changed, 73 insertions(+), 2 deletions(-)
 create mode 100644 src/composables/tooltip.ts

diff --git a/demo/examples/LineChart.vue b/demo/examples/LineChart.vue
index b43c2e3..4fdd294 100644
--- a/demo/examples/LineChart.vue
+++ b/demo/examples/LineChart.vue
@@ -1,4 +1,4 @@
-<script setup>
+<script setup lang="ts">
 import { use } from "echarts/core";
 import { LineChart } from "echarts/charts";
 import {
@@ -9,6 +9,7 @@ import {
 } from "echarts/components";
 import { shallowRef } from "vue";
 import VChart from "../../src/ECharts";
+import { createTooltipTemplate } from "../../src/composables/tooltip";
 import VExample from "./Example.vue";
 
 use([
@@ -19,11 +20,19 @@ use([
   TooltipComponent,
 ]);
 
+type MyParams = {
+  seriesName: string;
+  seriesIndex: number;
+  data: number[];
+  marker: string;
+}[];
+const { define: DefineTooltip, formatter } = createTooltipTemplate<MyParams>();
+
 const option = shallowRef({
   legend: { top: 20 },
   tooltip: {
     trigger: "axis",
-    showContent: false,
+    formatter,
   },
   dataset: {
     source: [
@@ -72,5 +81,26 @@ const option = shallowRef({
     desc="(with component rendered tooltip)"
   >
     <v-chart :option="option" autoresize />
+    <!-- TODO: use a Pie Chart as tooltip -->
+    <define-tooltip v-slot="params">
+      <b>Custom Tooltip</b>
+      <table>
+        <thead>
+          <tr>
+            <th>Product</th>
+            <th>Value</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="(item, i) in params" :key="i">
+            <td>
+              <span v-html="item.marker" />
+              {{ item.seriesName }}
+            </td>
+            <td>{{ item.data[i + 1] }}</td>
+          </tr>
+        </tbody>
+      </table>
+    </define-tooltip>
   </v-example>
 </template>
diff --git a/src/composables/tooltip.ts b/src/composables/tooltip.ts
new file mode 100644
index 0000000..e3a9e4e
--- /dev/null
+++ b/src/composables/tooltip.ts
@@ -0,0 +1,41 @@
+import {
+  h,
+  render,
+  defineComponent,
+  Slot,
+  Fragment,
+  type DefineComponent,
+} from "vue";
+
+export function createTooltipTemplate<Params extends object>() {
+  let slot: Slot | undefined;
+
+  const define = defineComponent({
+    setup(_, { slots }) {
+      return () => {
+        slot = slots.default;
+      };
+    },
+  }) as DefineComponent & {
+    new (): { $slots: { default(_: Params): any } };
+  };
+
+  const formatter = (params: Params): HTMLElement[] => {
+    if (!slot) {
+      throw new Error(
+        `[vue-echarts] Failed to find the definition of tooltip template`,
+      );
+    }
+
+    const container = document.createElement("div");
+
+    const vnode = h(Fragment, null, slot(params));
+    // console.log(params);
+
+    render(vnode, container);
+
+    return Array.from(container.children) as HTMLElement[];
+  };
+
+  return { define, formatter };
+}

From 978317eef9c50a3b84d734455ea14bbae09f786d Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Sat, 7 Jun 2025 21:16:57 +0800
Subject: [PATCH 04/27] feat: try createApp

---
 src/composables/tooltip.ts | 36 +++++++++++++++++++++++++-----------
 1 file changed, 25 insertions(+), 11 deletions(-)

diff --git a/src/composables/tooltip.ts b/src/composables/tooltip.ts
index e3a9e4e..63eaac1 100644
--- a/src/composables/tooltip.ts
+++ b/src/composables/tooltip.ts
@@ -1,14 +1,17 @@
 import {
-  h,
-  render,
   defineComponent,
   Slot,
-  Fragment,
+  shallowRef,
+  createApp,
+  onUnmounted,
+  type App,
   type DefineComponent,
 } from "vue";
 
 export function createTooltipTemplate<Params extends object>() {
   let slot: Slot | undefined;
+  let app: App<Element> | undefined;
+  const props = shallowRef<Params>();
 
   const define = defineComponent({
     setup(_, { slots }) {
@@ -20,22 +23,33 @@ export function createTooltipTemplate<Params extends object>() {
     new (): { $slots: { default(_: Params): any } };
   };
 
-  const formatter = (params: Params): HTMLElement[] => {
+  const formatter = (params: Params) => {
+    props.value = params;
+
     if (!slot) {
       throw new Error(
         `[vue-echarts] Failed to find the definition of tooltip template`,
       );
     }
 
-    const container = document.createElement("div");
-
-    const vnode = h(Fragment, null, slot(params));
-    // console.log(params);
-
-    render(vnode, container);
+    if (!app) {
+      app = createApp({
+        // root component is just a render function
+        render() {
+          // call the slot function with your props
+          // return slot!(props);
+          return slot!(props.value);
+        },
+      });
+      app.mount(document.createElement("div"));
+    }
 
-    return Array.from(container.children) as HTMLElement[];
+    return app._container!.innerHTML;
   };
 
+  onUnmounted(() => {
+    app?.unmount();
+  });
+
   return { define, formatter };
 }

From 89743cf67389799fea48b7f906acfa1274098955 Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Sun, 8 Jun 2025 22:26:10 +0800
Subject: [PATCH 05/27] feat: use pie chart as tooltip

---
 demo/examples/Example.vue   |  2 +-
 demo/examples/LineChart.vue | 65 ++++++++++++++++++++-----------------
 package.json                |  2 +-
 src/composables/tooltip.ts  |  5 +--
 4 files changed, 38 insertions(+), 36 deletions(-)

diff --git a/demo/examples/Example.vue b/demo/examples/Example.vue
index 88fa11e..3e36779 100644
--- a/demo/examples/Example.vue
+++ b/demo/examples/Example.vue
@@ -43,7 +43,7 @@ defineProps({
   width: fit-content;
   margin: 2em auto;
 
-  .echarts {
+  > .echarts {
     width: calc(60vw + 4em);
     height: 360px;
     max-width: 720px;
diff --git a/demo/examples/LineChart.vue b/demo/examples/LineChart.vue
index 4fdd294..9febc2b 100644
--- a/demo/examples/LineChart.vue
+++ b/demo/examples/LineChart.vue
@@ -1,6 +1,6 @@
-<script setup lang="ts">
+<script setup>
 import { use } from "echarts/core";
-import { LineChart } from "echarts/charts";
+import { LineChart, PieChart } from "echarts/charts";
 import {
   GridComponent,
   DatasetComponent,
@@ -18,21 +18,22 @@ use([
   LegendComponent,
   LineChart,
   TooltipComponent,
+  PieChart,
 ]);
 
-type MyParams = {
-  seriesName: string;
-  seriesIndex: number;
-  data: number[];
-  marker: string;
-}[];
-const { define: DefineTooltip, formatter } = createTooltipTemplate<MyParams>();
+const { define: DefineTooltip, formatter } = createTooltipTemplate();
 
 const option = shallowRef({
   legend: { top: 20 },
   tooltip: {
     trigger: "axis",
-    formatter,
+    formatter: (params) => {
+      const source = [params[0].dimensionNames, params[0].data];
+      const dataset = { source };
+      const option = { ...pieOption, dataset };
+      option.series[0].label.formatter = params[0].name;
+      return formatter(option);
+    },
   },
   dataset: {
     source: [
@@ -72,6 +73,23 @@ const option = shallowRef({
     },
   ],
 });
+
+const pieOption = {
+  animation: false,
+  series: [
+    {
+      type: "pie",
+      radius: ["60%", "100%"],
+      seriesLayoutBy: "row",
+      itemStyle: {
+        borderRadius: 5,
+        borderColor: "#fff",
+        borderWidth: 2,
+      },
+      label: { position: "center" },
+    },
+  ],
+};
 </script>
 
 <template>
@@ -81,26 +99,13 @@ const option = shallowRef({
     desc="(with component rendered tooltip)"
   >
     <v-chart :option="option" autoresize />
-    <!-- TODO: use a Pie Chart as tooltip -->
-    <define-tooltip v-slot="params">
-      <b>Custom Tooltip</b>
-      <table>
-        <thead>
-          <tr>
-            <th>Product</th>
-            <th>Value</th>
-          </tr>
-        </thead>
-        <tbody>
-          <tr v-for="(item, i) in params" :key="i">
-            <td>
-              <span v-html="item.marker" />
-              {{ item.seriesName }}
-            </td>
-            <td>{{ item.data[i + 1] }}</td>
-          </tr>
-        </tbody>
-      </table>
+    <define-tooltip v-slot="opt">
+      <v-chart
+        :style="{ width: '100px', height: '100px' }"
+        :option="opt"
+        :update-options="{ notMerge: false }"
+        autoresize
+      />
     </define-tooltip>
   </v-example>
 </template>
diff --git a/package.json b/package.json
index e11bc79..69142bd 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,7 @@
     "docs": "node ./scripts/docs.mjs",
     "prepublishOnly": "pnpm run typecheck && pnpm run dev:typecheck && pnpm run build && publint"
   },
-  "packageManager": "pnpm@10.11.0",
+  "packageManager": "pnpm@10.11.1",
   "type": "module",
   "main": "dist/index.js",
   "unpkg": "dist/index.min.js",
diff --git a/src/composables/tooltip.ts b/src/composables/tooltip.ts
index 63eaac1..bc3a804 100644
--- a/src/composables/tooltip.ts
+++ b/src/composables/tooltip.ts
@@ -34,17 +34,14 @@ export function createTooltipTemplate<Params extends object>() {
 
     if (!app) {
       app = createApp({
-        // root component is just a render function
         render() {
-          // call the slot function with your props
-          // return slot!(props);
           return slot!(props.value);
         },
       });
       app.mount(document.createElement("div"));
     }
 
-    return app._container!.innerHTML;
+    return app._container!;
   };
 
   onUnmounted(() => {

From 585279ef50bd3efb74132a348e62ccab72acd04c Mon Sep 17 00:00:00 2001
From: Yue JIN <yjin@nustarnuclear.com>
Date: Mon, 9 Jun 2025 15:06:48 +0800
Subject: [PATCH 06/27] feat: switch to createVNode

The limitation is that the tooltip detached from the current component tree, not provide/inject

will try teleport next
---
 demo/examples/LineChart.vue |  1 -
 src/composables/tooltip.ts  | 36 ++++++++++++++++++++++++++----------
 2 files changed, 26 insertions(+), 11 deletions(-)

diff --git a/demo/examples/LineChart.vue b/demo/examples/LineChart.vue
index 9febc2b..74c388f 100644
--- a/demo/examples/LineChart.vue
+++ b/demo/examples/LineChart.vue
@@ -75,7 +75,6 @@ const option = shallowRef({
 });
 
 const pieOption = {
-  animation: false,
   series: [
     {
       type: "pie",
diff --git a/src/composables/tooltip.ts b/src/composables/tooltip.ts
index bc3a804..3e438bd 100644
--- a/src/composables/tooltip.ts
+++ b/src/composables/tooltip.ts
@@ -2,16 +2,25 @@ import {
   defineComponent,
   Slot,
   shallowRef,
-  createApp,
+  createVNode,
+  render,
   onUnmounted,
-  type App,
+  getCurrentInstance,
   type DefineComponent,
 } from "vue";
 
 export function createTooltipTemplate<Params extends object>() {
   let slot: Slot | undefined;
-  let app: App<Element> | undefined;
+  let container: HTMLElement | undefined;
   const props = shallowRef<Params>();
+  const internal = getCurrentInstance();
+
+  if (!internal) {
+    throw new Error(
+      `[vue-echarts] createTooltipTemplate must be used in a setup function`,
+    );
+  }
+  const { appContext } = internal;
 
   const define = defineComponent({
     setup(_, { slots }) {
@@ -32,20 +41,27 @@ export function createTooltipTemplate<Params extends object>() {
       );
     }
 
-    if (!app) {
-      app = createApp({
-        render() {
-          return slot!(props.value);
+    if (!container) {
+      const component = defineComponent({
+        setup() {
+          return () => slot!(props.value);
         },
       });
-      app.mount(document.createElement("div"));
+      const vnode = createVNode(component);
+      vnode.appContext = appContext;
+
+      container = document.createElement("div");
+      render(vnode, container);
     }
 
-    return app._container!;
+    return container;
   };
 
   onUnmounted(() => {
-    app?.unmount();
+    if (container) {
+      render(null, container);
+      container.remove();
+    }
   });
 
   return { define, formatter };

From c6bab0d892c9a339e491ff70482946d884a852d7 Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Mon, 9 Jun 2025 23:00:31 +0800
Subject: [PATCH 07/27] feat: try component with teleport

---
 demo/examples/LineChart.vue | 14 ++++----
 package.json                |  2 +-
 src/ECharts.ts              |  1 +
 src/EChartsTooltip.ts       | 30 ++++++++++++++++
 src/composables/tooltip.ts  | 68 -------------------------------------
 5 files changed, 38 insertions(+), 77 deletions(-)
 create mode 100644 src/EChartsTooltip.ts
 delete mode 100644 src/composables/tooltip.ts

diff --git a/demo/examples/LineChart.vue b/demo/examples/LineChart.vue
index 74c388f..4738daf 100644
--- a/demo/examples/LineChart.vue
+++ b/demo/examples/LineChart.vue
@@ -7,9 +7,8 @@ import {
   LegendComponent,
   TooltipComponent,
 } from "echarts/components";
-import { shallowRef } from "vue";
-import VChart from "../../src/ECharts";
-import { createTooltipTemplate } from "../../src/composables/tooltip";
+import { useTemplateRef, shallowRef } from "vue";
+import VChart, { EChartsTooltip as VChartTooltip } from "../../src/ECharts";
 import VExample from "./Example.vue";
 
 use([
@@ -21,7 +20,7 @@ use([
   PieChart,
 ]);
 
-const { define: DefineTooltip, formatter } = createTooltipTemplate();
+const tooltipRef = useTemplateRef("tooltipRef");
 
 const option = shallowRef({
   legend: { top: 20 },
@@ -32,7 +31,7 @@ const option = shallowRef({
       const dataset = { source };
       const option = { ...pieOption, dataset };
       option.series[0].label.formatter = params[0].name;
-      return formatter(option);
+      return tooltipRef.value?.formatter(option);
     },
   },
   dataset: {
@@ -98,13 +97,12 @@ const pieOption = {
     desc="(with component rendered tooltip)"
   >
     <v-chart :option="option" autoresize />
-    <define-tooltip v-slot="opt">
+    <v-chart-tooltip ref="tooltipRef" v-slot="opt">
       <v-chart
         :style="{ width: '100px', height: '100px' }"
         :option="opt"
-        :update-options="{ notMerge: false }"
         autoresize
       />
-    </define-tooltip>
+    </v-chart-tooltip>
   </v-example>
 </template>
diff --git a/package.json b/package.json
index 69142bd..d3c2944 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,7 @@
     "docs": "node ./scripts/docs.mjs",
     "prepublishOnly": "pnpm run typecheck && pnpm run dev:typecheck && pnpm run build && publint"
   },
-  "packageManager": "pnpm@10.11.1",
+  "packageManager": "pnpm@10.12.1",
   "type": "module",
   "main": "dist/index.js",
   "unpkg": "dist/index.min.js",
diff --git a/src/ECharts.ts b/src/ECharts.ts
index 66e2646..97b0ecc 100644
--- a/src/ECharts.ts
+++ b/src/ECharts.ts
@@ -48,6 +48,7 @@ export const INIT_OPTIONS_KEY: InjectionKey<InitOptionsInjection> = Symbol();
 export const UPDATE_OPTIONS_KEY: InjectionKey<UpdateOptionsInjection> =
   Symbol();
 export { LOADING_OPTIONS_KEY } from "./composables";
+export { default as EChartsTooltip } from "./EChartsTooltip";
 
 export default defineComponent({
   name: "echarts",
diff --git a/src/EChartsTooltip.ts b/src/EChartsTooltip.ts
new file mode 100644
index 0000000..932e3ce
--- /dev/null
+++ b/src/EChartsTooltip.ts
@@ -0,0 +1,30 @@
+import { defineComponent, shallowRef, onBeforeUnmount, h, Teleport } from "vue";
+
+export default defineComponent({
+  name: "VChartTooltip",
+  methods: {} as { formatter: (params: any) => HTMLDivElement | undefined },
+  setup(_, { slots, expose }) {
+    const container = document?.createElement("div");
+    const initialized = shallowRef(false);
+    const state = shallowRef<any>();
+
+    function formatter(params: any) {
+      initialized.value = true;
+      state.value = params;
+      return container;
+    }
+
+    onBeforeUnmount(() => {
+      container?.remove();
+    });
+
+    expose({ formatter });
+
+    return () => {
+      const slotContent = initialized.value
+        ? slots.default?.(state.value)
+        : undefined;
+      return h(Teleport as any, { to: container }, slotContent);
+    };
+  },
+});
diff --git a/src/composables/tooltip.ts b/src/composables/tooltip.ts
deleted file mode 100644
index 3e438bd..0000000
--- a/src/composables/tooltip.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import {
-  defineComponent,
-  Slot,
-  shallowRef,
-  createVNode,
-  render,
-  onUnmounted,
-  getCurrentInstance,
-  type DefineComponent,
-} from "vue";
-
-export function createTooltipTemplate<Params extends object>() {
-  let slot: Slot | undefined;
-  let container: HTMLElement | undefined;
-  const props = shallowRef<Params>();
-  const internal = getCurrentInstance();
-
-  if (!internal) {
-    throw new Error(
-      `[vue-echarts] createTooltipTemplate must be used in a setup function`,
-    );
-  }
-  const { appContext } = internal;
-
-  const define = defineComponent({
-    setup(_, { slots }) {
-      return () => {
-        slot = slots.default;
-      };
-    },
-  }) as DefineComponent & {
-    new (): { $slots: { default(_: Params): any } };
-  };
-
-  const formatter = (params: Params) => {
-    props.value = params;
-
-    if (!slot) {
-      throw new Error(
-        `[vue-echarts] Failed to find the definition of tooltip template`,
-      );
-    }
-
-    if (!container) {
-      const component = defineComponent({
-        setup() {
-          return () => slot!(props.value);
-        },
-      });
-      const vnode = createVNode(component);
-      vnode.appContext = appContext;
-
-      container = document.createElement("div");
-      render(vnode, container);
-    }
-
-    return container;
-  };
-
-  onUnmounted(() => {
-    if (container) {
-      render(null, container);
-      container.remove();
-    }
-  });
-
-  return { define, formatter };
-}

From 2072c3b1841ebf46d6b70e1f9b014ea0b955b775 Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Sat, 21 Jun 2025 23:25:32 +0800
Subject: [PATCH 08/27] wip

---
 demo/data/line.js           |  49 +++++++++++++++++
 demo/examples/LineChart.vue | 107 +++++++++++-------------------------
 src/ECharts.ts              |  25 ++++++---
 src/EChartsTooltip.ts       |  30 ----------
 src/composables/tooltip.ts  |  71 ++++++++++++++++++++++++
 src/utils.ts                |   6 ++
 6 files changed, 174 insertions(+), 114 deletions(-)
 create mode 100644 demo/data/line.js
 delete mode 100644 src/EChartsTooltip.ts
 create mode 100644 src/composables/tooltip.ts

diff --git a/demo/data/line.js b/demo/data/line.js
new file mode 100644
index 0000000..631dec4
--- /dev/null
+++ b/demo/data/line.js
@@ -0,0 +1,49 @@
+export default function getData() {
+  return {
+    legend: { top: 20 },
+    tooltip: {
+      trigger: "axis",
+    },
+    dataset: {
+      source: [
+        ["product", "2012", "2013", "2014", "2015", "2016", "2017"],
+        ["Milk Tea", 56.5, 82.1, 88.7, 70.1, 53.4, 85.1],
+        ["Matcha Latte", 51.1, 51.4, 55.1, 53.3, 73.8, 68.7],
+        ["Cheese Cocoa", 40.1, 62.2, 69.5, 36.4, 45.2, 32.5],
+        ["Walnut Brownie", 25.2, 37.1, 41.2, 18, 33.9, 49.1],
+      ],
+    },
+    xAxis: {
+      type: "category",
+      triggerEvent: true,
+      tooltip: { show: true },
+    },
+    yAxis: {},
+    series: [
+      {
+        type: "line",
+        smooth: true,
+        seriesLayoutBy: "row",
+        emphasis: { focus: "series" },
+      },
+      {
+        type: "line",
+        smooth: true,
+        seriesLayoutBy: "row",
+        emphasis: { focus: "series" },
+      },
+      {
+        type: "line",
+        smooth: true,
+        seriesLayoutBy: "row",
+        emphasis: { focus: "series" },
+      },
+      {
+        type: "line",
+        smooth: true,
+        seriesLayoutBy: "row",
+        emphasis: { focus: "series" },
+      },
+    ],
+  };
+}
diff --git a/demo/examples/LineChart.vue b/demo/examples/LineChart.vue
index 4738daf..acf7242 100644
--- a/demo/examples/LineChart.vue
+++ b/demo/examples/LineChart.vue
@@ -7,9 +7,10 @@ import {
   LegendComponent,
   TooltipComponent,
 } from "echarts/components";
-import { useTemplateRef, shallowRef } from "vue";
-import VChart, { EChartsTooltip as VChartTooltip } from "../../src/ECharts";
+import { shallowRef } from "vue";
+import VChart from "../../src/ECharts";
 import VExample from "./Example.vue";
+import getData from "../data/line";
 
 use([
   DatasetComponent,
@@ -20,74 +21,27 @@ use([
   PieChart,
 ]);
 
-const tooltipRef = useTemplateRef("tooltipRef");
+const option = shallowRef(getData());
 
-const option = shallowRef({
-  legend: { top: 20 },
-  tooltip: {
-    trigger: "axis",
-    formatter: (params) => {
-      const source = [params[0].dimensionNames, params[0].data];
-      const dataset = { source };
-      const option = { ...pieOption, dataset };
-      option.series[0].label.formatter = params[0].name;
-      return tooltipRef.value?.formatter(option);
-    },
-  },
-  dataset: {
-    source: [
-      ["product", "2012", "2013", "2014", "2015", "2016", "2017"],
-      ["Milk Tea", 56.5, 82.1, 88.7, 70.1, 53.4, 85.1],
-      ["Matcha Latte", 51.1, 51.4, 55.1, 53.3, 73.8, 68.7],
-      ["Cheese Cocoa", 40.1, 62.2, 69.5, 36.4, 45.2, 32.5],
-      ["Walnut Brownie", 25.2, 37.1, 41.2, 18, 33.9, 49.1],
-    ],
-  },
-  xAxis: { type: "category" },
-  yAxis: {},
-  series: [
-    {
-      type: "line",
-      smooth: true,
-      seriesLayoutBy: "row",
-      emphasis: { focus: "series" },
-    },
-    {
-      type: "line",
-      smooth: true,
-      seriesLayoutBy: "row",
-      emphasis: { focus: "series" },
-    },
-    {
-      type: "line",
-      smooth: true,
-      seriesLayoutBy: "row",
-      emphasis: { focus: "series" },
-    },
-    {
-      type: "line",
-      smooth: true,
-      seriesLayoutBy: "row",
-      emphasis: { focus: "series" },
-    },
-  ],
-});
-
-const pieOption = {
-  series: [
-    {
-      type: "pie",
-      radius: ["60%", "100%"],
-      seriesLayoutBy: "row",
-      itemStyle: {
-        borderRadius: 5,
-        borderColor: "#fff",
-        borderWidth: 2,
+function getPieOption(params) {
+  const option = {
+    dataset: { source: [params[0].dimensionNames, params[0].data] },
+    series: [
+      {
+        type: "pie",
+        radius: ["60%", "100%"],
+        seriesLayoutBy: "row",
+        itemStyle: {
+          borderRadius: 5,
+          borderColor: "#fff",
+          borderWidth: 2,
+        },
+        label: { position: "center", formatter: params[0].name },
       },
-      label: { position: "center" },
-    },
-  ],
-};
+    ],
+  };
+  return option;
+}
 </script>
 
 <template>
@@ -96,13 +50,14 @@ const pieOption = {
     title="Line chart"
     desc="(with component rendered tooltip)"
   >
-    <v-chart :option="option" autoresize />
-    <v-chart-tooltip ref="tooltipRef" v-slot="opt">
-      <v-chart
-        :style="{ width: '100px', height: '100px' }"
-        :option="opt"
-        autoresize
-      />
-    </v-chart-tooltip>
+    <v-chart :option="option" autoresize>
+      <template #tooltip="{ params }">
+        <v-chart
+          :style="{ width: '100px', height: '100px' }"
+          :option="getPieOption(params)"
+          autoresize
+        />
+      </template>
+    </v-chart>
   </v-example>
 </template>
diff --git a/src/ECharts.ts b/src/ECharts.ts
index 97b0ecc..cb79ef6 100644
--- a/src/ECharts.ts
+++ b/src/ECharts.ts
@@ -40,6 +40,7 @@ import type {
 import type { EChartsElement } from "./wc";
 
 import "./style.css";
+import { useTooltip } from "./composables/tooltip";
 
 const wcRegistered = register();
 
@@ -48,7 +49,6 @@ export const INIT_OPTIONS_KEY: InjectionKey<InitOptionsInjection> = Symbol();
 export const UPDATE_OPTIONS_KEY: InjectionKey<UpdateOptionsInjection> =
   Symbol();
 export { LOADING_OPTIONS_KEY } from "./composables";
-export { default as EChartsTooltip } from "./EChartsTooltip";
 
 export default defineComponent({
   name: "echarts",
@@ -66,7 +66,7 @@ export default defineComponent({
   },
   emits: {} as unknown as Emits,
   inheritAttrs: false,
-  setup(props, { attrs, expose }) {
+  setup(props, { attrs, expose, slots }) {
     const root = shallowRef<EChartsElement>();
     const chart = shallowRef<EChartsType>();
     const manualOption = shallowRef<Option>();
@@ -175,6 +175,7 @@ export default defineComponent({
       function commit() {
         const opt = option || realOption.value;
         if (opt) {
+          mutateOption(opt);
           instance.setOption(opt, realUpdateOptions.value);
         }
       }
@@ -205,6 +206,7 @@ export default defineComponent({
       if (!chart.value) {
         init(option);
       } else {
+        mutateOption(option);
         chart.value.setOption(option, updateOptions || {});
       }
     };
@@ -235,6 +237,7 @@ export default defineComponent({
               if (!chart.value) {
                 init();
               } else {
+                mutateOption(option);
                 chart.value.setOption(option, {
                   // mutating `option` will lead to `notMerge: false` and
                   // replacing it with new reference will lead to `notMerge: true`
@@ -275,6 +278,8 @@ export default defineComponent({
 
     useAutoresize(chart, autoresize, root);
 
+    const { teleportedSlots, mutateOption } = useTooltip(slots);
+
     onMounted(() => {
       init();
     });
@@ -303,11 +308,15 @@ export default defineComponent({
     // This type casting ensures TypeScript correctly types the exposed members
     // that will be available when using this component.
     return (() =>
-      h(TAG_NAME, {
-        ...nonEventAttrs.value,
-        ...nativeListeners,
-        ref: root,
-        class: ["echarts", ...(nonEventAttrs.value.class || [])],
-      })) as unknown as typeof exposed & PublicMethods;
+      h(
+        TAG_NAME,
+        {
+          ...nonEventAttrs.value,
+          ...nativeListeners,
+          ref: root,
+          class: ["echarts", nonEventAttrs.value.class],
+        },
+        teleportedSlots(),
+      )) as unknown as typeof exposed & PublicMethods;
   },
 });
diff --git a/src/EChartsTooltip.ts b/src/EChartsTooltip.ts
deleted file mode 100644
index 932e3ce..0000000
--- a/src/EChartsTooltip.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { defineComponent, shallowRef, onBeforeUnmount, h, Teleport } from "vue";
-
-export default defineComponent({
-  name: "VChartTooltip",
-  methods: {} as { formatter: (params: any) => HTMLDivElement | undefined },
-  setup(_, { slots, expose }) {
-    const container = document?.createElement("div");
-    const initialized = shallowRef(false);
-    const state = shallowRef<any>();
-
-    function formatter(params: any) {
-      initialized.value = true;
-      state.value = params;
-      return container;
-    }
-
-    onBeforeUnmount(() => {
-      container?.remove();
-    });
-
-    expose({ formatter });
-
-    return () => {
-      const slotContent = initialized.value
-        ? slots.default?.(state.value)
-        : undefined;
-      return h(Teleport as any, { to: container }, slotContent);
-    };
-  },
-});
diff --git a/src/composables/tooltip.ts b/src/composables/tooltip.ts
new file mode 100644
index 0000000..1540c07
--- /dev/null
+++ b/src/composables/tooltip.ts
@@ -0,0 +1,71 @@
+import {
+  type ShallowRef,
+  Slots,
+  Teleport,
+  h,
+  onBeforeUnmount,
+  shallowRef,
+} from "vue";
+import { parseProperties } from "../utils";
+import { Option } from "src/types";
+
+export function useTooltip(slots: Slots) {
+  const tooltipSlots = Object.fromEntries(
+    Object.entries(slots).filter(
+      ([key]) => key === "tooltip" || key.startsWith("tooltip:"),
+    ),
+  );
+  const initialized: Record<string, ShallowRef<boolean>> = {};
+  const params: Record<string, ShallowRef<any>> = {};
+  const containers: Record<string, HTMLElement> = {};
+  const properties: Record<string, string[]> = {};
+
+  Object.keys(tooltipSlots).forEach((key) => {
+    initialized[key] = shallowRef(false);
+    params[key] = shallowRef(null);
+    properties[key] =
+      key === "tooltip" ? [] : parseProperties(key.replace("tooltip:", ""));
+    containers[key] = document?.createElement("div");
+  });
+
+  const teleportedSlots = () =>
+    Object.keys(tooltipSlots).map((key) => {
+      const slot = tooltipSlots[key];
+      const slotContent = initialized[key].value
+        ? slot?.({ params: params[key].value })
+        : undefined;
+      return h(Teleport as any, { to: containers[key] }, slotContent);
+    });
+
+  function mutateOption(option: Option) {
+    Object.keys(tooltipSlots).forEach((key) => {
+      let current: any = option;
+      if (key !== "tooltip") {
+        for (const prop of properties[key]) {
+          current = current[prop];
+          if (current == null) {
+            console.warn(`[vue-echarts] "option.${key}" is not defined`);
+            return;
+          }
+        }
+      }
+      current.tooltip ??= {};
+      current.tooltip.formatter = (p: any) => {
+        initialized[key].value = true;
+        params[key].value = p;
+        return containers[key];
+      };
+    });
+  }
+
+  onBeforeUnmount(() => {
+    Object.values(containers).forEach((container) => {
+      container?.remove();
+    });
+  });
+
+  return {
+    teleportedSlots,
+    mutateOption,
+  };
+}
diff --git a/src/utils.ts b/src/utils.ts
index 6dec3f5..bdb01cb 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -29,3 +29,9 @@ const isFunction = (val: unknown): val is Function => typeof val === "function";
 export function toValue<T>(source: MaybeRefOrGetter<T>): T {
   return isFunction(source) ? source() : unref(source);
 }
+
+export function parseProperties(path: string) {
+  // Convert bracket notation to dot notation and split the path
+  // "[2].series[0].data" -> ["2", "series", "0", "data"]
+  return path.replace(/\[(\w+)\]/g, ".$1").split(".");
+}

From 177e875a7d39c11094c77aff56a2d3abf09887da Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Sun, 22 Jun 2025 12:09:37 +0800
Subject: [PATCH 09/27] add xAxis example

---
 demo/examples/LineChart.vue |  3 +++
 package.json                |  2 +-
 pnpm-lock.yaml              | 31 +++++++++++++++++++------------
 src/ECharts.ts              |  5 ++++-
 src/composables/tooltip.ts  | 26 +++++++++++++-------------
 5 files changed, 40 insertions(+), 27 deletions(-)

diff --git a/demo/examples/LineChart.vue b/demo/examples/LineChart.vue
index acf7242..530c42f 100644
--- a/demo/examples/LineChart.vue
+++ b/demo/examples/LineChart.vue
@@ -58,6 +58,9 @@ function getPieOption(params) {
           autoresize
         />
       </template>
+      <template #tooltip:xAxis="{ params }">
+        Year: <b>{{ params.name }}</b>
+      </template>
     </v-chart>
   </v-example>
 </template>
diff --git a/package.json b/package.json
index d3c2944..a45bddd 100644
--- a/package.json
+++ b/package.json
@@ -50,7 +50,7 @@
     "@vue/tsconfig": "^0.7.0",
     "@vueuse/core": "^13.1.0",
     "comment-mark": "^2.0.1",
-    "echarts": "^5.5.1",
+    "echarts": "^5.6.0",
     "echarts-gl": "^2.0.9",
     "echarts-liquidfill": "^3.1.0",
     "esbuild-wasm": "^0.23.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 192fa13..64e3b65 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -39,14 +39,14 @@ importers:
         specifier: ^2.0.1
         version: 2.0.1
       echarts:
-        specifier: ^5.5.1
-        version: 5.5.1
+        specifier: ^5.6.0
+        version: 5.6.0
       echarts-gl:
         specifier: ^2.0.9
-        version: 2.0.9(echarts@5.5.1)
+        version: 2.0.9(echarts@5.6.0)
       echarts-liquidfill:
         specifier: ^3.1.0
-        version: 3.1.0(echarts@5.5.1)
+        version: 3.1.0(echarts@5.6.0)
       esbuild-wasm:
         specifier: ^0.23.0
         version: 0.23.0
@@ -820,8 +820,8 @@ packages:
     peerDependencies:
       echarts: ^5.0.1
 
-  echarts@5.5.1:
-    resolution: {integrity: sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==}
+  echarts@5.6.0:
+    resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==}
 
   emoji-regex@8.0.0:
     resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -1511,6 +1511,9 @@ packages:
   zrender@5.6.0:
     resolution: {integrity: sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==}
 
+  zrender@5.6.1:
+    resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==}
+
 snapshots:
 
   '@aashutoshrathi/word-wrap@1.2.6': {}
@@ -2134,20 +2137,20 @@ snapshots:
 
   eastasianwidth@0.2.0: {}
 
-  echarts-gl@2.0.9(echarts@5.5.1):
+  echarts-gl@2.0.9(echarts@5.6.0):
     dependencies:
       claygl: 1.3.0
-      echarts: 5.5.1
+      echarts: 5.6.0
       zrender: 5.6.0
 
-  echarts-liquidfill@3.1.0(echarts@5.5.1):
+  echarts-liquidfill@3.1.0(echarts@5.6.0):
     dependencies:
-      echarts: 5.5.1
+      echarts: 5.6.0
 
-  echarts@5.5.1:
+  echarts@5.6.0:
     dependencies:
       tslib: 2.3.0
-      zrender: 5.6.0
+      zrender: 5.6.1
 
   emoji-regex@8.0.0: {}
 
@@ -2819,3 +2822,7 @@ snapshots:
   zrender@5.6.0:
     dependencies:
       tslib: 2.3.0
+
+  zrender@5.6.1:
+    dependencies:
+      tslib: 2.3.0
diff --git a/src/ECharts.ts b/src/ECharts.ts
index cb79ef6..9dda1c9 100644
--- a/src/ECharts.ts
+++ b/src/ECharts.ts
@@ -24,7 +24,7 @@ import {
 import { isOn, omitOn, toValue } from "./utils";
 import { register, TAG_NAME } from "./wc";
 
-import type { PropType, InjectionKey } from "vue";
+import type { PropType, InjectionKey, SlotsType } from "vue";
 import type {
   EChartsType,
   SetOptionType,
@@ -65,6 +65,9 @@ export default defineComponent({
     ...loadingProps,
   },
   emits: {} as unknown as Emits,
+  slots: Object as SlotsType<
+    Record<"tooltip" | `tooltip:${string}`, { params: any }>
+  >,
   inheritAttrs: false,
   setup(props, { attrs, expose, slots }) {
     const root = shallowRef<EChartsElement>();
diff --git a/src/composables/tooltip.ts b/src/composables/tooltip.ts
index 1540c07..8c28c85 100644
--- a/src/composables/tooltip.ts
+++ b/src/composables/tooltip.ts
@@ -1,13 +1,13 @@
 import {
-  type ShallowRef,
-  Slots,
-  Teleport,
   h,
-  onBeforeUnmount,
+  Teleport,
+  onUnmounted,
   shallowRef,
+  type ShallowRef,
+  type Slots,
 } from "vue";
 import { parseProperties } from "../utils";
-import { Option } from "src/types";
+import type { Option } from "src/types";
 
 export function useTooltip(slots: Slots) {
   const tooltipSlots = Object.fromEntries(
@@ -40,13 +40,13 @@ export function useTooltip(slots: Slots) {
   function mutateOption(option: Option) {
     Object.keys(tooltipSlots).forEach((key) => {
       let current: any = option;
-      if (key !== "tooltip") {
-        for (const prop of properties[key]) {
-          current = current[prop];
-          if (current == null) {
-            console.warn(`[vue-echarts] "option.${key}" is not defined`);
-            return;
-          }
+      for (const prop of properties[key]) {
+        current = current[prop];
+        if (current == null) {
+          console.warn(
+            `[vue-echarts] "option.${key.replace("tooltip:", "")}" is not defined`,
+          );
+          return;
         }
       }
       current.tooltip ??= {};
@@ -58,7 +58,7 @@ export function useTooltip(slots: Slots) {
     });
   }
 
-  onBeforeUnmount(() => {
+  onUnmounted(() => {
     Object.values(containers).forEach((container) => {
       container?.remove();
     });

From 117df61ddee9f86c740c95337605fc684f805a9a Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Sun, 22 Jun 2025 19:46:37 +0800
Subject: [PATCH 10/27] refactor with shallowReactive

---
 src/composables/tooltip.ts | 64 ++++++++++++++++++--------------------
 1 file changed, 31 insertions(+), 33 deletions(-)

diff --git a/src/composables/tooltip.ts b/src/composables/tooltip.ts
index 8c28c85..3623f80 100644
--- a/src/composables/tooltip.ts
+++ b/src/composables/tooltip.ts
@@ -1,11 +1,4 @@
-import {
-  h,
-  Teleport,
-  onUnmounted,
-  shallowRef,
-  type ShallowRef,
-  type Slots,
-} from "vue";
+import { h, Teleport, onUnmounted, shallowReactive, type Slots } from "vue";
 import { parseProperties } from "../utils";
 import type { Option } from "src/types";
 
@@ -15,27 +8,34 @@ export function useTooltip(slots: Slots) {
       ([key]) => key === "tooltip" || key.startsWith("tooltip:"),
     ),
   );
-  const initialized: Record<string, ShallowRef<boolean>> = {};
-  const params: Record<string, ShallowRef<any>> = {};
-  const containers: Record<string, HTMLElement> = {};
-  const properties: Record<string, string[]> = {};
-
-  Object.keys(tooltipSlots).forEach((key) => {
-    initialized[key] = shallowRef(false);
-    params[key] = shallowRef(null);
-    properties[key] =
-      key === "tooltip" ? [] : parseProperties(key.replace("tooltip:", ""));
-    containers[key] = document?.createElement("div");
-  });
+  const detachedRoot = document?.createElement("div");
+  const containers = shallowReactive<Record<string, HTMLElement>>({});
+  const initialized = shallowReactive<Record<string, boolean>>({});
+  const params = shallowReactive<Record<string, any>>({});
+  const properties = Object.fromEntries(
+    Object.keys(tooltipSlots).map((key) => [
+      key,
+      key === "tooltip" ? [] : parseProperties(key.replace("tooltip:", "")),
+    ]),
+  );
 
-  const teleportedSlots = () =>
-    Object.keys(tooltipSlots).map((key) => {
-      const slot = tooltipSlots[key];
-      const slotContent = initialized[key].value
-        ? slot?.({ params: params[key].value })
-        : undefined;
-      return h(Teleport as any, { to: containers[key] }, slotContent);
-    });
+  const teleportedSlots = () => {
+    return h(
+      Teleport as any,
+      { to: detachedRoot },
+      Object.keys(tooltipSlots).map((key) => {
+        const slot = tooltipSlots[key];
+        const slotContent = initialized[key]
+          ? slot?.({ params: params[key] })
+          : undefined;
+        return h(
+          "div",
+          { ref: (el) => (containers[key] = el as HTMLElement) },
+          slotContent,
+        );
+      }),
+    );
+  };
 
   function mutateOption(option: Option) {
     Object.keys(tooltipSlots).forEach((key) => {
@@ -51,17 +51,15 @@ export function useTooltip(slots: Slots) {
       }
       current.tooltip ??= {};
       current.tooltip.formatter = (p: any) => {
-        initialized[key].value = true;
-        params[key].value = p;
+        initialized[key] = true;
+        params[key] = p;
         return containers[key];
       };
     });
   }
 
   onUnmounted(() => {
-    Object.values(containers).forEach((container) => {
-      container?.remove();
-    });
+    detachedRoot?.remove();
   });
 
   return {

From 2dccfdb80ab99cef575c4a8d7b7c808ac27582ac Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Mon, 23 Jun 2025 23:27:43 +0800
Subject: [PATCH 11/27] Support dynamic slot

---
 demo/data/line.js           |   7 ++-
 demo/examples/LineChart.vue |  15 ++++-
 src/ECharts.ts              |  47 +++++++++++++--
 src/composables/tooltip.ts  | 117 ++++++++++++++++++++++--------------
 4 files changed, 132 insertions(+), 54 deletions(-)

diff --git a/demo/data/line.js b/demo/data/line.js
index 631dec4..64371c5 100644
--- a/demo/data/line.js
+++ b/demo/data/line.js
@@ -16,9 +16,12 @@ export default function getData() {
     xAxis: {
       type: "category",
       triggerEvent: true,
-      tooltip: { show: true },
+      tooltip: { show: true, formatter: "" },
+    },
+    yAxis: {
+      triggerEvent: true,
+      tooltip: { show: true, formatter: "" },
     },
-    yAxis: {},
     series: [
       {
         type: "line",
diff --git a/demo/examples/LineChart.vue b/demo/examples/LineChart.vue
index 530c42f..b1404a5 100644
--- a/demo/examples/LineChart.vue
+++ b/demo/examples/LineChart.vue
@@ -22,6 +22,7 @@ use([
 ]);
 
 const option = shallowRef(getData());
+const axis = shallowRef("xAxis");
 
 function getPieOption(params) {
   const option = {
@@ -58,9 +59,19 @@ function getPieOption(params) {
           autoresize
         />
       </template>
-      <template #tooltip:xAxis="{ params }">
-        Year: <b>{{ params.name }}</b>
+      <template #[`tooltip:${axis}`]="{ params }">
+        {{ axis === "xAxis" ? "Year" : "Value" }}:
+        <b>{{ params.name }}</b>
       </template>
     </v-chart>
+    <template #extra>
+      <p class="actions">
+        Custom tooltip on
+        <select v-model="axis">
+          <option value="xAxis">X Axis</option>
+          <option value="yAxis">Y Axis</option>
+        </select>
+      </p>
+    </template>
   </v-example>
 </template>
diff --git a/src/ECharts.ts b/src/ECharts.ts
index 9dda1c9..743f7a9 100644
--- a/src/ECharts.ts
+++ b/src/ECharts.ts
@@ -178,8 +178,16 @@ export default defineComponent({
       function commit() {
         const opt = option || realOption.value;
         if (opt) {
-          mutateOption(opt);
-          instance.setOption(opt, realUpdateOptions.value);
+          const tooltipOption = createTooltipOption();
+          instance.setOption(opt, {
+            ...realUpdateOptions.value,
+            lazyUpdate: true,
+          });
+          instance.setOption(tooltipOption, {
+            ...realUpdateOptions.value,
+            notMerge: false,
+            silent: true,
+          });
         }
       }
 
@@ -209,8 +217,16 @@ export default defineComponent({
       if (!chart.value) {
         init(option);
       } else {
-        mutateOption(option);
-        chart.value.setOption(option, updateOptions || {});
+        const tooltipOption = createTooltipOption();
+        chart.value.setOption(option, {
+          ...(updateOptions || {}),
+          lazyUpdate: true,
+        });
+        chart.value.setOption(tooltipOption, {
+          ...(updateOptions || {}),
+          notMerge: false,
+          silent: true,
+        });
       }
     };
 
@@ -240,12 +256,18 @@ export default defineComponent({
               if (!chart.value) {
                 init();
               } else {
-                mutateOption(option);
+                const tooltipOption = createTooltipOption();
                 chart.value.setOption(option, {
                   // mutating `option` will lead to `notMerge: false` and
                   // replacing it with new reference will lead to `notMerge: true`
                   notMerge: option !== oldOption,
                   ...realUpdateOptions.value,
+                  lazyUpdate: true,
+                });
+                chart.value.setOption(tooltipOption, {
+                  ...realUpdateOptions.value,
+                  notMerge: false,
+                  silent: true,
                 });
               }
             },
@@ -281,7 +303,20 @@ export default defineComponent({
 
     useAutoresize(chart, autoresize, root);
 
-    const { teleportedSlots, mutateOption } = useTooltip(slots);
+    const { teleportedSlots, createTooltipOption } = useTooltip(slots, () => {
+      if (!manualUpdate.value && props.option && chart.value) {
+        const tooltipOption = createTooltipOption();
+        chart.value.setOption(props.option, {
+          ...realUpdateOptions.value,
+          lazyUpdate: true,
+        });
+        chart.value.setOption(tooltipOption, {
+          ...realUpdateOptions.value,
+          notMerge: false,
+          silent: true,
+        });
+      }
+    });
 
     onMounted(() => {
       init();
diff --git a/src/composables/tooltip.ts b/src/composables/tooltip.ts
index 3623f80..5724cb2 100644
--- a/src/composables/tooltip.ts
+++ b/src/composables/tooltip.ts
@@ -1,69 +1,98 @@
-import { h, Teleport, onUnmounted, shallowReactive, type Slots } from "vue";
+import {
+  h,
+  Teleport,
+  onUpdated,
+  onUnmounted,
+  shallowReactive,
+  type Slots,
+} from "vue";
 import { parseProperties } from "../utils";
 import type { Option } from "src/types";
 
-export function useTooltip(slots: Slots) {
-  const tooltipSlots = Object.fromEntries(
-    Object.entries(slots).filter(
-      ([key]) => key === "tooltip" || key.startsWith("tooltip:"),
-    ),
-  );
+function isTooltipSlot(key: string) {
+  return key === "tooltip" || key.startsWith("tooltip:");
+}
+
+export function useTooltip(slots: Slots, onSlotsChange?: () => void) {
   const detachedRoot = document?.createElement("div");
   const containers = shallowReactive<Record<string, HTMLElement>>({});
   const initialized = shallowReactive<Record<string, boolean>>({});
   const params = shallowReactive<Record<string, any>>({});
-  const properties = Object.fromEntries(
-    Object.keys(tooltipSlots).map((key) => [
-      key,
-      key === "tooltip" ? [] : parseProperties(key.replace("tooltip:", "")),
-    ]),
-  );
 
+  // Teleport the tooltip slots to a detached root
   const teleportedSlots = () => {
     return h(
       Teleport as any,
       { to: detachedRoot },
-      Object.keys(tooltipSlots).map((key) => {
-        const slot = tooltipSlots[key];
-        const slotContent = initialized[key]
-          ? slot?.({ params: params[key] })
-          : undefined;
-        return h(
-          "div",
-          { ref: (el) => (containers[key] = el as HTMLElement) },
-          slotContent,
-        );
-      }),
+      Object.entries(slots)
+        .filter(([key]) => isTooltipSlot(key))
+        .map(([key, slot]) => {
+          const slotContent = initialized[key]
+            ? slot?.({ params: params[key] })
+            : undefined;
+          return h(
+            "div",
+            { ref: (el) => (containers[key] = el as HTMLElement), name: key },
+            slotContent,
+          );
+        }),
     );
   };
 
-  function mutateOption(option: Option) {
-    Object.keys(tooltipSlots).forEach((key) => {
-      let current: any = option;
-      for (const prop of properties[key]) {
-        current = current[prop];
-        if (current == null) {
-          console.warn(
-            `[vue-echarts] "option.${key.replace("tooltip:", "")}" is not defined`,
-          );
-          return;
-        }
-      }
-      current.tooltip ??= {};
-      current.tooltip.formatter = (p: any) => {
-        initialized[key] = true;
-        params[key] = p;
-        return containers[key];
-      };
-    });
+  // Create a minimal option with component rendered tooltip formatter
+  function createTooltipOption(): Option {
+    const option: any = {};
+
+    Object.keys(slots)
+      .filter((key) => isTooltipSlot(key))
+      .forEach((key) => {
+        const path =
+          key === "tooltip" ? [] : parseProperties(key.replace("tooltip:", ""));
+        let current = option;
+        path.forEach((k) => {
+          if (!(k in current)) {
+            // If the key is a number, create an array, otherwise create an object
+            current[k] = isNaN(Number(k)) ? {} : [];
+          }
+          current = current[k];
+        });
+        current.tooltip = {
+          formatter(p: any) {
+            initialized[key] = true;
+            params[key] = p;
+            return containers[key];
+          },
+        };
+      });
+
+    return option;
   }
 
+  // `slots` is not reactive and cannot be watched
+  // so we need to watch it manually
+  let slotNames = Object.keys(slots).filter((key) => isTooltipSlot(key));
+  onUpdated(() => {
+    const newSlotNames = Object.keys(slots).filter((key) => isTooltipSlot(key));
+    if (newSlotNames.join() !== slotNames.join()) {
+      // Clean up params and initialized for removed slots
+      slotNames.forEach((key) => {
+        if (!(key in slots)) {
+          delete params[key];
+          delete initialized[key];
+          delete containers[key];
+        }
+      });
+      slotNames = newSlotNames;
+      onSlotsChange?.();
+    }
+  });
+
   onUnmounted(() => {
     detachedRoot?.remove();
   });
 
   return {
     teleportedSlots,
-    mutateOption,
+    createTooltipOption,
   };
 }

From 322fda4711b28cf897bc31c0bf102c10c82d73aa Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Tue, 24 Jun 2025 11:17:19 +0800
Subject: [PATCH 12/27] fix: fill empty elements with object in array

---
 src/composables/tooltip.ts | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/src/composables/tooltip.ts b/src/composables/tooltip.ts
index 5724cb2..ed513be 100644
--- a/src/composables/tooltip.ts
+++ b/src/composables/tooltip.ts
@@ -49,10 +49,18 @@ export function useTooltip(slots: Slots, onSlotsChange?: () => void) {
         const path =
           key === "tooltip" ? [] : parseProperties(key.replace("tooltip:", ""));
         let current = option;
-        path.forEach((k) => {
+        path.forEach((k, index, arr) => {
           if (!(k in current)) {
-            // If the key is a number, create an array, otherwise create an object
-            current[k] = isNaN(Number(k)) ? {} : [];
+            // If the next key is a number, create an array, otherwise create an object
+            current[k] = isNaN(Number(arr[index + 1])) ? {} : [];
+            // fill the non-existent elements with empty objects
+            if (Array.isArray(current) && !isNaN(Number(k))) {
+              for (let i = 0; i < Number(k); i++) {
+                if (current[i] == undefined) {
+                  current[i] = {};
+                }
+              }
+            }
           }
           current = current[k];
         });

From 49ad09b8096a86a73eea88755ab269d1641017ed Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Tue, 1 Jul 2025 11:32:14 +0800
Subject: [PATCH 13/27] shallow copy option along the path

---
 demo/examples/LineChart.vue |  2 +-
 src/ECharts.ts              | 47 +++++--------------------
 src/composables/tooltip.ts  | 69 ++++++++++++++++++++-----------------
 src/utils.ts                |  7 ++--
 4 files changed, 49 insertions(+), 76 deletions(-)

diff --git a/demo/examples/LineChart.vue b/demo/examples/LineChart.vue
index b1404a5..039e060 100644
--- a/demo/examples/LineChart.vue
+++ b/demo/examples/LineChart.vue
@@ -59,7 +59,7 @@ function getPieOption(params) {
           autoresize
         />
       </template>
-      <template #[`tooltip:${axis}`]="{ params }">
+      <template #[`tooltip-${axis}`]="{ params }">
         {{ axis === "xAxis" ? "Year" : "Value" }}:
         <b>{{ params.name }}</b>
       </template>
diff --git a/src/ECharts.ts b/src/ECharts.ts
index 743f7a9..a33b504 100644
--- a/src/ECharts.ts
+++ b/src/ECharts.ts
@@ -178,16 +178,7 @@ export default defineComponent({
       function commit() {
         const opt = option || realOption.value;
         if (opt) {
-          const tooltipOption = createTooltipOption();
-          instance.setOption(opt, {
-            ...realUpdateOptions.value,
-            lazyUpdate: true,
-          });
-          instance.setOption(tooltipOption, {
-            ...realUpdateOptions.value,
-            notMerge: false,
-            silent: true,
-          });
+          instance.setOption(patchOption(opt), realUpdateOptions.value);
         }
       }
 
@@ -217,16 +208,7 @@ export default defineComponent({
       if (!chart.value) {
         init(option);
       } else {
-        const tooltipOption = createTooltipOption();
-        chart.value.setOption(option, {
-          ...(updateOptions || {}),
-          lazyUpdate: true,
-        });
-        chart.value.setOption(tooltipOption, {
-          ...(updateOptions || {}),
-          notMerge: false,
-          silent: true,
-        });
+        chart.value.setOption(patchOption(option), updateOptions);
       }
     };
 
@@ -256,18 +238,11 @@ export default defineComponent({
               if (!chart.value) {
                 init();
               } else {
-                const tooltipOption = createTooltipOption();
-                chart.value.setOption(option, {
+                chart.value.setOption(patchOption(option), {
                   // mutating `option` will lead to `notMerge: false` and
                   // replacing it with new reference will lead to `notMerge: true`
                   notMerge: option !== oldOption,
                   ...realUpdateOptions.value,
-                  lazyUpdate: true,
-                });
-                chart.value.setOption(tooltipOption, {
-                  ...realUpdateOptions.value,
-                  notMerge: false,
-                  silent: true,
                 });
               }
             },
@@ -303,18 +278,12 @@ export default defineComponent({
 
     useAutoresize(chart, autoresize, root);
 
-    const { teleportedSlots, createTooltipOption } = useTooltip(slots, () => {
+    const { teleportedSlots, patchOption } = useTooltip(slots, () => {
       if (!manualUpdate.value && props.option && chart.value) {
-        const tooltipOption = createTooltipOption();
-        chart.value.setOption(props.option, {
-          ...realUpdateOptions.value,
-          lazyUpdate: true,
-        });
-        chart.value.setOption(tooltipOption, {
-          ...realUpdateOptions.value,
-          notMerge: false,
-          silent: true,
-        });
+        chart.value.setOption(
+          patchOption(props.option),
+          realUpdateOptions.value,
+        );
       }
     });
 
diff --git a/src/composables/tooltip.ts b/src/composables/tooltip.ts
index ed513be..9c2e5b8 100644
--- a/src/composables/tooltip.ts
+++ b/src/composables/tooltip.ts
@@ -6,11 +6,11 @@ import {
   shallowReactive,
   type Slots,
 } from "vue";
-import { parseProperties } from "../utils";
-import type { Option } from "src/types";
+import type { Option } from "../types";
+import { isValidArrayIndex } from "../utils";
 
 function isTooltipSlot(key: string) {
-  return key === "tooltip" || key.startsWith("tooltip:");
+  return key === "tooltip" || key.startsWith("tooltip-");
 }
 
 export function useTooltip(slots: Slots, onSlotsChange?: () => void) {
@@ -39,41 +39,46 @@ export function useTooltip(slots: Slots, onSlotsChange?: () => void) {
     );
   };
 
-  // Create a minimal option with component rendered tooltip formatter
-  function createTooltipOption(): Option {
-    const option: any = {};
+  // Shallow clone the option along the path and patch the tooltip formatter
+  function patchOption(src: Option): Option {
+    const root = { ...src };
 
     Object.keys(slots)
       .filter((key) => isTooltipSlot(key))
       .forEach((key) => {
-        const path =
-          key === "tooltip" ? [] : parseProperties(key.replace("tooltip:", ""));
-        let current = option;
-        path.forEach((k, index, arr) => {
-          if (!(k in current)) {
-            // If the next key is a number, create an array, otherwise create an object
-            current[k] = isNaN(Number(arr[index + 1])) ? {} : [];
-            // fill the non-existent elements with empty objects
-            if (Array.isArray(current) && !isNaN(Number(k))) {
-              for (let i = 0; i < Number(k); i++) {
-                if (current[i] == undefined) {
-                  current[i] = {};
-                }
-              }
-            }
+        const path = key.split("-");
+        path.push(path.shift()!);
+        let cur: any = root;
+
+        for (let i = 0; i < path.length; i++) {
+          const seg = path[i];
+          const next = cur[seg];
+
+          if (i < path.length - 1) {
+            // shallow-clone the link; create empty shell if missing
+            cur[seg] = next
+              ? Array.isArray(next)
+                ? next.slice()
+                : { ...next }
+              : isValidArrayIndex(seg)
+                ? []
+                : {};
+            cur = cur[seg];
+          } else {
+            // final node = tooltip
+            cur[seg] = {
+              ...(next || {}),
+              formatter(p: any) {
+                initialized[key] = true;
+                params[key] = p;
+                return containers[key];
+              },
+            };
           }
-          current = current[k];
-        });
-        current.tooltip = {
-          formatter(p: any) {
-            initialized[key] = true;
-            params[key] = p;
-            return containers[key];
-          },
-        };
+        }
       });
 
-    return option;
+    return root;
   }
 
   // `slots` is not reactive and cannot be watched
@@ -101,6 +106,6 @@ export function useTooltip(slots: Slots, onSlotsChange?: () => void) {
 
   return {
     teleportedSlots,
-    createTooltipOption,
+    patchOption,
   };
 }
diff --git a/src/utils.ts b/src/utils.ts
index bdb01cb..546ecca 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -30,8 +30,7 @@ export function toValue<T>(source: MaybeRefOrGetter<T>): T {
   return isFunction(source) ? source() : unref(source);
 }
 
-export function parseProperties(path: string) {
-  // Convert bracket notation to dot notation and split the path
-  // "[2].series[0].data" -> ["2", "series", "0", "data"]
-  return path.replace(/\[(\w+)\]/g, ".$1").split(".");
+export function isValidArrayIndex(key: string): boolean {
+  const num = Number(key);
+  return Number.isInteger(num) && num >= 0 && num < Math.pow(2, 32) - 1;
 }

From 9c2f4313c8106d10188a0bdfa41c39875233799f Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Tue, 1 Jul 2025 15:11:37 +0800
Subject: [PATCH 14/27] ssr friendly

---
 src/composables/tooltip.ts | 49 +++++++++++++++++++++++---------------
 1 file changed, 30 insertions(+), 19 deletions(-)

diff --git a/src/composables/tooltip.ts b/src/composables/tooltip.ts
index 9c2e5b8..8b179fb 100644
--- a/src/composables/tooltip.ts
+++ b/src/composables/tooltip.ts
@@ -3,6 +3,8 @@ import {
   Teleport,
   onUpdated,
   onUnmounted,
+  onMounted,
+  shallowRef,
   shallowReactive,
   type Slots,
 } from "vue";
@@ -13,30 +15,35 @@ function isTooltipSlot(key: string) {
   return key === "tooltip" || key.startsWith("tooltip-");
 }
 
-export function useTooltip(slots: Slots, onSlotsChange?: () => void) {
-  const detachedRoot = document?.createElement("div");
+export function useTooltip(slots: Slots, onSlotsChange: () => void) {
+  const detachedRoot =
+    typeof window !== "undefined" ? document.createElement("div") : undefined;
   const containers = shallowReactive<Record<string, HTMLElement>>({});
   const initialized = shallowReactive<Record<string, boolean>>({});
   const params = shallowReactive<Record<string, any>>({});
+  const isMounted = shallowRef(false);
 
   // Teleport the tooltip slots to a detached root
   const teleportedSlots = () => {
-    return h(
-      Teleport as any,
-      { to: detachedRoot },
-      Object.entries(slots)
-        .filter(([key]) => isTooltipSlot(key))
-        .map(([key, slot]) => {
-          const slotContent = initialized[key]
-            ? slot?.({ params: params[key] })
-            : undefined;
-          return h(
-            "div",
-            { ref: (el) => (containers[key] = el as HTMLElement), name: key },
-            slotContent,
-          );
-        }),
-    );
+    // Make tooltip slots client-side only to avoid SSR hydration mismatch
+    return isMounted.value
+      ? h(
+          Teleport as any,
+          { to: detachedRoot, defer: true },
+          Object.entries(slots)
+            .filter(([key]) => isTooltipSlot(key))
+            .map(([key, slot]) => {
+              const slotContent = initialized[key]
+                ? slot?.({ params: params[key] })
+                : undefined;
+              return h(
+                "div",
+                { ref: (el) => (containers[key] = el as HTMLElement) },
+                slotContent,
+              );
+            }),
+        )
+      : undefined;
   };
 
   // Shallow clone the option along the path and patch the tooltip formatter
@@ -96,10 +103,14 @@ export function useTooltip(slots: Slots, onSlotsChange?: () => void) {
         }
       });
       slotNames = newSlotNames;
-      onSlotsChange?.();
+      onSlotsChange();
     }
   });
 
+  onMounted(() => {
+    isMounted.value = true;
+  });
+
   onUnmounted(() => {
     detachedRoot?.remove();
   });

From 0acb7b2d12c13373492a27e8cc5f38accf4a8ef8 Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Tue, 1 Jul 2025 16:28:06 +0800
Subject: [PATCH 15/27] vibe docs

---
 README.md         | 62 ++++++++++++++++++++++++++++++++++++++++++++---
 README.zh-Hans.md | 56 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 115 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 9ad752b..ff5db43 100644
--- a/README.md
+++ b/README.md
@@ -155,7 +155,8 @@ See more examples [here](https://github.com/ecomfe/vue-echarts/tree/main/demo).
 
   ECharts' universal interface. Modifying this prop will trigger ECharts' `setOption` method. Read more [here →](https://echarts.apache.org/en/option.html)
 
-  > 💡 When `update-options` is not specified, `notMerge: false` will be specified by default when the `setOption` method is called if the `option` object is modified directly and the reference remains unchanged; otherwise, if a new reference is bound to `option`, ` notMerge: true` will be specified.
+  > [!TIP]
+  > When `update-options` is not specified, `notMerge: false` will be specified by default when the `setOption` method is called if the `option` object is modified directly and the reference remains unchanged; otherwise, if a new reference is bound to `option`, ` notMerge: true` will be specified.
 
 - `update-options: object`
 
@@ -195,8 +196,7 @@ You can bind events with Vue's `v-on` directive.
 </template>
 ```
 
-> **Note**
->
+> [!NOTE]
 > Only the `.once` event modifier is supported as other modifiers are tightly coupled with the DOM event system.
 
 Vue-ECharts support the following events:
@@ -331,6 +331,62 @@ export default {
 - `clear` [→](https://echarts.apache.org/en/api.html#echartsInstance.clear)
 - `dispose` [→](https://echarts.apache.org/en/api.html#echartsInstance.dispose)
 
+### Slots
+
+Vue-ECharts allows you to define ECharts option's `tooltip.formatter` callbacks via Vue slots instead of defining them in your `option` object. This simplifies custom tooltip rendering using familiar Vue templating.
+
+**Slot Naming Convention**
+
+- Slot names begin with `tooltip`, followed by hyphen-separated path segments to the target formatter.
+- Each segment corresponds to an `option` property name or an array index (for arrays, use the numeric index).
+- The constructed slot name maps directly to the nested `formatter` it overrides.
+
+**Example mappings**:
+
+- `tooltip` → `option.tooltip.formatter`
+- `tooltip-baseOption` → `option.baseOption.tooltip.formatter`
+- `tooltip-xAxis-1` → `option.xAxis[1].tooltip.formatter`
+- `tooltip-series-2-data-4` → `option.series[2].data[4].tooltip.formatter`
+
+<details>
+<summary>Usage</summary>
+
+```vue
+<template>
+  <v-chart :option="chartOptions">
+    <!-- Override global tooltip.formatter -->
+    <template #tooltip="{ params }">
+      <table>
+        <tr>
+          <th>Series</th>
+          <th>Value</th>
+        </tr>
+        <tr v-for="(s, i) in params" :key="i">
+          <td>{{ s.seriesName }}</td>
+          <td>{{ s.data }}</td>
+        </tr>
+      </table>
+    </template>
+
+    <!-- Override tooltip on xAxis -->
+    <template #tooltip-xAxis="{ params }">
+      <div>X-Axis : {{ params.value }}</div>
+    </template>
+  </v-chart>
+</template>
+```
+
+[Example→](https://vue-echarts.dev/#line)
+
+</details>
+
+#### Slot Props
+
+- `params`: The first argument passed to the original [`tooltip.formatter`](https://echarts.apache.org/en/option.html#tooltip.formatter) callback.
+
+> [!NOTE]
+> Slots take precedence over any `tooltip.formatter` defined in `props.option`. If a matching slot is present, the slot's content will render instead of using `option`'s formatter.
+
 ### Static Methods
 
 Static methods can be accessed from [`echarts` itself](https://echarts.apache.org/en/api.html#echarts).
diff --git a/README.zh-Hans.md b/README.zh-Hans.md
index 7bb8289..bd0a1ce 100644
--- a/README.zh-Hans.md
+++ b/README.zh-Hans.md
@@ -331,6 +331,62 @@ export default {
 - `clear` [→](https://echarts.apache.org/zh/api.html#echartsInstance.clear)
 - `dispose` [→](https://echarts.apache.org/zh/api.html#echartsInstance.dispose)
 
+### 插槽(Slots)
+
+Vue-ECharts 允许你通过 Vue 插槽来定义 ECharts 配置中的 `tooltip.formatter` 回调,而无需在 `option` 对象中定义它们。这简化了自定义提示框的渲染,让你可以用熟悉的 Vue 模板语法来编写。
+
+**插槽命名约定**
+
+- 插槽名称以 `tooltip` 开头,后面跟随用连字符分隔的路径片段,用于定位要覆盖的 `formatter`。
+- 每个片段对应 `option` 对象的属性名或数组索引(数组索引使用数字形式)。
+- 拼接后的插槽名称直接映射到要覆盖的嵌套 `formatter`。
+
+**示例映射**:
+
+- `tooltip` → `option.tooltip.formatter`
+- `tooltip-baseOption` → `option.baseOption.tooltip.formatter`
+- `tooltip-xAxis-1` → `option.xAxis[1].tooltip.formatter`
+- `tooltip-series-2-data-4` → `option.series[2].data[4].tooltip.formatter`
+
+<details>
+<summary>用法示例</summary>
+
+```vue
+<template>
+  <v-chart :option="chartOptions">
+    <!-- 覆盖全局 tooltip.formatter -->
+    <template #tooltip="{ params }">
+      <table>
+        <tr>
+          <th>系列名称</th>
+          <th>数值</th>
+        </tr>
+        <tr v-for="(item, idx) in params" :key="idx">
+          <td>{{ item.seriesName }}</td>
+          <td>{{ item.data }}</td>
+        </tr>
+      </table>
+    </template>
+
+    <!-- 覆盖x轴 tooltip.formatter -->
+    <template #tooltip-xAxis="{ params }">
+      <div>X 轴: {{ params.value }}</div>
+    </template>
+  </v-chart>
+</template>
+```
+
+[Example→](https://vue-echarts.dev/#line)
+
+</details>
+
+#### 插槽Props
+
+- `params`:[`tooltip.formatter`](https://echarts.apache.org/zh/option.html#tooltip.formatter) 回调的第一个参数。
+
+> [!NOTE]
+> 插槽内容会优先于 `props.option` 中对应的 `tooltip.formatter` 渲染,如果两者同时存在。
+
 ### 静态方法
 
 静态方法请直接通过 [`echarts` 本身](https://echarts.apache.org/zh/api.html#echarts)进行调用。

From 3556afe81728046fca7a69d900300e567d7240ef Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Tue, 1 Jul 2025 16:43:43 +0800
Subject: [PATCH 16/27] typo

---
 README.zh-Hans.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/README.zh-Hans.md b/README.zh-Hans.md
index bd0a1ce..18ef12c 100644
--- a/README.zh-Hans.md
+++ b/README.zh-Hans.md
@@ -155,7 +155,8 @@ app.component('v-chart', VueECharts)
 
   ECharts 的万能接口。修改这个 prop 会触发 ECharts 实例的 `setOption` 方法。查看[详情 →](https://echarts.apache.org/zh/option.html)
 
-  > 💡 在没有指定 `update-options` 时,如果直接修改 `option` 对象而引用保持不变,`setOption` 方法调用时将默认指定 `notMerge: false`;否则,如果为 `option` 绑定一个新的引用,将指定 `notMerge: true`。
+  > [!TIP]
+  > 在没有指定 `update-options` 时,如果直接修改 `option` 对象而引用保持不变,`setOption` 方法调用时将默认指定 `notMerge: false`;否则,如果为 `option` 绑定一个新的引用,将指定 `notMerge: true`。
 
 - `update-options: object`
 
@@ -195,8 +196,7 @@ app.component('v-chart', VueECharts)
 </template>
 ```
 
-> **Note**
->
+> [!NOTE]
 > 仅支持 `.once` 修饰符,因为其它修饰符都与 DOM 事件机制强耦合。
 
 Vue-ECharts 支持如下事件:

From 6aa8d6daa3d055e8af74c721f71df489518a09e5 Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Thu, 3 Jul 2025 23:46:55 +0800
Subject: [PATCH 17/27] update according to the review

---
 README.md                   | 4 ++--
 README.zh-Hans.md           | 2 +-
 demo/data/line.js           | 4 ++++
 demo/examples/LineChart.vue | 9 ++++++++-
 src/composables/tooltip.ts  | 2 +-
 src/utils.ts                | 7 ++++++-
 6 files changed, 22 insertions(+), 6 deletions(-)

diff --git a/README.md b/README.md
index ff5db43..236548e 100644
--- a/README.md
+++ b/README.md
@@ -156,7 +156,7 @@ See more examples [here](https://github.com/ecomfe/vue-echarts/tree/main/demo).
   ECharts' universal interface. Modifying this prop will trigger ECharts' `setOption` method. Read more [here →](https://echarts.apache.org/en/option.html)
 
   > [!TIP]
-  > When `update-options` is not specified, `notMerge: false` will be specified by default when the `setOption` method is called if the `option` object is modified directly and the reference remains unchanged; otherwise, if a new reference is bound to `option`, ` notMerge: true` will be specified.
+  > When `update-options` is not specified, `notMerge: false` will be specified by default when the `setOption` method is called if the `option` object is modified directly and the reference remains unchanged; otherwise, if a new reference is bound to `option`, `notMerge: true` will be specified.
 
 - `update-options: object`
 
@@ -376,7 +376,7 @@ Vue-ECharts allows you to define ECharts option's `tooltip.formatter` callbacks
 </template>
 ```
 
-[Example→](https://vue-echarts.dev/#line)
+[Example →](https://vue-echarts.dev/#line)
 
 </details>
 
diff --git a/README.zh-Hans.md b/README.zh-Hans.md
index 18ef12c..4b948c6 100644
--- a/README.zh-Hans.md
+++ b/README.zh-Hans.md
@@ -376,7 +376,7 @@ Vue-ECharts 允许你通过 Vue 插槽来定义 ECharts 配置中的 `tooltip.fo
 </template>
 ```
 
-[Example→](https://vue-echarts.dev/#line)
+[示例 →](https://vue-echarts.dev/#line)
 
 </details>
 
diff --git a/demo/data/line.js b/demo/data/line.js
index 64371c5..35f1601 100644
--- a/demo/data/line.js
+++ b/demo/data/line.js
@@ -1,5 +1,9 @@
 export default function getData() {
   return {
+    textStyle: {
+      fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
+      fontWeight: 300,
+    },
     legend: { top: 20 },
     tooltip: {
       trigger: "axis",
diff --git a/demo/examples/LineChart.vue b/demo/examples/LineChart.vue
index 039e060..659c893 100644
--- a/demo/examples/LineChart.vue
+++ b/demo/examples/LineChart.vue
@@ -37,7 +37,14 @@ function getPieOption(params) {
           borderColor: "#fff",
           borderWidth: 2,
         },
-        label: { position: "center", formatter: params[0].name },
+        label: {
+          position: "center",
+          formatter: params[0].name,
+          textStyle: {
+            fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
+            fontWeight: 300,
+          },
+        },
       },
     ],
   };
diff --git a/src/composables/tooltip.ts b/src/composables/tooltip.ts
index 8b179fb..7c36f03 100644
--- a/src/composables/tooltip.ts
+++ b/src/composables/tooltip.ts
@@ -65,7 +65,7 @@ export function useTooltip(slots: Slots, onSlotsChange: () => void) {
             // shallow-clone the link; create empty shell if missing
             cur[seg] = next
               ? Array.isArray(next)
-                ? next.slice()
+                ? [...next]
                 : { ...next }
               : isValidArrayIndex(seg)
                 ? []
diff --git a/src/utils.ts b/src/utils.ts
index 546ecca..4d3a383 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -32,5 +32,10 @@ export function toValue<T>(source: MaybeRefOrGetter<T>): T {
 
 export function isValidArrayIndex(key: string): boolean {
   const num = Number(key);
-  return Number.isInteger(num) && num >= 0 && num < Math.pow(2, 32) - 1;
+  return (
+    Number.isInteger(num) &&
+    num >= 0 &&
+    num < Math.pow(2, 32) - 1 &&
+    String(num) === key
+  );
 }

From 887102d1f55f9b024459151bbd62c1da17208860 Mon Sep 17 00:00:00 2001
From: Yue JIN <40021217+kingyue737@users.noreply.github.com>
Date: Fri, 4 Jul 2025 17:02:23 +0800
Subject: [PATCH 18/27] add dataView slot

* chore: fix warnings and errors in demo (#839)

* chore: suppress warning in demo

* chore: prevent multiple intializations of esbuild-wasm in demo HMR

* feat: dynamically update the theme (#841)

Co-authored-by: GU Yiling <justice360@gmail.com>

* feat: add dataView slot

* vibe docs

---------

Co-authored-by: GU Yiling <justice360@gmail.com>
---
 README.md                               | 60 ++++++++++++-------
 README.zh-Hans.md                       | 64 +++++++++++++-------
 demo/CodeGen.vue                        |  6 +-
 demo/data/logo.js                       |  6 +-
 demo/data/radar.ts                      |  1 +
 demo/examples/LineChart.vue             | 28 ++++++++-
 package.json                            |  6 +-
 pnpm-lock.yaml                          | 30 +++++-----
 scripts/docs.mjs                        |  2 +-
 src/ECharts.ts                          | 19 ++++--
 src/composables/index.ts                |  1 +
 src/composables/{tooltip.ts => slot.ts} | 80 ++++++++++++++-----------
 12 files changed, 196 insertions(+), 107 deletions(-)
 rename src/composables/{tooltip.ts => slot.ts} (54%)

diff --git a/README.md b/README.md
index 236548e..a0d293d 100644
--- a/README.md
+++ b/README.md
@@ -120,7 +120,7 @@ Drop `<script>` inside your HTML file and access the component via `window.VueEC
 
 ```html
 <script src="https://cdn.jsdelivr.net/npm/vue@3.5.13"></script>
-<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1"></script>
+<script src="https://cdn.jsdelivr.net/npm/echarts@6.0.0-beta.1"></script>
 <script src="https://cdn.jsdelivr.net/npm/vue-echarts@7.0.3"></script>
 ```
 
@@ -331,15 +331,21 @@ export default {
 - `clear` [→](https://echarts.apache.org/en/api.html#echartsInstance.clear)
 - `dispose` [→](https://echarts.apache.org/en/api.html#echartsInstance.dispose)
 
+> [!NOTE]
+> The following ECharts instance methods aren't exposed because their functionality is already provided by component [props](#props):
+>
+> - [`showLoading`](https://echarts.apache.org/en/api.html#echartsInstance.showLoading) / [`hideLoading`](https://echarts.apache.org/en/api.html#echartsInstance.hideLoading): use the `loading` and `loading-options` props instead.
+> - `setTheme`: use the `theme` prop instead.
+
 ### Slots
 
-Vue-ECharts allows you to define ECharts option's `tooltip.formatter` callbacks via Vue slots instead of defining them in your `option` object. This simplifies custom tooltip rendering using familiar Vue templating.
+Vue-ECharts allows you to define ECharts option's [`tooltip.formatter`](https://echarts.apache.org/en/option.html#tooltip.formatter) and [`toolbox.feature.dataView.optionToContent`](https://echarts.apache.org/en/option.html#toolbox.feature.dataView.optionToContent) callbacks via Vue slots instead of defining them in your `option` object. This simplifies custom HTMLElement rendering using familiar Vue templating.
 
 **Slot Naming Convention**
 
-- Slot names begin with `tooltip`, followed by hyphen-separated path segments to the target formatter.
+- Slot names begin with `tooltip`/`dataView`, followed by hyphen-separated path segments to the target.
 - Each segment corresponds to an `option` property name or an array index (for arrays, use the numeric index).
-- The constructed slot name maps directly to the nested `formatter` it overrides.
+- The constructed slot name maps directly to the nested callback it overrides.
 
 **Example mappings**:
 
@@ -347,6 +353,8 @@ Vue-ECharts allows you to define ECharts option's `tooltip.formatter` callbacks
 - `tooltip-baseOption` → `option.baseOption.tooltip.formatter`
 - `tooltip-xAxis-1` → `option.xAxis[1].tooltip.formatter`
 - `tooltip-series-2-data-4` → `option.series[2].data[4].tooltip.formatter`
+- `dataView` → `option.toolbox.feature.dataView.optionToContent`
+- `dataView-media[1]-option` → `option.media[1].option.toolbox.feature.dataView.optionToContent`
 
 <details>
 <summary>Usage</summary>
@@ -354,24 +362,38 @@ Vue-ECharts allows you to define ECharts option's `tooltip.formatter` callbacks
 ```vue
 <template>
   <v-chart :option="chartOptions">
-    <!-- Override global tooltip.formatter -->
+    <!-- Global `tooltip.formatter` -->
     <template #tooltip="{ params }">
-      <table>
-        <tr>
-          <th>Series</th>
-          <th>Value</th>
-        </tr>
-        <tr v-for="(s, i) in params" :key="i">
-          <td>{{ s.seriesName }}</td>
-          <td>{{ s.data }}</td>
-        </tr>
-      </table>
+      <div v-for="(param, i) in params" :key="i">
+        <span v-html="param.marker" />
+        <span>{{ param.seriesName }}</span>
+        <span>{{ param.value[0] }}</span>
+      </div>
     </template>
 
-    <!-- Override tooltip on xAxis -->
+    <!-- Tooltip on xAxis -->
     <template #tooltip-xAxis="{ params }">
       <div>X-Axis : {{ params.value }}</div>
     </template>
+
+    <!-- Data View Content -->
+    <template #dataView="{ option }">
+      <table>
+        <thead>
+          <tr>
+            <th v-for="(t, i) in option.dataset[0].source[0]" :key="i">
+              {{ t }}
+            </th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="(row, i) in option.dataset[0].source.slice(1)" :key="i">
+            <th>{{ row[0] }}</th>
+            <td v-for="(v, i) in row.slice(1)" :key="i">{{ v }}</td>
+          </tr>
+        </tbody>
+      </table>
+    </template>
   </v-chart>
 </template>
 ```
@@ -380,12 +402,8 @@ Vue-ECharts allows you to define ECharts option's `tooltip.formatter` callbacks
 
 </details>
 
-#### Slot Props
-
-- `params`: The first argument passed to the original [`tooltip.formatter`](https://echarts.apache.org/en/option.html#tooltip.formatter) callback.
-
 > [!NOTE]
-> Slots take precedence over any `tooltip.formatter` defined in `props.option`. If a matching slot is present, the slot's content will render instead of using `option`'s formatter.
+> Slots take precedence over the corresponding callback defined in `props.option`.
 
 ### Static Methods
 
diff --git a/README.zh-Hans.md b/README.zh-Hans.md
index 4b948c6..e4b9456 100644
--- a/README.zh-Hans.md
+++ b/README.zh-Hans.md
@@ -120,7 +120,7 @@ import "echarts";
 
 ```html
 <script src="https://cdn.jsdelivr.net/npm/vue@3.5.13"></script>
-<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1"></script>
+<script src="https://cdn.jsdelivr.net/npm/echarts@6.0.0-beta.1"></script>
 <script src="https://cdn.jsdelivr.net/npm/vue-echarts@7.0.3"></script>
 ```
 
@@ -331,15 +331,21 @@ export default {
 - `clear` [→](https://echarts.apache.org/zh/api.html#echartsInstance.clear)
 - `dispose` [→](https://echarts.apache.org/zh/api.html#echartsInstance.dispose)
 
+> [!NOTE]
+> 如下 ECharts 实例方法没有被暴露,因为它们的功能已经通过组件 [props](#props) 提供了:
+>
+> - [`showLoading`](https://echarts.apache.org/zh/api.html#echartsInstance.showLoading) / [`hideLoading`](https://echarts.apache.org/zh/api.html#echartsInstance.hideLoading):请使用 `loading` 和 `loading-options` prop。
+> - `setTheme`:请使用 `theme` prop。
+
 ### 插槽(Slots)
 
-Vue-ECharts 允许你通过 Vue 插槽来定义 ECharts 配置中的 `tooltip.formatter` 回调,而无需在 `option` 对象中定义它们。这简化了自定义提示框的渲染,让你可以用熟悉的 Vue 模板语法来编写。
+Vue-ECharts 允许你通过 Vue 插槽来定义 ECharts 配置中的 [`tooltip.formatter`](https://echarts.apache.org/zh/option.html#tooltip.formatter) 和 [`toolbox.feature.dataView.optionToContent`](https://echarts.apache.org/zh/option.html#toolbox.feature.dataView.optionToContent) 回调,而无需在 `option` 对象中定义它们。你可以使用熟悉的 Vue 模板语法来编写自定义提示框或数据视图中的内容。
 
 **插槽命名约定**
 
-- 插槽名称以 `tooltip` 开头,后面跟随用连字符分隔的路径片段,用于定位要覆盖的 `formatter`。
-- 每个片段对应 `option` 对象的属性名或数组索引(数组索引使用数字形式)。
-- 拼接后的插槽名称直接映射到要覆盖的嵌套 `formatter`。
+- 插槽名称以 `tooltip`/`dataView` 开头,后面跟随用连字符分隔的路径片段,用于定位目标。
+- 每个路径片段对应 `option` 对象的属性名或数组索引(数组索引使用数字形式)。
+- 拼接后的插槽名称直接映射到要覆盖的嵌套回调函数。
 
 **示例映射**:
 
@@ -347,6 +353,8 @@ Vue-ECharts 允许你通过 Vue 插槽来定义 ECharts 配置中的 `tooltip.fo
 - `tooltip-baseOption` → `option.baseOption.tooltip.formatter`
 - `tooltip-xAxis-1` → `option.xAxis[1].tooltip.formatter`
 - `tooltip-series-2-data-4` → `option.series[2].data[4].tooltip.formatter`
+- `dataView` → `option.toolbox.feature.dataView.optionToContent`
+- `dataView-media[1]-option` → `option.media[1].option.toolbox.feature.dataView.optionToContent`
 
 <details>
 <summary>用法示例</summary>
@@ -354,23 +362,37 @@ Vue-ECharts 允许你通过 Vue 插槽来定义 ECharts 配置中的 `tooltip.fo
 ```vue
 <template>
   <v-chart :option="chartOptions">
-    <!-- 覆盖全局 tooltip.formatter -->
+    <!-- 全局 `tooltip.formatter` -->
     <template #tooltip="{ params }">
-      <table>
-        <tr>
-          <th>系列名称</th>
-          <th>数值</th>
-        </tr>
-        <tr v-for="(item, idx) in params" :key="idx">
-          <td>{{ item.seriesName }}</td>
-          <td>{{ item.data }}</td>
-        </tr>
-      </table>
+      <div v-for="(param, i) in params" :key="i">
+        <span v-html="param.marker" />
+        <span>{{ param.seriesName }}</span>
+        <span>{{ param.value[0] }}</span>
+      </div>
     </template>
 
-    <!-- 覆盖x轴 tooltip.formatter -->
+    <!-- x轴 tooltip -->
     <template #tooltip-xAxis="{ params }">
-      <div>X 轴: {{ params.value }}</div>
+      <div>X轴: {{ params.value }}</div>
+    </template>
+
+    <!-- 数据视图内容 -->
+    <template #dataView="{ option }">
+      <table>
+        <thead>
+          <tr>
+            <th v-for="(t, i) in option.dataset[0].source[0]" :key="i">
+              {{ t }}
+            </th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="(row, i) in option.dataset[0].source.slice(1)" :key="i">
+            <th>{{ row[0] }}</th>
+            <td v-for="(v, i) in row.slice(1)" :key="i">{{ v }}</td>
+          </tr>
+        </tbody>
+      </table>
     </template>
   </v-chart>
 </template>
@@ -380,12 +402,8 @@ Vue-ECharts 允许你通过 Vue 插槽来定义 ECharts 配置中的 `tooltip.fo
 
 </details>
 
-#### 插槽Props
-
-- `params`:[`tooltip.formatter`](https://echarts.apache.org/zh/option.html#tooltip.formatter) 回调的第一个参数。
-
 > [!NOTE]
-> 插槽内容会优先于 `props.option` 中对应的 `tooltip.formatter` 渲染,如果两者同时存在。
+> 插槽会优先于 `props.option` 中对应的回调函数。
 
 ### 静态方法
 
diff --git a/demo/CodeGen.vue b/demo/CodeGen.vue
index 08cbfb0..3793bd9 100644
--- a/demo/CodeGen.vue
+++ b/demo/CodeGen.vue
@@ -78,7 +78,11 @@ const transformedCode = ref("");
 const transformErrors = ref([]);
 
 onMounted(async () => {
-  await initialize({ wasmURL });
+  // prevent multiple initializations during HMR
+  if (!window.__esbuildInitialized) {
+    await initialize({ wasmURL });
+    window.__esbuildInitialized = true;
+  }
 
   initializing.value = false;
 
diff --git a/demo/data/logo.js b/demo/data/logo.js
index 6947e3d..017578b 100644
--- a/demo/data/logo.js
+++ b/demo/data/logo.js
@@ -20,10 +20,8 @@ export default {
       },
       shape: `path://${d}`,
       label: {
-        normal: {
-          formatter() {
-            return "";
-          },
+        formatter() {
+          return "";
         },
       },
       itemStyle: {
diff --git a/demo/data/radar.ts b/demo/data/radar.ts
index 8d00c21..6cd8bea 100644
--- a/demo/data/radar.ts
+++ b/demo/data/radar.ts
@@ -27,6 +27,7 @@ export const useScoreStore = defineStore("store", () => {
         fontWeight: 300,
       },
       radar: {
+        splitNumber: 4,
         indicator: scores.value.map(({ name, max }, index) => {
           if (index === activeIndex) {
             return { name, max, color: "goldenrod" };
diff --git a/demo/examples/LineChart.vue b/demo/examples/LineChart.vue
index 659c893..eded634 100644
--- a/demo/examples/LineChart.vue
+++ b/demo/examples/LineChart.vue
@@ -6,6 +6,7 @@ import {
   DatasetComponent,
   LegendComponent,
   TooltipComponent,
+  ToolboxComponent,
 } from "echarts/components";
 import { shallowRef } from "vue";
 import VChart from "../../src/ECharts";
@@ -18,6 +19,7 @@ use([
   LegendComponent,
   LineChart,
   TooltipComponent,
+  ToolboxComponent,
   PieChart,
 ]);
 
@@ -56,7 +58,7 @@ function getPieOption(params) {
   <v-example
     id="line"
     title="Line chart"
-    desc="(with component rendered tooltip)"
+    desc="(with tooltip and dataView slots)"
   >
     <v-chart :option="option" autoresize>
       <template #tooltip="{ params }">
@@ -70,6 +72,23 @@ function getPieOption(params) {
         {{ axis === "xAxis" ? "Year" : "Value" }}:
         <b>{{ params.name }}</b>
       </template>
+      <template #dataView="{ option }">
+        <table style="margin: 20px auto">
+          <thead>
+            <tr>
+              <th v-for="(t, i) in option.dataset[0].source[0]" :key="i">
+                {{ t }}
+              </th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr v-for="(row, i) in option.dataset[0].source.slice(1)" :key="i">
+              <th>{{ row[0] }}</th>
+              <td v-for="(v, i) in row.slice(1)" :key="i">{{ v }}</td>
+            </tr>
+          </tbody>
+        </table>
+      </template>
     </v-chart>
     <template #extra>
       <p class="actions">
@@ -82,3 +101,10 @@ function getPieOption(params) {
     </template>
   </v-example>
 </template>
+
+<style scoped>
+th,
+td {
+  padding: 4px 8px;
+}
+</style>
diff --git a/package.json b/package.json
index a45bddd..163c7b9 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,7 @@
     "docs": "node ./scripts/docs.mjs",
     "prepublishOnly": "pnpm run typecheck && pnpm run dev:typecheck && pnpm run build && publint"
   },
-  "packageManager": "pnpm@10.12.1",
+  "packageManager": "pnpm@10.12.4",
   "type": "module",
   "main": "dist/index.js",
   "unpkg": "dist/index.min.js",
@@ -36,7 +36,7 @@
     "dist"
   ],
   "peerDependencies": {
-    "echarts": "^5.5.1",
+    "echarts": "^6.0.0-beta.1",
     "vue": "^3.1.1"
   },
   "devDependencies": {
@@ -50,7 +50,7 @@
     "@vue/tsconfig": "^0.7.0",
     "@vueuse/core": "^13.1.0",
     "comment-mark": "^2.0.1",
-    "echarts": "^5.6.0",
+    "echarts": "^6.0.0-beta.1",
     "echarts-gl": "^2.0.9",
     "echarts-liquidfill": "^3.1.0",
     "esbuild-wasm": "^0.23.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 64e3b65..5cd968d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -39,14 +39,14 @@ importers:
         specifier: ^2.0.1
         version: 2.0.1
       echarts:
-        specifier: ^5.6.0
-        version: 5.6.0
+        specifier: ^6.0.0-beta.1
+        version: 6.0.0-beta.1
       echarts-gl:
         specifier: ^2.0.9
-        version: 2.0.9(echarts@5.6.0)
+        version: 2.0.9(echarts@6.0.0-beta.1)
       echarts-liquidfill:
         specifier: ^3.1.0
-        version: 3.1.0(echarts@5.6.0)
+        version: 3.1.0(echarts@6.0.0-beta.1)
       esbuild-wasm:
         specifier: ^0.23.0
         version: 0.23.0
@@ -820,8 +820,8 @@ packages:
     peerDependencies:
       echarts: ^5.0.1
 
-  echarts@5.6.0:
-    resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==}
+  echarts@6.0.0-beta.1:
+    resolution: {integrity: sha512-hEtCVOohAWr8fCMNXwg0cRZjkWO+LwbhO30cX/fzwb2LF4sHt06YHVWlAQclayhwHlxCyYtMG9FkFnNUAHK72Q==}
 
   emoji-regex@8.0.0:
     resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -1511,8 +1511,8 @@ packages:
   zrender@5.6.0:
     resolution: {integrity: sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==}
 
-  zrender@5.6.1:
-    resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==}
+  zrender@6.0.0-rc.1:
+    resolution: {integrity: sha512-DWYxDvSHb69PlZ9bs2C4NHt0xHMojHztGForDFAiNSzw9XDwycwXAhJydFrNyq/vy0I8usTZ+KbtZyrX+6ePJQ==}
 
 snapshots:
 
@@ -2137,20 +2137,20 @@ snapshots:
 
   eastasianwidth@0.2.0: {}
 
-  echarts-gl@2.0.9(echarts@5.6.0):
+  echarts-gl@2.0.9(echarts@6.0.0-beta.1):
     dependencies:
       claygl: 1.3.0
-      echarts: 5.6.0
+      echarts: 6.0.0-beta.1
       zrender: 5.6.0
 
-  echarts-liquidfill@3.1.0(echarts@5.6.0):
+  echarts-liquidfill@3.1.0(echarts@6.0.0-beta.1):
     dependencies:
-      echarts: 5.6.0
+      echarts: 6.0.0-beta.1
 
-  echarts@5.6.0:
+  echarts@6.0.0-beta.1:
     dependencies:
       tslib: 2.3.0
-      zrender: 5.6.1
+      zrender: 6.0.0-rc.1
 
   emoji-regex@8.0.0: {}
 
@@ -2823,6 +2823,6 @@ snapshots:
     dependencies:
       tslib: 2.3.0
 
-  zrender@5.6.1:
+  zrender@6.0.0-rc.1:
     dependencies:
       tslib: 2.3.0
diff --git a/scripts/docs.mjs b/scripts/docs.mjs
index dc9c42c..b8e1abc 100644
--- a/scripts/docs.mjs
+++ b/scripts/docs.mjs
@@ -8,7 +8,7 @@ const CDN_PREFIX = "https://cdn.jsdelivr.net/npm/";
 
 const DEP_VERSIONS = {
   vue: "3.5.13",
-  echarts: "5.5.1",
+  echarts: "6.0.0-beta.1",
   [name]: version,
 };
 
diff --git a/src/ECharts.ts b/src/ECharts.ts
index a33b504..56fdc98 100644
--- a/src/ECharts.ts
+++ b/src/ECharts.ts
@@ -19,6 +19,7 @@ import {
   autoresizeProps,
   useLoading,
   loadingProps,
+  useSlotOption,
   type PublicMethods,
 } from "./composables";
 import { isOn, omitOn, toValue } from "./utils";
@@ -40,7 +41,6 @@ import type {
 import type { EChartsElement } from "./wc";
 
 import "./style.css";
-import { useTooltip } from "./composables/tooltip";
 
 const wcRegistered = register();
 
@@ -66,7 +66,8 @@ export default defineComponent({
   },
   emits: {} as unknown as Emits,
   slots: Object as SlotsType<
-    Record<"tooltip" | `tooltip:${string}`, { params: any }>
+    Record<"tooltip" | `tooltip-${string}`, { params: any }> &
+      Record<"dataView" | `dataView-${string}`, { option: Option }>
   >,
   inheritAttrs: false,
   setup(props, { attrs, expose, slots }) {
@@ -256,7 +257,7 @@ export default defineComponent({
     );
 
     watch(
-      [realTheme, realInitOptions],
+      realInitOptions,
       () => {
         cleanup();
         init();
@@ -266,6 +267,16 @@ export default defineComponent({
       },
     );
 
+    watch(
+      realTheme,
+      (theme) => {
+        chart.value?.setTheme(theme);
+      },
+      {
+        deep: true,
+      },
+    );
+
     watchEffect(() => {
       if (props.group && chart.value) {
         chart.value.group = props.group;
@@ -278,7 +289,7 @@ export default defineComponent({
 
     useAutoresize(chart, autoresize, root);
 
-    const { teleportedSlots, patchOption } = useTooltip(slots, () => {
+    const { teleportedSlots, patchOption } = useSlotOption(slots, () => {
       if (!manualUpdate.value && props.option && chart.value) {
         chart.value.setOption(
           patchOption(props.option),
diff --git a/src/composables/index.ts b/src/composables/index.ts
index 7708f91..68526de 100644
--- a/src/composables/index.ts
+++ b/src/composables/index.ts
@@ -1,3 +1,4 @@
 export * from "./api";
 export * from "./autoresize";
 export * from "./loading";
+export * from "./slot";
diff --git a/src/composables/tooltip.ts b/src/composables/slot.ts
similarity index 54%
rename from src/composables/tooltip.ts
rename to src/composables/slot.ts
index 7c36f03..cbedc49 100644
--- a/src/composables/tooltip.ts
+++ b/src/composables/slot.ts
@@ -11,11 +11,19 @@ import {
 import type { Option } from "../types";
 import { isValidArrayIndex } from "../utils";
 
-function isTooltipSlot(key: string) {
-  return key === "tooltip" || key.startsWith("tooltip-");
+const SLOT_PATH_MAP = {
+  tooltip: ["tooltip", "formatter"],
+  dataView: ["toolbox", "feature", "dataView", "optionToContent"],
+};
+type SlotPrefix = keyof typeof SLOT_PATH_MAP;
+
+function isValidSlotName(key: string) {
+  return Object.keys(SLOT_PATH_MAP).some(
+    (slotPrefix) => key === slotPrefix || key.startsWith(slotPrefix + "-"),
+  );
 }
 
-export function useTooltip(slots: Slots, onSlotsChange: () => void) {
+export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
   const detachedRoot =
     typeof window !== "undefined" ? document.createElement("div") : undefined;
   const containers = shallowReactive<Record<string, HTMLElement>>({});
@@ -31,14 +39,18 @@ export function useTooltip(slots: Slots, onSlotsChange: () => void) {
           Teleport as any,
           { to: detachedRoot, defer: true },
           Object.entries(slots)
-            .filter(([key]) => isTooltipSlot(key))
+            .filter(([key]) => isValidSlotName(key))
             .map(([key, slot]) => {
+              const propName = key.startsWith("tooltip") ? "params" : "option";
               const slotContent = initialized[key]
-                ? slot?.({ params: params[key] })
+                ? slot?.({ [propName]: params[key] })
                 : undefined;
               return h(
                 "div",
-                { ref: (el) => (containers[key] = el as HTMLElement) },
+                {
+                  ref: (el) => (containers[key] = el as HTMLElement),
+                  style: { display: "contents" },
+                },
                 slotContent,
               );
             }),
@@ -46,43 +58,37 @@ export function useTooltip(slots: Slots, onSlotsChange: () => void) {
       : undefined;
   };
 
-  // Shallow clone the option along the path and patch the tooltip formatter
+  // Shallow clone the option along the path and override the target callback
   function patchOption(src: Option): Option {
     const root = { ...src };
 
     Object.keys(slots)
-      .filter((key) => isTooltipSlot(key))
+      .filter((key) => isValidSlotName(key))
       .forEach((key) => {
         const path = key.split("-");
-        path.push(path.shift()!);
-        let cur: any = root;
+        const prefix = path.shift() as SlotPrefix;
+        path.push(...SLOT_PATH_MAP[prefix]);
 
-        for (let i = 0; i < path.length; i++) {
+        let cur: any = root;
+        for (let i = 0; i < path.length - 1; i++) {
           const seg = path[i];
           const next = cur[seg];
 
-          if (i < path.length - 1) {
-            // shallow-clone the link; create empty shell if missing
-            cur[seg] = next
-              ? Array.isArray(next)
-                ? [...next]
-                : { ...next }
-              : isValidArrayIndex(seg)
-                ? []
-                : {};
-            cur = cur[seg];
-          } else {
-            // final node = tooltip
-            cur[seg] = {
-              ...(next || {}),
-              formatter(p: any) {
-                initialized[key] = true;
-                params[key] = p;
-                return containers[key];
-              },
-            };
-          }
+          // shallow-clone the link; create empty shell if missing
+          cur[seg] = next
+            ? Array.isArray(next)
+              ? [...next]
+              : { ...next }
+            : isValidArrayIndex(seg)
+              ? []
+              : {};
+          cur = cur[seg];
         }
+        cur[path[path.length - 1]] = (p: any) => {
+          initialized[key] = true;
+          params[key] = p;
+          return containers[key];
+        };
       });
 
     return root;
@@ -90,9 +96,15 @@ export function useTooltip(slots: Slots, onSlotsChange: () => void) {
 
   // `slots` is not reactive and cannot be watched
   // so we need to watch it manually
-  let slotNames = Object.keys(slots).filter((key) => isTooltipSlot(key));
+  let slotNames: string[] = [];
   onUpdated(() => {
-    const newSlotNames = Object.keys(slots).filter((key) => isTooltipSlot(key));
+    const newSlotNames = Object.keys(slots).filter((key) => {
+      if (isValidSlotName(key)) {
+        return true;
+      }
+      console.warn(`[vue-echarts] Invalid slot name: ${key}`);
+      return false;
+    });
     if (newSlotNames.join() !== slotNames.join()) {
       // Clean up params and initialized for removed slots
       slotNames.forEach((key) => {

From 429943ab283864ee9b0b4df7523557afdeda2672 Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Mon, 7 Jul 2025 13:41:06 +0800
Subject: [PATCH 19/27] fix docs typo

---
 README.md         | 2 +-
 README.zh-Hans.md | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index a0d293d..ca8e014 100644
--- a/README.md
+++ b/README.md
@@ -354,7 +354,7 @@ Vue-ECharts allows you to define ECharts option's [`tooltip.formatter`](https://
 - `tooltip-xAxis-1` → `option.xAxis[1].tooltip.formatter`
 - `tooltip-series-2-data-4` → `option.series[2].data[4].tooltip.formatter`
 - `dataView` → `option.toolbox.feature.dataView.optionToContent`
-- `dataView-media[1]-option` → `option.media[1].option.toolbox.feature.dataView.optionToContent`
+- `dataView-media-1-option` → `option.media[1].option.toolbox.feature.dataView.optionToContent`
 
 <details>
 <summary>Usage</summary>
diff --git a/README.zh-Hans.md b/README.zh-Hans.md
index e4b9456..f2d1fe4 100644
--- a/README.zh-Hans.md
+++ b/README.zh-Hans.md
@@ -354,7 +354,7 @@ Vue-ECharts 允许你通过 Vue 插槽来定义 ECharts 配置中的 [`tooltip.f
 - `tooltip-xAxis-1` → `option.xAxis[1].tooltip.formatter`
 - `tooltip-series-2-data-4` → `option.series[2].data[4].tooltip.formatter`
 - `dataView` → `option.toolbox.feature.dataView.optionToContent`
-- `dataView-media[1]-option` → `option.media[1].option.toolbox.feature.dataView.optionToContent`
+- `dataView-media-1-option` → `option.media[1].option.toolbox.feature.dataView.optionToContent`
 
 <details>
 <summary>用法示例</summary>

From c8a8869ed05f06fbd9bf33fc265bf51e589fce6a Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Tue, 15 Jul 2025 16:43:37 +0800
Subject: [PATCH 20/27] update according to the review

---
 src/composables/slot.ts | 79 +++++++++++++++++++++++++----------------
 src/utils.ts            | 13 +++++++
 2 files changed, 62 insertions(+), 30 deletions(-)

diff --git a/src/composables/slot.ts b/src/composables/slot.ts
index cbedc49..48bb1a6 100644
--- a/src/composables/slot.ts
+++ b/src/composables/slot.ts
@@ -6,19 +6,28 @@ import {
   onMounted,
   shallowRef,
   shallowReactive,
-  type Slots,
+  warn,
 } from "vue";
+import type { Slots } from "vue";
 import type { Option } from "../types";
-import { isValidArrayIndex } from "../utils";
+import { isValidArrayIndex, isSameSet } from "../utils";
 
-const SLOT_PATH_MAP = {
-  tooltip: ["tooltip", "formatter"],
-  dataView: ["toolbox", "feature", "dataView", "optionToContent"],
-};
-type SlotPrefix = keyof typeof SLOT_PATH_MAP;
+const SLOT_CONFIG = {
+  tooltip: {
+    path: ["tooltip", "formatter"],
+    propNames: ["params"],
+  },
+  dataView: {
+    path: ["toolbox", "feature", "dataView", "optionToContent"],
+    propNames: ["option"],
+  },
+} as const;
+type SlotPrefix = keyof typeof SLOT_CONFIG;
+type SlotName = SlotPrefix | `${SlotPrefix}-${string}`;
+const SLOT_PREFIXES = Object.keys(SLOT_CONFIG) as SlotPrefix[];
 
-function isValidSlotName(key: string) {
-  return Object.keys(SLOT_PATH_MAP).some(
+function isValidSlotName(key: string): key is SlotName {
+  return SLOT_PREFIXES.some(
     (slotPrefix) => key === slotPrefix || key.startsWith(slotPrefix + "-"),
   );
 }
@@ -26,9 +35,13 @@ function isValidSlotName(key: string) {
 export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
   const detachedRoot =
     typeof window !== "undefined" ? document.createElement("div") : undefined;
-  const containers = shallowReactive<Record<string, HTMLElement>>({});
-  const initialized = shallowReactive<Record<string, boolean>>({});
-  const params = shallowReactive<Record<string, any>>({});
+  const containers = shallowReactive<Partial<Record<SlotName, HTMLElement>>>(
+    {},
+  );
+  const initialized = shallowReactive<Partial<Record<SlotName, boolean>>>({});
+  const params = shallowReactive<
+    Partial<Record<SlotName, Record<string, any>>>
+  >({});
   const isMounted = shallowRef(false);
 
   // Teleport the tooltip slots to a detached root
@@ -41,14 +54,14 @@ export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
           Object.entries(slots)
             .filter(([key]) => isValidSlotName(key))
             .map(([key, slot]) => {
-              const propName = key.startsWith("tooltip") ? "params" : "option";
-              const slotContent = initialized[key]
-                ? slot?.({ [propName]: params[key] })
+              const slotName = key as SlotName;
+              const slotContent = initialized[slotName]
+                ? slot?.(params[slotName])
                 : undefined;
               return h(
                 "div",
                 {
-                  ref: (el) => (containers[key] = el as HTMLElement),
+                  ref: (el) => (containers[slotName] = el as HTMLElement),
                   style: { display: "contents" },
                 },
                 slotContent,
@@ -63,11 +76,17 @@ export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
     const root = { ...src };
 
     Object.keys(slots)
-      .filter((key) => isValidSlotName(key))
+      .filter((key) => {
+        const isValidSlot = isValidSlotName(key);
+        if (!isValidSlot) {
+          warn(`Invalid slot name: ${key}`);
+        }
+        return isValidSlot;
+      })
       .forEach((key) => {
         const path = key.split("-");
         const prefix = path.shift() as SlotPrefix;
-        path.push(...SLOT_PATH_MAP[prefix]);
+        path.push(...SLOT_CONFIG[prefix].path);
 
         let cur: any = root;
         for (let i = 0; i < path.length - 1; i++) {
@@ -84,9 +103,15 @@ export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
               : {};
           cur = cur[seg];
         }
-        cur[path[path.length - 1]] = (p: any) => {
+        cur[path[path.length - 1]] = (...args: any[]) => {
           initialized[key] = true;
-          params[key] = p;
+          params[key] = SLOT_CONFIG[prefix].propNames.reduce(
+            (acc, paramName, index) => {
+              acc[paramName] = args[index];
+              return acc;
+            },
+            {} as Record<string, any>,
+          );
           return containers[key];
         };
       });
@@ -96,19 +121,13 @@ export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
 
   // `slots` is not reactive and cannot be watched
   // so we need to watch it manually
-  let slotNames: string[] = [];
+  let slotNames: SlotName[] = [];
   onUpdated(() => {
-    const newSlotNames = Object.keys(slots).filter((key) => {
-      if (isValidSlotName(key)) {
-        return true;
-      }
-      console.warn(`[vue-echarts] Invalid slot name: ${key}`);
-      return false;
-    });
-    if (newSlotNames.join() !== slotNames.join()) {
+    const newSlotNames = Object.keys(slots).filter(isValidSlotName);
+    if (!isSameSet(newSlotNames, slotNames)) {
       // Clean up params and initialized for removed slots
       slotNames.forEach((key) => {
-        if (!(key in slots)) {
+        if (!newSlotNames.includes(key)) {
           delete params[key];
           delete initialized[key];
           delete containers[key];
diff --git a/src/utils.ts b/src/utils.ts
index 4d3a383..2169a9e 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -39,3 +39,16 @@ export function isValidArrayIndex(key: string): boolean {
     String(num) === key
   );
 }
+
+export function isSameSet<T>(a: T[], b: T[]): boolean {
+  const setA = new Set(a);
+  const setB = new Set(b);
+
+  if (setA.size !== setB.size) return false;
+
+  for (const val of setA) {
+    if (!setB.has(val)) return false;
+  }
+
+  return true;
+}

From f5fdc88bc8c0cfc8626a35e05b8091f808c808c7 Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Tue, 15 Jul 2025 17:30:37 +0800
Subject: [PATCH 21/27] small fix

---
 demo/examples/LineChart.vue |  6 ++----
 src/composables/slot.ts     | 13 +++++--------
 2 files changed, 7 insertions(+), 12 deletions(-)

diff --git a/demo/examples/LineChart.vue b/demo/examples/LineChart.vue
index eded634..e458627 100644
--- a/demo/examples/LineChart.vue
+++ b/demo/examples/LineChart.vue
@@ -42,10 +42,8 @@ function getPieOption(params) {
         label: {
           position: "center",
           formatter: params[0].name,
-          textStyle: {
-            fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
-            fontWeight: 300,
-          },
+          fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
+          fontWeight: 300,
         },
       },
     ],
diff --git a/src/composables/slot.ts b/src/composables/slot.ts
index 48bb1a6..271b3e3 100644
--- a/src/composables/slot.ts
+++ b/src/composables/slot.ts
@@ -24,6 +24,7 @@ const SLOT_CONFIG = {
 } as const;
 type SlotPrefix = keyof typeof SLOT_CONFIG;
 type SlotName = SlotPrefix | `${SlotPrefix}-${string}`;
+type SlotRecord<T> = Partial<Record<SlotName, T>>;
 const SLOT_PREFIXES = Object.keys(SLOT_CONFIG) as SlotPrefix[];
 
 function isValidSlotName(key: string): key is SlotName {
@@ -35,13 +36,9 @@ function isValidSlotName(key: string): key is SlotName {
 export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
   const detachedRoot =
     typeof window !== "undefined" ? document.createElement("div") : undefined;
-  const containers = shallowReactive<Partial<Record<SlotName, HTMLElement>>>(
-    {},
-  );
-  const initialized = shallowReactive<Partial<Record<SlotName, boolean>>>({});
-  const params = shallowReactive<
-    Partial<Record<SlotName, Record<string, any>>>
-  >({});
+  const containers = shallowReactive<SlotRecord<HTMLElement>>({});
+  const initialized = shallowReactive<SlotRecord<boolean>>({});
+  const params = shallowReactive<SlotRecord<Record<string, any>>>({});
   const isMounted = shallowRef(false);
 
   // Teleport the tooltip slots to a detached root
@@ -79,7 +76,7 @@ export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
       .filter((key) => {
         const isValidSlot = isValidSlotName(key);
         if (!isValidSlot) {
-          warn(`Invalid slot name: ${key}`);
+          warn(`Invalid vue-echarts slot name: ${key}`);
         }
         return isValidSlot;
       })

From 49d807bd5f0b1c5da92c0b080543ac42bce48a40 Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Sat, 19 Jul 2025 16:20:23 +0800
Subject: [PATCH 22/27] remove wrapper around slotProp

---
 README.md                   |  6 +++---
 README.zh-Hans.md           |  6 +++---
 demo/examples/LineChart.vue |  6 +++---
 src/ECharts.ts              |  4 ++--
 src/composables/slot.ts     | 30 +++++++++---------------------
 5 files changed, 20 insertions(+), 32 deletions(-)

diff --git a/README.md b/README.md
index ca8e014..a05e0b4 100644
--- a/README.md
+++ b/README.md
@@ -363,7 +363,7 @@ Vue-ECharts allows you to define ECharts option's [`tooltip.formatter`](https://
 <template>
   <v-chart :option="chartOptions">
     <!-- Global `tooltip.formatter` -->
-    <template #tooltip="{ params }">
+    <template #tooltip="params">
       <div v-for="(param, i) in params" :key="i">
         <span v-html="param.marker" />
         <span>{{ param.seriesName }}</span>
@@ -372,12 +372,12 @@ Vue-ECharts allows you to define ECharts option's [`tooltip.formatter`](https://
     </template>
 
     <!-- Tooltip on xAxis -->
-    <template #tooltip-xAxis="{ params }">
+    <template #tooltip-xAxis="params">
       <div>X-Axis : {{ params.value }}</div>
     </template>
 
     <!-- Data View Content -->
-    <template #dataView="{ option }">
+    <template #dataView="option">
       <table>
         <thead>
           <tr>
diff --git a/README.zh-Hans.md b/README.zh-Hans.md
index f2d1fe4..6b6d3be 100644
--- a/README.zh-Hans.md
+++ b/README.zh-Hans.md
@@ -363,7 +363,7 @@ Vue-ECharts 允许你通过 Vue 插槽来定义 ECharts 配置中的 [`tooltip.f
 <template>
   <v-chart :option="chartOptions">
     <!-- 全局 `tooltip.formatter` -->
-    <template #tooltip="{ params }">
+    <template #tooltip="params">
       <div v-for="(param, i) in params" :key="i">
         <span v-html="param.marker" />
         <span>{{ param.seriesName }}</span>
@@ -372,12 +372,12 @@ Vue-ECharts 允许你通过 Vue 插槽来定义 ECharts 配置中的 [`tooltip.f
     </template>
 
     <!-- x轴 tooltip -->
-    <template #tooltip-xAxis="{ params }">
+    <template #tooltip-xAxis="params">
       <div>X轴: {{ params.value }}</div>
     </template>
 
     <!-- 数据视图内容 -->
-    <template #dataView="{ option }">
+    <template #dataView="option">
       <table>
         <thead>
           <tr>
diff --git a/demo/examples/LineChart.vue b/demo/examples/LineChart.vue
index e458627..30173d4 100644
--- a/demo/examples/LineChart.vue
+++ b/demo/examples/LineChart.vue
@@ -59,18 +59,18 @@ function getPieOption(params) {
     desc="(with tooltip and dataView slots)"
   >
     <v-chart :option="option" autoresize>
-      <template #tooltip="{ params }">
+      <template #tooltip="params">
         <v-chart
           :style="{ width: '100px', height: '100px' }"
           :option="getPieOption(params)"
           autoresize
         />
       </template>
-      <template #[`tooltip-${axis}`]="{ params }">
+      <template #[`tooltip-${axis}`]="params">
         {{ axis === "xAxis" ? "Year" : "Value" }}:
         <b>{{ params.name }}</b>
       </template>
-      <template #dataView="{ option }">
+      <template #dataView="option">
         <table style="margin: 20px auto">
           <thead>
             <tr>
diff --git a/src/ECharts.ts b/src/ECharts.ts
index 56fdc98..5561fde 100644
--- a/src/ECharts.ts
+++ b/src/ECharts.ts
@@ -66,8 +66,8 @@ export default defineComponent({
   },
   emits: {} as unknown as Emits,
   slots: Object as SlotsType<
-    Record<"tooltip" | `tooltip-${string}`, { params: any }> &
-      Record<"dataView" | `dataView-${string}`, { option: Option }>
+    Record<"tooltip" | `tooltip-${string}`, any> &
+      Record<"dataView" | `dataView-${string}`, Option>
   >,
   inheritAttrs: false,
   setup(props, { attrs, expose, slots }) {
diff --git a/src/composables/slot.ts b/src/composables/slot.ts
index 271b3e3..88243f2 100644
--- a/src/composables/slot.ts
+++ b/src/composables/slot.ts
@@ -12,20 +12,14 @@ import type { Slots } from "vue";
 import type { Option } from "../types";
 import { isValidArrayIndex, isSameSet } from "../utils";
 
-const SLOT_CONFIG = {
-  tooltip: {
-    path: ["tooltip", "formatter"],
-    propNames: ["params"],
-  },
-  dataView: {
-    path: ["toolbox", "feature", "dataView", "optionToContent"],
-    propNames: ["option"],
-  },
+const SLOT_OPTION_PATHS = {
+  tooltip: ["tooltip", "formatter"],
+  dataView: ["toolbox", "feature", "dataView", "optionToContent"],
 } as const;
-type SlotPrefix = keyof typeof SLOT_CONFIG;
+type SlotPrefix = keyof typeof SLOT_OPTION_PATHS;
 type SlotName = SlotPrefix | `${SlotPrefix}-${string}`;
 type SlotRecord<T> = Partial<Record<SlotName, T>>;
-const SLOT_PREFIXES = Object.keys(SLOT_CONFIG) as SlotPrefix[];
+const SLOT_PREFIXES = Object.keys(SLOT_OPTION_PATHS) as SlotPrefix[];
 
 function isValidSlotName(key: string): key is SlotName {
   return SLOT_PREFIXES.some(
@@ -47,7 +41,7 @@ export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
     return isMounted.value
       ? h(
           Teleport as any,
-          { to: detachedRoot, defer: true },
+          { to: detachedRoot },
           Object.entries(slots)
             .filter(([key]) => isValidSlotName(key))
             .map(([key, slot]) => {
@@ -83,7 +77,7 @@ export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
       .forEach((key) => {
         const path = key.split("-");
         const prefix = path.shift() as SlotPrefix;
-        path.push(...SLOT_CONFIG[prefix].path);
+        path.push(...SLOT_OPTION_PATHS[prefix]);
 
         let cur: any = root;
         for (let i = 0; i < path.length - 1; i++) {
@@ -100,15 +94,9 @@ export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
               : {};
           cur = cur[seg];
         }
-        cur[path[path.length - 1]] = (...args: any[]) => {
+        cur[path[path.length - 1]] = (p: any) => {
           initialized[key] = true;
-          params[key] = SLOT_CONFIG[prefix].propNames.reduce(
-            (acc, paramName, index) => {
-              acc[paramName] = args[index];
-              return acc;
-            },
-            {} as Record<string, any>,
-          );
+          params[key] = p;
           return containers[key];
         };
       });

From fe3040acda3c3d62028668034bb465344d5c5a2b Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Sat, 19 Jul 2025 16:31:15 +0800
Subject: [PATCH 23/27] update comments

---
 README.md               |  2 ++
 README.zh-Hans.md       |  2 ++
 src/composables/slot.ts | 13 ++++++-------
 3 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/README.md b/README.md
index a05e0b4..cd990b2 100644
--- a/README.md
+++ b/README.md
@@ -356,6 +356,8 @@ Vue-ECharts allows you to define ECharts option's [`tooltip.formatter`](https://
 - `dataView` → `option.toolbox.feature.dataView.optionToContent`
 - `dataView-media-1-option` → `option.media[1].option.toolbox.feature.dataView.optionToContent`
 
+The slot props correspond to the first parameter of the callback function.
+
 <details>
 <summary>Usage</summary>
 
diff --git a/README.zh-Hans.md b/README.zh-Hans.md
index 6b6d3be..dceef5f 100644
--- a/README.zh-Hans.md
+++ b/README.zh-Hans.md
@@ -356,6 +356,8 @@ Vue-ECharts 允许你通过 Vue 插槽来定义 ECharts 配置中的 [`tooltip.f
 - `dataView` → `option.toolbox.feature.dataView.optionToContent`
 - `dataView-media-1-option` → `option.media[1].option.toolbox.feature.dataView.optionToContent`
 
+插槽的 props 对象对应回调函数的第一个参数。
+
 <details>
 <summary>用法示例</summary>
 
diff --git a/src/composables/slot.ts b/src/composables/slot.ts
index 88243f2..8ba3bbd 100644
--- a/src/composables/slot.ts
+++ b/src/composables/slot.ts
@@ -35,9 +35,9 @@ export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
   const params = shallowReactive<SlotRecord<Record<string, any>>>({});
   const isMounted = shallowRef(false);
 
-  // Teleport the tooltip slots to a detached root
+  // Teleport the slots to a detached root
   const teleportedSlots = () => {
-    // Make tooltip slots client-side only to avoid SSR hydration mismatch
+    // Make slots client-side only to avoid SSR hydration mismatch
     return isMounted.value
       ? h(
           Teleport as any,
@@ -62,7 +62,7 @@ export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
       : undefined;
   };
 
-  // Shallow clone the option along the path and override the target callback
+  // Shallow-clone the option along the path and override the target callback
   function patchOption(src: Option): Option {
     const root = { ...src };
 
@@ -84,7 +84,7 @@ export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
           const seg = path[i];
           const next = cur[seg];
 
-          // shallow-clone the link; create empty shell if missing
+          // Shallow-clone the link; create empty shell if missing
           cur[seg] = next
             ? Array.isArray(next)
               ? [...next]
@@ -104,13 +104,12 @@ export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
     return root;
   }
 
-  // `slots` is not reactive and cannot be watched
-  // so we need to watch it manually
+  // `slots` is not reactive, so we need to watch it manually
   let slotNames: SlotName[] = [];
   onUpdated(() => {
     const newSlotNames = Object.keys(slots).filter(isValidSlotName);
     if (!isSameSet(newSlotNames, slotNames)) {
-      // Clean up params and initialized for removed slots
+      // Clean up states for removed slots
       slotNames.forEach((key) => {
         if (!newSlotNames.includes(key)) {
           delete params[key];

From c7d9f451669316a5913cb6412e379cffbed90244 Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Tue, 22 Jul 2025 12:40:13 +0800
Subject: [PATCH 24/27] remove anys

---
 src/composables/slot.ts | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/composables/slot.ts b/src/composables/slot.ts
index 8ba3bbd..f01b747 100644
--- a/src/composables/slot.ts
+++ b/src/composables/slot.ts
@@ -32,7 +32,7 @@ export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
     typeof window !== "undefined" ? document.createElement("div") : undefined;
   const containers = shallowReactive<SlotRecord<HTMLElement>>({});
   const initialized = shallowReactive<SlotRecord<boolean>>({});
-  const params = shallowReactive<SlotRecord<Record<string, any>>>({});
+  const params = shallowReactive<SlotRecord<unknown>>({});
   const isMounted = shallowRef(false);
 
   // Teleport the slots to a detached root
@@ -40,7 +40,7 @@ export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
     // Make slots client-side only to avoid SSR hydration mismatch
     return isMounted.value
       ? h(
-          Teleport as any,
+          Teleport,
           { to: detachedRoot },
           Object.entries(slots)
             .filter(([key]) => isValidSlotName(key))
@@ -94,7 +94,7 @@ export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
               : {};
           cur = cur[seg];
         }
-        cur[path[path.length - 1]] = (p: any) => {
+        cur[path[path.length - 1]] = (p: unknown) => {
           initialized[key] = true;
           params[key] = p;
           return containers[key];

From 7e6132f18b6f8772d88279283f4107610200cda9 Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Tue, 22 Jul 2025 13:47:49 +0800
Subject: [PATCH 25/27] add tooltip slot prop type

---
 src/ECharts.ts | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/ECharts.ts b/src/ECharts.ts
index 5561fde..977065c 100644
--- a/src/ECharts.ts
+++ b/src/ECharts.ts
@@ -12,6 +12,7 @@ import {
   watchEffect,
 } from "vue";
 import { init as initChart } from "echarts/core";
+import type { TooltipComponentFormatterCallbackParams } from "echarts";
 
 import {
   usePublicAPI,
@@ -66,7 +67,10 @@ export default defineComponent({
   },
   emits: {} as unknown as Emits,
   slots: Object as SlotsType<
-    Record<"tooltip" | `tooltip-${string}`, any> &
+    Record<
+      "tooltip" | `tooltip-${string}`,
+      TooltipComponentFormatterCallbackParams
+    > &
       Record<"dataView" | `dataView-${string}`, Option>
   >,
   inheritAttrs: false,

From 00f222a438fdb255851c406dbc1b6ed34e400b8a Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Thu, 24 Jul 2025 22:10:01 +0800
Subject: [PATCH 26/27] target to vue 3.3

---
 package.json               |  2 +-
 src/ECharts.ts             |  3 ++-
 src/composables/loading.ts |  5 ++---
 src/types.ts               | 11 +----------
 src/utils.ts               | 14 --------------
 5 files changed, 6 insertions(+), 29 deletions(-)

diff --git a/package.json b/package.json
index 163c7b9..ec7620b 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,7 @@
   ],
   "peerDependencies": {
     "echarts": "^6.0.0-beta.1",
-    "vue": "^3.1.1"
+    "vue": "^3.3.0"
   },
   "devDependencies": {
     "@highlightjs/vue-plugin": "^2.1.0",
diff --git a/src/ECharts.ts b/src/ECharts.ts
index 977065c..aa3982d 100644
--- a/src/ECharts.ts
+++ b/src/ECharts.ts
@@ -10,6 +10,7 @@ import {
   h,
   nextTick,
   watchEffect,
+  toValue,
 } from "vue";
 import { init as initChart } from "echarts/core";
 import type { TooltipComponentFormatterCallbackParams } from "echarts";
@@ -23,7 +24,7 @@ import {
   useSlotOption,
   type PublicMethods,
 } from "./composables";
-import { isOn, omitOn, toValue } from "./utils";
+import { isOn, omitOn } from "./utils";
 import { register, TAG_NAME } from "./wc";
 
 import type { PropType, InjectionKey, SlotsType } from "vue";
diff --git a/src/composables/loading.ts b/src/composables/loading.ts
index e305fc9..6c76379 100644
--- a/src/composables/loading.ts
+++ b/src/composables/loading.ts
@@ -1,5 +1,4 @@
-import { inject, computed, watchEffect } from "vue";
-import { toValue } from "../utils";
+import { inject, computed, watchEffect, toValue } from "vue";
 
 import type { Ref, InjectionKey, PropType } from "vue";
 import type {
@@ -18,7 +17,7 @@ export function useLoading(
 ): void {
   const defaultLoadingOptions = inject(LOADING_OPTIONS_KEY, {});
   const realLoadingOptions = computed(() => ({
-    ...(toValue(defaultLoadingOptions) || {}),
+    ...toValue(defaultLoadingOptions),
     ...loadingOptions?.value,
   }));
 
diff --git a/src/types.ts b/src/types.ts
index 0e44e81..df574f8 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,17 +1,8 @@
 import { init } from "echarts/core";
 
 import type { SetOptionOpts, ECElementEvent, ElementEvent } from "echarts/core";
-import type { Ref, ShallowRef, WritableComputedRef, ComputedRef } from "vue";
+import type { MaybeRefOrGetter } from "vue";
 
-export type MaybeRef<T = any> =
-  | T
-  | Ref<T>
-  | ShallowRef<T>
-  | WritableComputedRef<T>;
-export type MaybeRefOrGetter<T = any> =
-  | MaybeRef<T>
-  | ComputedRef<T>
-  | (() => T);
 export type Injection<T> = MaybeRefOrGetter<T | null>;
 
 type InitType = typeof init;
diff --git a/src/utils.ts b/src/utils.ts
index 2169a9e..4dc1e11 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,6 +1,3 @@
-import type { MaybeRefOrGetter } from "./types";
-import { unref } from "vue";
-
 type Attrs = Record<string, any>;
 
 // Copied from
@@ -19,17 +16,6 @@ export function omitOn(attrs: Attrs): Attrs {
   return result;
 }
 
-// Copied from
-// https://github.com/vuejs/core/blob/3cb4db21efa61852b0541475b4ddf57fdec4c479/packages/shared/src/general.ts#L49-L50
-// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
-const isFunction = (val: unknown): val is Function => typeof val === "function";
-
-// Copied from
-// https://github.com/vuejs/core/blob/3cb4db21efa61852b0541475b4ddf57fdec4c479/packages/reactivity/src/ref.ts#L246-L248
-export function toValue<T>(source: MaybeRefOrGetter<T>): T {
-  return isFunction(source) ? source() : unref(source);
-}
-
 export function isValidArrayIndex(key: string): boolean {
   const num = Number(key);
   return (

From f356f89bf73ff26760dab0b382d3925f92707f15 Mon Sep 17 00:00:00 2001
From: Yue JIN <yuejin13@qq.com>
Date: Thu, 24 Jul 2025 22:46:17 +0800
Subject: [PATCH 27/27] move slot related codes to slot.ts

---
 src/ECharts.ts          | 31 ++++++++++++-------------------
 src/composables/slot.ts | 11 ++++++++++-
 2 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/src/ECharts.ts b/src/ECharts.ts
index aa3982d..93739f0 100644
--- a/src/ECharts.ts
+++ b/src/ECharts.ts
@@ -13,7 +13,6 @@ import {
   toValue,
 } from "vue";
 import { init as initChart } from "echarts/core";
-import type { TooltipComponentFormatterCallbackParams } from "echarts";
 
 import {
   usePublicAPI,
@@ -22,12 +21,12 @@ import {
   useLoading,
   loadingProps,
   useSlotOption,
-  type PublicMethods,
 } from "./composables";
+import type { PublicMethods, SlotsTypes } from "./composables";
 import { isOn, omitOn } from "./utils";
 import { register, TAG_NAME } from "./wc";
 
-import type { PropType, InjectionKey, SlotsType } from "vue";
+import type { PropType, InjectionKey } from "vue";
 import type {
   EChartsType,
   SetOptionType,
@@ -67,13 +66,7 @@ export default defineComponent({
     ...loadingProps,
   },
   emits: {} as unknown as Emits,
-  slots: Object as SlotsType<
-    Record<
-      "tooltip" | `tooltip-${string}`,
-      TooltipComponentFormatterCallbackParams
-    > &
-      Record<"dataView" | `dataView-${string}`, Option>
-  >,
+  slots: Object as SlotsTypes,
   inheritAttrs: false,
   setup(props, { attrs, expose, slots }) {
     const root = shallowRef<EChartsElement>();
@@ -103,6 +96,15 @@ export default defineComponent({
     const listeners: Map<{ event: string; once?: boolean; zr?: boolean }, any> =
       new Map();
 
+    const { teleportedSlots, patchOption } = useSlotOption(slots, () => {
+      if (!manualUpdate.value && props.option && chart.value) {
+        chart.value.setOption(
+          patchOption(props.option),
+          realUpdateOptions.value,
+        );
+      }
+    });
+
     // We are converting all `on<Event>` props and collect them into `listeners` so that
     // we can bind them to the chart instance later.
     // For `onNative:<event>` props, we just strip the `Native:` part and collect them into
@@ -294,15 +296,6 @@ export default defineComponent({
 
     useAutoresize(chart, autoresize, root);
 
-    const { teleportedSlots, patchOption } = useSlotOption(slots, () => {
-      if (!manualUpdate.value && props.option && chart.value) {
-        chart.value.setOption(
-          patchOption(props.option),
-          realUpdateOptions.value,
-        );
-      }
-    });
-
     onMounted(() => {
       init();
     });
diff --git a/src/composables/slot.ts b/src/composables/slot.ts
index f01b747..b355526 100644
--- a/src/composables/slot.ts
+++ b/src/composables/slot.ts
@@ -8,9 +8,10 @@ import {
   shallowReactive,
   warn,
 } from "vue";
-import type { Slots } from "vue";
+import type { Slots, SlotsType } from "vue";
 import type { Option } from "../types";
 import { isValidArrayIndex, isSameSet } from "../utils";
+import type { TooltipComponentFormatterCallbackParams } from "echarts";
 
 const SLOT_OPTION_PATHS = {
   tooltip: ["tooltip", "formatter"],
@@ -135,3 +136,11 @@ export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
     patchOption,
   };
 }
+
+export type SlotsTypes = SlotsType<
+  Record<
+    "tooltip" | `tooltip-${string}`,
+    TooltipComponentFormatterCallbackParams
+  > &
+    Record<"dataView" | `dataView-${string}`, Option>
+>;