From c5a7733423cd9cebf19fd023d3aa1c6e5e5b4f58 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 26 Aug 2026 17:04:30 +0800 Subject: [PATCH 1/2] docs: improve third-party component popup handling guidance and demos (#4232, #4213) --- .../arco-select-editor.md | 15 +++++++--- .../arco-select-editor.md | 10 +++++-- .../en/edit-data/arco-select-editor.md | 14 ++++++++- .../zh/edit-data/arco-select-editor.md | 13 ++++++++- docs/assets/guide/en/edit/edit_cell.md | 29 +++++++++++++++++++ docs/assets/guide/zh/edit/edit_cell.md | 29 +++++++++++++++++++ 6 files changed, 102 insertions(+), 8 deletions(-) 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..10d8206404 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: { + class: selectEditorPopupClassName + }, 'onUpdate:modelValue': value => { this.currentValue = value; self.setValue(value); @@ -110,12 +115,19 @@ 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') || + target.classList.contains('arco-select-popup')) + ) { 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..26f27d459b 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: { + class: selectEditorPopupClassName + }, 'onUpdate:modelValue': value => { this.currentValue = value; self.setValue(value); @@ -110,12 +115,18 @@ 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') || + target.classList.contains('arco-select-popup')) + ) { 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..feec4f08a2 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.class`, 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..7cafa9e484 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.class` 配置,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 From b90c3f7d3855476b05804428e8b51936d17aa46e Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 26 Aug 2026 17:30:15 +0800 Subject: [PATCH 2/2] fix: correct Vue Arco Select triggerProps.contentClass and remove broad arco-select-popup check --- docs/assets/demo-vue/en/edit-data/arco-select-editor.md | 5 ++--- docs/assets/demo-vue/zh/edit-data/arco-select-editor.md | 5 ++--- docs/assets/guide/en/edit/edit_cell.md | 2 +- docs/assets/guide/zh/edit/edit_cell.md | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) 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 10d8206404..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 @@ -67,7 +67,7 @@ class ArcoListEditor { placeholder: 'Select city', modelValue: this.currentValue, triggerProps: { - class: selectEditorPopupClassName + contentClass: selectEditorPopupClassName }, 'onUpdate:modelValue': value => { this.currentValue = value; @@ -125,8 +125,7 @@ class ArcoListEditor { if ( target.classList && (target.classList.contains(selectEditorPopupClassName) || - target.classList.contains('arco-select-vtable') || - target.classList.contains('arco-select-popup')) + target.classList.contains('arco-select-vtable')) ) { return true; } 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 26f27d459b..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 @@ -67,7 +67,7 @@ class ArcoListEditor { placeholder: 'Select city', modelValue: this.currentValue, triggerProps: { - class: selectEditorPopupClassName + contentClass: selectEditorPopupClassName }, 'onUpdate:modelValue': value => { this.currentValue = value; @@ -124,8 +124,7 @@ class ArcoListEditor { if ( target.classList && (target.classList.contains(selectEditorPopupClassName) || - target.classList.contains('arco-select-vtable') || - target.classList.contains('arco-select-popup')) + target.classList.contains('arco-select-vtable')) ) { return true; } diff --git a/docs/assets/guide/en/edit/edit_cell.md b/docs/assets/guide/en/edit/edit_cell.md index feec4f08a2..3b0bbc049e 100644 --- a/docs/assets/guide/en/edit/edit_cell.md +++ b/docs/assets/guide/en/edit/edit_cell.md @@ -209,7 +209,7 @@ isClickPopUp(target: HTMLElement) { } ``` -Different component libraries expose different props for popup class names. For example, React Arco Select can use `dropdownMenuClassName`, Vue Arco Select can use `triggerProps.class`, 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. +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. diff --git a/docs/assets/guide/zh/edit/edit_cell.md b/docs/assets/guide/zh/edit/edit_cell.md index 7cafa9e484..9b55dcc85b 100644 --- a/docs/assets/guide/zh/edit/edit_cell.md +++ b/docs/assets/guide/zh/edit/edit_cell.md @@ -214,7 +214,7 @@ isClickPopUp(target: HTMLElement) { } ``` -不同组件库暴露的弹层 class 配置项不同,例如 React Arco Select 可以通过 `dropdownMenuClassName` 配置,Vue Arco Select 可以通过 `triggerProps.class` 配置,Ant Design Select 可以通过 `classNames.popup.root` 或旧版本的 `popupClassName` 配置。完整示例可参考 React 和 Vue 的 Arco Select 自定义编辑器 demo。 +不同组件库暴露的弹层 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` 读取到旧值。