diff --git a/docs/assets/demo-react/en/functional-components/arco-select-editor.md b/docs/assets/demo-react/en/functional-components/arco-select-editor.md
index a6bb930ee6..1b533b0de9 100644
--- a/docs/assets/demo-react/en/functional-components/arco-select-editor.md
+++ b/docs/assets/demo-react/en/functional-components/arco-select-editor.md
@@ -20,6 +20,8 @@ https://arco.design/react/components/select
```javascript livedemo template=vtable-react
// import * as ReactVTable from '@visactor/react-vtable';
+const selectEditorPopupClassName = 'vtable-editor-select-popup';
+
class ArcoListEditor {
constructor() {
this.root = null;
@@ -48,6 +50,7 @@ class ArcoListEditor {
{
this.currentValue = value;
@@ -86,19 +89,23 @@ class ArcoListEditor {
}
isEditorElement(target) {
- // cascader创建时时在cavas后追加一个dom,而popup append在body尾部。不论popup还是dom,都应该被认为是点击到了editor区域
+ // The popup is appended to body and is outside the editor container.
+ // Clicking the popup should still be treated as clicking inside the editor.
return this.element.contains(target) || this.isClickPopUp(target);
}
isClickPopUp(target) {
while (target) {
- if (target.classList && target.classList.contains('arco-select-vtable')) {
+ if (
+ target.classList &&
+ (target.classList.contains(selectEditorPopupClassName) || target.classList.contains('arco-select-vtable'))
+ ) {
return true;
}
- // 如果到达了DOM树的顶部,则停止搜索
+ // Stop searching when reaching the top of the DOM tree.
target = target.parentNode;
}
- // 如果遍历结束也没有找到符合条件的父元素,则返回false
+ // Return false if no matching parent element is found.
return false;
}
}
diff --git a/docs/assets/demo-react/zh/functional-components/arco-select-editor.md b/docs/assets/demo-react/zh/functional-components/arco-select-editor.md
index c4edc624b4..9b9d4abfb4 100644
--- a/docs/assets/demo-react/zh/functional-components/arco-select-editor.md
+++ b/docs/assets/demo-react/zh/functional-components/arco-select-editor.md
@@ -20,6 +20,8 @@ https://arco.design/react/components/select
```javascript livedemo template=vtable-react
// import * as ReactVTable from '@visactor/react-vtable';
+const selectEditorPopupClassName = 'vtable-editor-select-popup';
+
class ArcoListEditor {
constructor() {
this.root = null;
@@ -48,6 +50,7 @@ class ArcoListEditor {
{
this.currentValue = value;
@@ -86,13 +89,16 @@ class ArcoListEditor {
}
isEditorElement(target) {
- // cascader创建时时在cavas后追加一个dom,而popup append在body尾部。不论popup还是dom,都应该被认为是点击到了editor区域
+ // popup append 在 body 尾部,不在编辑器容器内;点击 popup 时也应该被认为仍在编辑器区域。
return this.element.contains(target) || this.isClickPopUp(target);
}
isClickPopUp(target) {
while (target) {
- if (target.classList && target.classList.contains('arco-select-vtable')) {
+ if (
+ target.classList &&
+ (target.classList.contains(selectEditorPopupClassName) || target.classList.contains('arco-select-vtable'))
+ ) {
return true;
}
// 如果到达了DOM树的顶部,则停止搜索
diff --git a/docs/assets/demo-vue/en/edit-data/arco-select-editor.md b/docs/assets/demo-vue/en/edit-data/arco-select-editor.md
index 6d830f64f7..f58df2d8d5 100644
--- a/docs/assets/demo-vue/en/edit-data/arco-select-editor.md
+++ b/docs/assets/demo-vue/en/edit-data/arco-select-editor.md
@@ -19,6 +19,8 @@ https://arco.design/vue/components/select
## Code Demonstration
```javascript livedemo template=vtable-vue
+const selectEditorPopupClassName = 'vtable-editor-select-popup';
+
class ArcoListEditor {
root = null;
element = null;
@@ -64,6 +66,9 @@ class ArcoListEditor {
style: { height: '32px' },
placeholder: 'Select city',
modelValue: this.currentValue,
+ triggerProps: {
+ contentClass: selectEditorPopupClassName
+ },
'onUpdate:modelValue': value => {
this.currentValue = value;
self.setValue(value);
@@ -110,12 +115,18 @@ class ArcoListEditor {
}
isEditorElement(target) {
+ // The popup is appended to body and is outside the editor container.
+ // Clicking the popup should still be treated as clicking inside the editor.
return this.element?.contains(target) || this.isClickPopUp(target);
}
isClickPopUp(target) {
while (target) {
- if (target.classList && target.classList.contains('arco-select-vtable')) {
+ if (
+ target.classList &&
+ (target.classList.contains(selectEditorPopupClassName) ||
+ target.classList.contains('arco-select-vtable'))
+ ) {
return true;
}
target = target.parentNode;
diff --git a/docs/assets/demo-vue/zh/edit-data/arco-select-editor.md b/docs/assets/demo-vue/zh/edit-data/arco-select-editor.md
index c4e66c1264..e1c0a2f5cf 100644
--- a/docs/assets/demo-vue/zh/edit-data/arco-select-editor.md
+++ b/docs/assets/demo-vue/zh/edit-data/arco-select-editor.md
@@ -19,6 +19,8 @@ https://arco.design/vue/components/select
## 代码演示
```javascript livedemo template=vtable-vue
+const selectEditorPopupClassName = 'vtable-editor-select-popup';
+
class ArcoListEditor {
root = null;
element = null;
@@ -64,6 +66,9 @@ class ArcoListEditor {
style: { height: '32px' },
placeholder: 'Select city',
modelValue: this.currentValue,
+ triggerProps: {
+ contentClass: selectEditorPopupClassName
+ },
'onUpdate:modelValue': value => {
this.currentValue = value;
self.setValue(value);
@@ -110,12 +115,17 @@ class ArcoListEditor {
}
isEditorElement(target) {
+ // popup append 在 body 尾部,不在编辑器容器内;点击 popup 时也应该被认为仍在编辑器区域。
return this.element?.contains(target) || this.isClickPopUp(target);
}
isClickPopUp(target) {
while (target) {
- if (target.classList && target.classList.contains('arco-select-vtable')) {
+ if (
+ target.classList &&
+ (target.classList.contains(selectEditorPopupClassName) ||
+ target.classList.contains('arco-select-vtable'))
+ ) {
return true;
}
target = target.parentNode;
diff --git a/docs/assets/guide/en/edit/edit_cell.md b/docs/assets/guide/en/edit/edit_cell.md
index a8f2e0d7e6..3b0bbc049e 100644
--- a/docs/assets/guide/en/edit/edit_cell.md
+++ b/docs/assets/guide/en/edit/edit_cell.md
@@ -184,6 +184,35 @@ VTable.register.editor('custom-date', custom_date_editor);
In the above example, we created a custom editor named `DateEditor` and implemented the methods required by the `IEditor` interface. Then, we register the custom editor into the VTable through the `VTable.register.editor` method for use in the table.
+### Handling popup DOM from third-party components
+
+When a custom editor uses third-party components such as Select, Cascader, DatePicker, or Tooltip, the popup DOM may not be mounted inside the editor container. Many component libraries append popups to `body` through portal/teleport. In this case, when the user clicks a popup option, the click target is outside the editor container from VTable's perspective, and the editor may exit too early.
+
+It is recommended to configure a stable class on the popup root element and let `isEditorElement` check both the editor container and the popup DOM:
+
+```ts
+const editorPopupClassName = 'vtable-editor-popup';
+
+isEditorElement(target: HTMLElement) {
+ return this.element.contains(target) || this.isClickPopUp(target);
+}
+
+isClickPopUp(target: HTMLElement) {
+ let current: HTMLElement | null = target;
+ while (current) {
+ if (current.classList?.contains(editorPopupClassName)) {
+ return true;
+ }
+ current = current.parentNode as HTMLElement | null;
+ }
+ return false;
+}
+```
+
+Different component libraries expose different props for popup class names. For example, React Arco Select can use `dropdownMenuClassName`, Vue Arco Select can use `triggerProps.contentClass`, and Ant Design Select can use `classNames.popup.root` or `popupClassName` in older versions. See the React and Vue Arco Select custom editor demos for complete examples.
+
+If the third-party component has a close animation after selection, or if its internal implementation affects the order of `onChange` and blur events, update the editor's current value in the component change callback before calling `endEdit`. This prevents `getValue` from reading the old value.
+
`IEditor` [definition](https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts):
```ts
diff --git a/docs/assets/guide/zh/edit/edit_cell.md b/docs/assets/guide/zh/edit/edit_cell.md
index 131974bee5..9b55dcc85b 100644
--- a/docs/assets/guide/zh/edit/edit_cell.md
+++ b/docs/assets/guide/zh/edit/edit_cell.md
@@ -189,6 +189,35 @@ VTable.register.editor('custom-date', custom_date_editor);
在上面的示例中,我们创建了一个名为`DateEditor`的自定义编辑器,并实现了`IEditor`接口所要求的方法。然后,我们通过`VTable.register.editor`方法将自定义编辑器注册到 VTable 中,以便在表格中使用。
+### 处理第三方组件的弹层 DOM
+
+当自定义编辑器使用 Select、Cascader、DatePicker、Tooltip 等第三方组件时,弹层 DOM 可能不会挂载在编辑器容器内部,而是通过 portal/teleport 追加到 `body`。这种情况下,用户点击弹层选项时,VTable 看到的点击目标不在编辑器容器内,可能会触发编辑器提前结束。
+
+建议为第三方组件的弹层根节点配置一个稳定的 class,并在 `isEditorElement` 中同时判断编辑器容器和弹层 DOM:
+
+```ts
+const editorPopupClassName = 'vtable-editor-popup';
+
+isEditorElement(target: HTMLElement) {
+ return this.element.contains(target) || this.isClickPopUp(target);
+}
+
+isClickPopUp(target: HTMLElement) {
+ let current: HTMLElement | null = target;
+ while (current) {
+ if (current.classList?.contains(editorPopupClassName)) {
+ return true;
+ }
+ current = current.parentNode as HTMLElement | null;
+ }
+ return false;
+}
+```
+
+不同组件库暴露的弹层 class 配置项不同,例如 React Arco Select 可以通过 `dropdownMenuClassName` 配置,Vue Arco Select 可以通过 `triggerProps.contentClass` 配置,Ant Design Select 可以通过 `classNames.popup.root` 或旧版本的 `popupClassName` 配置。完整示例可参考 React 和 Vue 的 Arco Select 自定义编辑器 demo。
+
+如果第三方组件在选中后存在关闭动画,或者 `onChange` 与失焦事件的触发顺序受组件内部实现影响,需要先在组件的变更回调中更新编辑器当前值,再按需调用 `endEdit` 结束编辑,避免 `getValue` 读取到旧值。
+
`IEditor` 接口[定义](https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts):
```ts