2 * This file originates from https://github.com/ProseMirror/prosemirror-tables
3 * and is hence subject to the MIT license found here:
4 * https://github.com/ProseMirror/prosemirror-menu/blob/master/LICENSE
5 * @copyright Marijn Haverbeke and others
8 import {Plugin, PluginKey} from "prosemirror-state"
9 import {Decoration, DecorationSet} from "prosemirror-view"
15 } from "prosemirror-tables";
17 export const key = new PluginKey("tableColumnResizing")
19 export function columnResizing(options = {}) {
21 handleWidth, cellMinWidth, lastColumnResizable
25 lastColumnResizable: true
28 let plugin = new Plugin({
32 return new ResizeState(-1, false)
40 let pluginState = key.getState(state)
41 return pluginState.activeHandle > -1 ? {class: "resize-cursor"} : null
45 mousemove(view, event) {
46 handleMouseMove(view, event, handleWidth, cellMinWidth, lastColumnResizable)
49 handleMouseLeave(view)
51 mousedown(view, event) {
52 handleMouseDown(view, event, cellMinWidth)
57 let pluginState = key.getState(state)
58 if (pluginState.activeHandle > -1) return handleDecorations(state, pluginState.activeHandle)
68 constructor(activeHandle, dragging) {
69 this.activeHandle = activeHandle
70 this.dragging = dragging
74 let state = this, action = tr.getMeta(key)
75 if (action && action.setHandle != null)
76 return new ResizeState(action.setHandle, null)
77 if (action && action.setDragging !== undefined)
78 return new ResizeState(state.activeHandle, action.setDragging)
79 if (state.activeHandle > -1 && tr.docChanged) {
80 let handle = tr.mapping.map(state.activeHandle, -1)
81 if (!pointsAtCell(tr.doc.resolve(handle))) handle = null
82 state = new ResizeState(handle, state.dragging)
88 function handleMouseMove(view, event, handleWidth, cellMinWidth, lastColumnResizable) {
89 let pluginState = key.getState(view.state)
91 if (!pluginState.dragging) {
92 let target = domCellAround(event.target), cell = -1
94 let {left, right} = target.getBoundingClientRect()
95 if (event.clientX - left <= handleWidth)
96 cell = edgeCell(view, event, "left")
97 else if (right - event.clientX <= handleWidth)
98 cell = edgeCell(view, event, "right")
101 if (cell != pluginState.activeHandle) {
102 if (!lastColumnResizable && cell !== -1) {
103 let $cell = view.state.doc.resolve(cell)
104 let table = $cell.node(-1), map = TableMap.get(table), start = $cell.start(-1)
105 let col = map.colCount($cell.pos - start) + $cell.nodeAfter.attrs.colspan - 1
107 if (col == map.width - 1) {
112 updateHandle(view, cell)
117 function handleMouseLeave(view) {
118 let pluginState = key.getState(view.state)
119 if (pluginState.activeHandle > -1 && !pluginState.dragging) updateHandle(view, -1)
122 function handleMouseDown(view, event, cellMinWidth) {
123 let pluginState = key.getState(view.state)
124 if (pluginState.activeHandle == -1 || pluginState.dragging) return false
126 let cell = view.state.doc.nodeAt(pluginState.activeHandle)
127 let width = currentColWidth(view, pluginState.activeHandle, cell.attrs)
128 view.dispatch(view.state.tr.setMeta(key, {setDragging: {startX: event.clientX, startWidth: width}}))
130 function finish(event) {
131 window.removeEventListener("mouseup", finish)
132 window.removeEventListener("mousemove", move)
133 let pluginState = key.getState(view.state)
134 if (pluginState.dragging) {
135 updateColumnWidth(view, pluginState.activeHandle, draggedWidth(pluginState.dragging, event, cellMinWidth))
136 view.dispatch(view.state.tr.setMeta(key, {setDragging: null}))
140 function move(event) {
141 if (!event.which) return finish(event)
142 let pluginState = key.getState(view.state)
143 let dragged = draggedWidth(pluginState.dragging, event, cellMinWidth)
144 displayColumnWidth(view, pluginState.activeHandle, dragged, cellMinWidth)
147 window.addEventListener("mouseup", finish)
148 window.addEventListener("mousemove", move)
149 event.preventDefault()
153 function currentColWidth(view, cellPos, {colspan, colwidth}) {
154 let width = colwidth && colwidth[colwidth.length - 1]
155 if (width) return width
156 let dom = view.domAtPos(cellPos)
157 let node = dom.node.childNodes[dom.offset]
158 let domWidth = node.offsetWidth, parts = colspan
159 if (colwidth) for (let i = 0; i < colspan; i++) if (colwidth[i]) {
160 domWidth -= colwidth[i]
163 return domWidth / parts
166 function domCellAround(target) {
167 while (target && target.nodeName != "TD" && target.nodeName != "TH")
168 target = target.classList.contains("ProseMirror") ? null : target.parentNode
172 function edgeCell(view, event, side) {
173 let found = view.posAtCoords({left: event.clientX, top: event.clientY})
174 if (!found) return -1
176 let $cell = cellAround(view.state.doc.resolve(pos))
177 if (!$cell) return -1
178 if (side == "right") return $cell.pos
179 let map = TableMap.get($cell.node(-1)), start = $cell.start(-1)
180 let index = map.map.indexOf($cell.pos - start)
181 return index % map.width == 0 ? -1 : start + map.map[index - 1]
184 function draggedWidth(dragging, event, cellMinWidth) {
185 let offset = event.clientX - dragging.startX
186 return Math.max(cellMinWidth, dragging.startWidth + offset)
189 function updateHandle(view, value) {
190 view.dispatch(view.state.tr.setMeta(key, {setHandle: value}))
193 function updateColumnWidth(view, cell, width) {
194 let $cell = view.state.doc.resolve(cell);
195 let table = $cell.node(-1);
196 let map = TableMap.get(table);
197 let start = $cell.start(-1);
198 let col = map.colCount($cell.pos - start) + $cell.nodeAfter.attrs.colspan - 1;
199 let tr = view.state.tr;
201 for (let row = 0; row < map.height; row++) {
202 let mapIndex = row * map.width + col;
203 // Rowspanning cell that has already been handled
204 if (row && map.map[mapIndex] == map.map[mapIndex - map.width]) continue
205 let pos = map.map[mapIndex]
206 let {attrs} = table.nodeAt(pos);
207 const newWidth = (attrs.colspan * width) + 'px';
209 tr.setNodeMarkup(start + pos, null, setAttr(attrs, "width", newWidth));
212 if (tr.docChanged) view.dispatch(tr)
215 function displayColumnWidth(view, cell, width, cellMinWidth) {
216 const $cell = view.state.doc.resolve(cell)
217 const table = $cell.node(-1);
218 const start = $cell.start(-1);
219 const col = TableMap.get(table).colCount($cell.pos - start) + $cell.nodeAfter.attrs.colspan - 1
220 let dom = view.domAtPos($cell.start(-1)).node
221 while (dom.nodeName !== "TABLE") {
224 updateColumnsOnResize(view, table, dom, cellMinWidth, col, width)
228 function updateColumnsOnResize(view, tableNode, tableDom, cellMinWidth, overrideCol, overrideValue) {
229 console.log({tableNode, tableDom, cellMinWidth, overrideCol, overrideValue});
231 let fixedWidth = true;
232 const rows = tableDom.querySelectorAll('tr');
234 for (let y = 0; y < rows.length; y++) {
236 const cell = row.children[overrideCol];
237 cell.style.width = `${overrideValue}px`;
239 for (let x = 0; x < row.children.length; x++) {
240 const cell = row.children[x];
241 if (cell.style.width) {
242 const width = Number(cell.style.width.replace('px', ''));
243 totalWidth += width || cellMinWidth;
246 totalWidth += cellMinWidth;
252 console.log(totalWidth);
254 tableDom.style.width = totalWidth + "px"
255 tableDom.style.minWidth = ""
257 tableDom.style.width = ""
258 tableDom.style.minWidth = totalWidth + "px"
264 for (let i = 0; i < n; i++) result.push(0)
268 function handleDecorations(state, cell) {
270 let $cell = state.doc.resolve(cell)
271 let table = $cell.node(-1), map = TableMap.get(table), start = $cell.start(-1)
272 let col = map.colCount($cell.pos - start) + $cell.nodeAfter.attrs.colspan
273 for (let row = 0; row < map.height; row++) {
274 let index = col + row * map.width - 1
275 // For positions that are have either a different cell or the end
276 // of the table to their right, and either the top of the table or
277 // a different cell above them, add a decoration
278 if ((col == map.width || map.map[index] != map.map[index + 1]) &&
279 (row == 0 || map.map[index - 1] != map.map[index - 1 - map.width])) {
280 let cellPos = map.map[index]
281 let pos = start + cellPos + table.nodeAt(cellPos).nodeSize - 1
282 let dom = document.createElement("div")
283 dom.className = "column-resize-handle"
284 decorations.push(Decoration.widget(pos, dom))
287 return DecorationSet.create(state.doc, decorations)