pref: resolve horizontal scrollbar display issue in table (#5191)

#### What type of PR is this?

/kind improvement

#### What this PR does / why we need it:

为溢出的表格增加鼠标滚动功能,当光标在溢出的表格上时,滚动鼠标则可以使表格左右滚动。
当表格可以左右滚动时,增加侧边阴影用于提示用户。

#### How to test it?

测试具有滚动条的表格是否有侧边阴影用于提示用户,并且用鼠标滚动是否可以使表格左右滚动。

#### Which issue(s) this PR fixes:

Fixes #5182 

#### Does this PR introduce a user-facing change?
```release-note
优化富文本编辑器中表格组件可滚动时的显示效果
```
pull/5250/head
Takagi 2024-01-25 11:05:50 +08:00 committed by GitHub
parent 38465253c8
commit 0faa8a89ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 119 additions and 9 deletions

View File

@ -8,12 +8,16 @@ import {
type Range,
mergeAttributes,
isNodeActive,
} from "@/tiptap/vue-3";
CoreEditor,
} from "@/tiptap";
import {
type Node as ProseMirrorNode,
type NodeView,
type EditorState,
type DOMOutputSpec,
Plugin,
DecorationSet,
Decoration,
} from "@/tiptap/pm";
import TableCell from "./table-cell";
import TableRow from "./table-row";
@ -94,6 +98,8 @@ function updateColumns(
}
}
let editor: CoreEditor | undefined = undefined;
class TableView implements NodeView {
node: ProseMirrorNode;
@ -109,15 +115,33 @@ class TableView implements NodeView {
contentDOM: HTMLElement;
containerDOM: HTMLElement;
constructor(node: ProseMirrorNode, cellMinWidth: number) {
this.node = node;
this.cellMinWidth = cellMinWidth;
this.dom = document.createElement("div");
this.dom.className = "tableWrapper";
this.dom.className = "table-container";
this.containerDOM = this.dom.appendChild(document.createElement("div"));
this.containerDOM.className = "tableWrapper";
this.containerDOM.addEventListener("wheel", (e) => {
return this.handleHorizontalWheel(this.containerDOM, e);
});
this.containerDOM.addEventListener("scroll", () => {
if (!editor) {
return false;
}
const { state, view } = editor;
const { tr } = state;
view.dispatch(tr);
return false;
});
this.scrollDom = document.createElement("div");
this.scrollDom.className = "scrollWrapper";
this.dom.appendChild(this.scrollDom);
this.containerDOM.appendChild(this.scrollDom);
this.table = this.scrollDom.appendChild(document.createElement("table"));
this.colgroup = this.table.appendChild(document.createElement("colgroup"));
@ -132,7 +156,6 @@ class TableView implements NodeView {
this.node = node;
updateColumns(node, this.colgroup, this.table, this.cellMinWidth);
return true;
}
@ -145,6 +168,16 @@ class TableView implements NodeView {
this.colgroup.contains(mutation.target))
);
}
handleHorizontalWheel(dom: HTMLElement, event: WheelEvent) {
const { scrollWidth, clientWidth } = dom;
const hasScrollWidth = scrollWidth > clientWidth;
if (hasScrollWidth) {
event.stopPropagation();
event.preventDefault();
dom.scrollBy({ left: event.deltaY });
}
}
}
const Table = TiptapTable.extend<ExtensionOptions & TableOptions>({
@ -450,6 +483,52 @@ const Table = TiptapTable.extend<ExtensionOptions & TableOptions>({
return table;
},
onTransaction() {
editor = this.editor;
},
addProseMirrorPlugins() {
const plugins = this.parent?.() ?? [];
return [
...plugins,
new Plugin({
props: {
decorations: (state) => {
const { doc, tr } = state;
const decorations: Decoration[] = [];
doc.descendants((node, pos) => {
if (node.type.name === Table.name) {
const { view } = this.editor;
const nodeDom = view.nodeDOM(pos) || view.domAtPos(pos)?.node;
if (!nodeDom) {
return true;
}
const { scrollWidth, clientWidth, scrollLeft } =
nodeDom.firstChild as HTMLElement;
let classNames = "";
if (
scrollWidth > clientWidth &&
scrollLeft < scrollWidth - clientWidth
) {
classNames += "table-right-shadow ";
}
if (scrollLeft > 0) {
classNames += "table-left-shadow ";
}
decorations.push(
Decoration.node(pos, pos + node.nodeSize, {
class: classNames,
})
);
}
});
return DecorationSet.create(tr.doc, decorations);
},
},
}),
];
},
}).configure({ resizable: true });
export default Table;

View File

@ -6,11 +6,46 @@
$tableResizeHandleBgColor: #adf;
.ProseMirror {
.table-container {
position: relative;
&.table-right-shadow {
&::after {
bottom: 15px;
background: linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.08));
right: 0;
content: " ";
position: absolute;
pointer-events: none;
top: 0;
width: 8px;
z-index: 2;
margin-top: 27px;
}
}
&.table-left-shadow {
&::before {
bottom: 15px;
background: linear-gradient(-90deg, transparent, rgba(0, 0, 0, 0.08));
left: 0;
content: " ";
position: absolute;
pointer-events: none;
top: 0;
width: 8px;
z-index: 2;
margin-top: 27px;
}
}
}
.tableWrapper {
position: relative;
margin: 0.5em 0px;
overflow-x: auto;
overflow-y: hidden;
cursor: default;
&.has-focus {
.scrollWrapper {
@ -44,6 +79,7 @@
tr {
position: relative;
border-bottom: 1px solid $tableBorderColor;
cursor: text;
}
th {
@ -281,13 +317,8 @@
right: -2px;
bottom: -2px;
width: 4px;
pointer-events: none;
background-color: $tableResizeHandleBgColor;
z-index: 1;
}
&.resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}
}