mirror of https://github.com/halo-dev/halo
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
parent
38465253c8
commit
0faa8a89ff
|
@ -8,12 +8,16 @@ import {
|
||||||
type Range,
|
type Range,
|
||||||
mergeAttributes,
|
mergeAttributes,
|
||||||
isNodeActive,
|
isNodeActive,
|
||||||
} from "@/tiptap/vue-3";
|
CoreEditor,
|
||||||
|
} from "@/tiptap";
|
||||||
import {
|
import {
|
||||||
type Node as ProseMirrorNode,
|
type Node as ProseMirrorNode,
|
||||||
type NodeView,
|
type NodeView,
|
||||||
type EditorState,
|
type EditorState,
|
||||||
type DOMOutputSpec,
|
type DOMOutputSpec,
|
||||||
|
Plugin,
|
||||||
|
DecorationSet,
|
||||||
|
Decoration,
|
||||||
} from "@/tiptap/pm";
|
} from "@/tiptap/pm";
|
||||||
import TableCell from "./table-cell";
|
import TableCell from "./table-cell";
|
||||||
import TableRow from "./table-row";
|
import TableRow from "./table-row";
|
||||||
|
@ -94,6 +98,8 @@ function updateColumns(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let editor: CoreEditor | undefined = undefined;
|
||||||
|
|
||||||
class TableView implements NodeView {
|
class TableView implements NodeView {
|
||||||
node: ProseMirrorNode;
|
node: ProseMirrorNode;
|
||||||
|
|
||||||
|
@ -109,15 +115,33 @@ class TableView implements NodeView {
|
||||||
|
|
||||||
contentDOM: HTMLElement;
|
contentDOM: HTMLElement;
|
||||||
|
|
||||||
|
containerDOM: HTMLElement;
|
||||||
|
|
||||||
constructor(node: ProseMirrorNode, cellMinWidth: number) {
|
constructor(node: ProseMirrorNode, cellMinWidth: number) {
|
||||||
this.node = node;
|
this.node = node;
|
||||||
this.cellMinWidth = cellMinWidth;
|
this.cellMinWidth = cellMinWidth;
|
||||||
this.dom = document.createElement("div");
|
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 = document.createElement("div");
|
||||||
this.scrollDom.className = "scrollWrapper";
|
this.scrollDom.className = "scrollWrapper";
|
||||||
this.dom.appendChild(this.scrollDom);
|
this.containerDOM.appendChild(this.scrollDom);
|
||||||
|
|
||||||
this.table = this.scrollDom.appendChild(document.createElement("table"));
|
this.table = this.scrollDom.appendChild(document.createElement("table"));
|
||||||
this.colgroup = this.table.appendChild(document.createElement("colgroup"));
|
this.colgroup = this.table.appendChild(document.createElement("colgroup"));
|
||||||
|
@ -132,7 +156,6 @@ class TableView implements NodeView {
|
||||||
|
|
||||||
this.node = node;
|
this.node = node;
|
||||||
updateColumns(node, this.colgroup, this.table, this.cellMinWidth);
|
updateColumns(node, this.colgroup, this.table, this.cellMinWidth);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,6 +168,16 @@ class TableView implements NodeView {
|
||||||
this.colgroup.contains(mutation.target))
|
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>({
|
const Table = TiptapTable.extend<ExtensionOptions & TableOptions>({
|
||||||
|
@ -450,6 +483,52 @@ const Table = TiptapTable.extend<ExtensionOptions & TableOptions>({
|
||||||
|
|
||||||
return table;
|
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 });
|
}).configure({ resizable: true });
|
||||||
|
|
||||||
export default Table;
|
export default Table;
|
||||||
|
|
|
@ -6,11 +6,46 @@
|
||||||
$tableResizeHandleBgColor: #adf;
|
$tableResizeHandleBgColor: #adf;
|
||||||
|
|
||||||
.ProseMirror {
|
.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 {
|
.tableWrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0.5em 0px;
|
margin: 0.5em 0px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
&.has-focus {
|
&.has-focus {
|
||||||
.scrollWrapper {
|
.scrollWrapper {
|
||||||
|
@ -44,6 +79,7 @@
|
||||||
tr {
|
tr {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-bottom: 1px solid $tableBorderColor;
|
border-bottom: 1px solid $tableBorderColor;
|
||||||
|
cursor: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
|
@ -281,13 +317,8 @@
|
||||||
right: -2px;
|
right: -2px;
|
||||||
bottom: -2px;
|
bottom: -2px;
|
||||||
width: 4px;
|
width: 4px;
|
||||||
pointer-events: none;
|
|
||||||
background-color: $tableResizeHandleBgColor;
|
background-color: $tableResizeHandleBgColor;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
|
||||||
|
|
||||||
&.resize-cursor {
|
|
||||||
cursor: ew-resize;
|
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue