Hello!
I’m building a custom table using TipTap’s extension-table
, extension-table-row
, extension-table-cell
and extension-table-header
. It has similar features to a notion table like add row/table buttons around it, as well as a floating button/menu for each row/column, a custom floating selection overlay, and so on.
For these features, I chose to create a custom TableView
class (extending the existing prosemirror’s one, that implements NodeView
) and wrap the main Table node in a few divs, to place these custom elements correctly around it.
Here is how I’m defining it:
export class TableView extends TTTableView {
private tableContainer: HTMLDivElement
private tableOuterWrapper: HTMLDivElement
private tableWrapper: HTMLDivElement
private selectionOverlay: HTMLDivElement
constructor(props: NodeViewRendererProps, options: TableOptions) {
const { node } = props
const { defaultCellMinWidth } = options
super(node, defaultCellMinWidth)
// create containers to hold the table and the add buttons
const tableContainer = document.createElement("div")
tableContainer.classList.add("tableContainer")
const tableOuterWrapper = document.createElement("div")
tableOuterWrapper.classList.add("tableOuterWrapper")
// add the default table to the container
tableOuterWrapper.appendChild(this.dom)
// create the selection overlay view
const selectionOverlay = document.createElement("div")
tableOuterWrapper.appendChild(selectionOverlay)
// add column/row add buttons to the containers
const addColumnButton = document.createElement("button")
addColumnButton.classList.add("addColumnButton")
tableOuterWrapper.appendChild(addColumnButton)
tableContainer.appendChild(tableOuterWrapper)
const addRowButton = document.createElement("button")
addRowButton.classList.add("addRowButton")
tableContainer.appendChild(addRowButton)
// dom at this point is the tableWrapper div
this.tableWrapper = this.dom
this.tableContainer = tableContainer
this.tableOuterWrapper = tableOuterWrapper
this.selectionOverlay = selectionOverlay
// pass everything to the table view dom node
this.dom = tableContainer
}
// ...
}
That said, I currently have an issue with these wrappers, which is that it’s possible to select them and append content, using the mouse and arrow shortcuts inside the table, specifically to the tableWrapper
, which is created by extension-table
. The idea is to never allow the Table node to be selected, deleted, or anything outside to be edited.
To fix it I first tried using the contenteditable
prop but, as it turns out, it breaks some of the feature it has, like cell focusing (extension-focus
), and make is misbehave. I then went to explore the ignoreMutation
function, which I already used before, but I found that it’s not blocking the mutations when returning true
. I’m able to detect a mutation that adds text to these wrappers, but the text node still gets created, so I had to remove it right away.
Here is the function I’m using:
override ignoreMutation(mutation: ViewMutationRecord): boolean {
const target = mutation.target as HTMLElement
const { parentElement } = target
if (
mutation.type === "characterData" &&
(parentElement === this.tableContainer ||
parentElement === this.tableOuterWrapper ||
parentElement === this.tableWrapper ||
parentElement === this.selectionOverlay)
) {
// Remove the text node that was created. Ignoring the mutation should
// be enough, but apparently it's not, so we remove the node manually.
if (parentElement.contains(target)) {
parentElement.removeChild(target)
}
return true
}
// ignore mutations outside table wrappers/containers
if ("closest" in target && !target.closest(`.tableOuterWrapper`)) {
return true
}
return super.ignoreMutation(mutation)
}
My questions are:
Is this the expected behavior of ignoreMutation
? Shouldn’t it block the mutation? What would you recommend in this case to successfully disallow editing these elements?
I am sorry if this is more TipTap related, but since my question is more about ignoreMutation
, I think this is the right place to post.
Thanks in advance.