Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -48,6 +50,7 @@ class ArcoListEditor {
<div>
<ArcoDesign.Select
placeholder="Select city"
dropdownMenuClassName={selectEditorPopupClassName}
defaultValue={defaultValue}
onChange={value => {
this.currentValue = value;
Expand Down Expand Up @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -48,6 +50,7 @@ class ArcoListEditor {
<div>
<ArcoDesign.Select
placeholder="Select city"
dropdownMenuClassName={selectEditorPopupClassName}
defaultValue={defaultValue}
onChange={value => {
this.currentValue = value;
Expand Down Expand Up @@ -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树的顶部,则停止搜索
Expand Down
13 changes: 12 additions & 1 deletion docs/assets/demo-vue/en/edit-data/arco-select-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
12 changes: 11 additions & 1 deletion docs/assets/demo-vue/zh/edit-data/arco-select-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
29 changes: 29 additions & 0 deletions docs/assets/guide/en/edit/edit_cell.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 29 additions & 0 deletions docs/assets/guide/zh/edit/edit_cell.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading