@domternal/extension-table 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +55 -0
- package/dist/index.cjs +1734 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +273 -0
- package/dist/index.d.ts +273 -0
- package/dist/index.js +1719 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1734 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@domternal/core');
|
|
4
|
+
var state = require('@domternal/pm/state');
|
|
5
|
+
var tables = require('@domternal/pm/tables');
|
|
6
|
+
var view = require('@domternal/pm/view');
|
|
7
|
+
|
|
8
|
+
// src/Table.ts
|
|
9
|
+
function getTableInfo(state) {
|
|
10
|
+
const $from = state.selection.$from;
|
|
11
|
+
for (let d = $from.depth; d > 0; d--) {
|
|
12
|
+
const node = $from.node(d);
|
|
13
|
+
if (node.type.name !== "table") continue;
|
|
14
|
+
const tableStart = $from.start(d);
|
|
15
|
+
const map = tables.TableMap.get(node);
|
|
16
|
+
const oldWidths = [];
|
|
17
|
+
let allFrozen = true;
|
|
18
|
+
for (let col = 0; col < map.width; col++) {
|
|
19
|
+
const cellOffset = map.map[col] ?? 0;
|
|
20
|
+
const cell = node.nodeAt(cellOffset);
|
|
21
|
+
if (!cell) {
|
|
22
|
+
allFrozen = false;
|
|
23
|
+
oldWidths.push(0);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const colInCell = col - map.colCount(cellOffset);
|
|
27
|
+
const colwidth = cell.attrs["colwidth"];
|
|
28
|
+
const w = colwidth?.[colInCell];
|
|
29
|
+
if (w) {
|
|
30
|
+
oldWidths.push(w);
|
|
31
|
+
} else {
|
|
32
|
+
allFrozen = false;
|
|
33
|
+
oldWidths.push(0);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { tableStart, oldWidths, allFrozen };
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function findTableDom(view, tableStart) {
|
|
41
|
+
try {
|
|
42
|
+
let node = view.domAtPos(tableStart).node;
|
|
43
|
+
while (node && node.nodeName !== "TABLE") {
|
|
44
|
+
node = node.parentNode;
|
|
45
|
+
}
|
|
46
|
+
return node;
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function getContainerWidth(view, tableStart) {
|
|
52
|
+
const tableDom = findTableDom(view, tableStart);
|
|
53
|
+
const wrapper = tableDom?.closest(".tableWrapper");
|
|
54
|
+
if (wrapper) return Math.floor(wrapper.getBoundingClientRect().width) - 1;
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
function redistributeColumns(tr, tableStartPos, targetWidth, cellMinWidth) {
|
|
58
|
+
const $pos = tr.doc.resolve(tableStartPos);
|
|
59
|
+
let tableDepth = -1;
|
|
60
|
+
for (let d = $pos.depth; d > 0; d--) {
|
|
61
|
+
if ($pos.node(d).type.name === "table") {
|
|
62
|
+
tableDepth = d;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (tableDepth === -1) return;
|
|
67
|
+
const table = $pos.node(tableDepth);
|
|
68
|
+
const tableStart = $pos.start(tableDepth);
|
|
69
|
+
const map = tables.TableMap.get(table);
|
|
70
|
+
const colCount = map.width;
|
|
71
|
+
const baseWidth = Math.max(cellMinWidth, Math.floor(targetWidth / colCount));
|
|
72
|
+
const newWidths = new Array(colCount).fill(baseWidth);
|
|
73
|
+
const used = baseWidth * colCount;
|
|
74
|
+
const diff = targetWidth - used;
|
|
75
|
+
if (diff !== 0 && colCount > 0) {
|
|
76
|
+
const lastIdx = colCount - 1;
|
|
77
|
+
newWidths[lastIdx] = Math.max(cellMinWidth, (newWidths[lastIdx] ?? baseWidth) + diff);
|
|
78
|
+
}
|
|
79
|
+
for (let col = 0; col < map.width; col++) {
|
|
80
|
+
const targetW = newWidths[col];
|
|
81
|
+
for (let row = 0; row < map.height; row++) {
|
|
82
|
+
const mapIndex = row * map.width + col;
|
|
83
|
+
if (row > 0 && map.map[mapIndex] === map.map[mapIndex - map.width]) continue;
|
|
84
|
+
const pos = map.map[mapIndex] ?? 0;
|
|
85
|
+
const cellNode = table.nodeAt(pos);
|
|
86
|
+
if (!cellNode) continue;
|
|
87
|
+
const colspan = cellNode.attrs["colspan"] || 1;
|
|
88
|
+
const index = colspan === 1 ? 0 : col - map.colCount(pos);
|
|
89
|
+
const colwidth = cellNode.attrs["colwidth"];
|
|
90
|
+
if (colwidth?.[index] === targetW) continue;
|
|
91
|
+
const newColwidth = colwidth ? colwidth.slice() : new Array(colspan).fill(0);
|
|
92
|
+
newColwidth[index] = targetW;
|
|
93
|
+
tr.setNodeMarkup(tableStart + pos, null, { ...cellNode.attrs, colwidth: newColwidth });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function constrainedAddColumn(pmCommand, view, cellMinWidth, defaultCellMinWidth) {
|
|
98
|
+
const state = view.state;
|
|
99
|
+
const info = getTableInfo(state);
|
|
100
|
+
if (!info?.allFrozen) {
|
|
101
|
+
return pmCommand(state, view.dispatch.bind(view));
|
|
102
|
+
}
|
|
103
|
+
const containerWidth = getContainerWidth(view, info.tableStart);
|
|
104
|
+
if (containerWidth <= 0) {
|
|
105
|
+
return pmCommand(state, view.dispatch.bind(view));
|
|
106
|
+
}
|
|
107
|
+
const oldTotal = info.oldWidths.reduce((a, b) => a + b, 0);
|
|
108
|
+
if (oldTotal + defaultCellMinWidth <= containerWidth) {
|
|
109
|
+
return pmCommand(state, view.dispatch.bind(view));
|
|
110
|
+
}
|
|
111
|
+
let captured;
|
|
112
|
+
pmCommand(state, (tr) => {
|
|
113
|
+
captured = tr;
|
|
114
|
+
});
|
|
115
|
+
if (!captured) return false;
|
|
116
|
+
redistributeColumns(captured, info.tableStart, Math.min(oldTotal, containerWidth), cellMinWidth);
|
|
117
|
+
view.dispatch(captured);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/icons.ts
|
|
122
|
+
var DOTS_H = '<svg width="10" height="4" viewBox="0 0 10 4"><circle cx="2" cy="2" r="1.2" fill="currentColor"/><circle cx="5" cy="2" r="1.2" fill="currentColor"/><circle cx="8" cy="2" r="1.2" fill="currentColor"/></svg>';
|
|
123
|
+
var DOTS_V = '<svg width="4" height="10" viewBox="0 0 4 10"><circle cx="2" cy="2" r="1.2" fill="currentColor"/><circle cx="2" cy="5" r="1.2" fill="currentColor"/><circle cx="2" cy="8" r="1.2" fill="currentColor"/></svg>';
|
|
124
|
+
var CHEVRON_DOWN = '<svg width="8" height="8" viewBox="0 0 8 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M1 2.5l3 3 3-3"/></svg>';
|
|
125
|
+
var ICON_COLOR = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"><path d="M200.77,53.89A103.27,103.27,0,0,0,128,24h-1.07A104,104,0,0,0,24,128c0,43,26.58,79.06,69.36,94.17A32,32,0,0,0,136,192a16,16,0,0,1,16-16h46.21a31.81,31.81,0,0,0,31.2-24.88,104.43,104.43,0,0,0,2.59-24A103.28,103.28,0,0,0,200.77,53.89Zm13,93.71A15.89,15.89,0,0,1,198.21,160H152a32,32,0,0,0-32,32,16,16,0,0,1-21.31,15.07C62.49,194.3,40,164,40,128a88,88,0,0,1,87.09-88h.9a88.35,88.35,0,0,1,88,87.25A88.86,88.86,0,0,1,213.81,147.6ZM140,76a12,12,0,1,1-12-12A12,12,0,0,1,140,76ZM96,100A12,12,0,1,1,84,88,12,12,0,0,1,96,100Zm0,56a12,12,0,1,1-12-12A12,12,0,0,1,96,156Zm88-56a12,12,0,1,1-12-12A12,12,0,0,1,184,100Z"/></svg>';
|
|
126
|
+
var ICON_ALIGNMENT = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"><path d="M32,64a8,8,0,0,1,8-8H216a8,8,0,0,1,0,16H40A8,8,0,0,1,32,64Zm8,48H168a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16Zm176,24H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm-48,40H40a8,8,0,0,0,0,16H168a8,8,0,0,0,0-16Z"/></svg>';
|
|
127
|
+
var ICON_HEADER = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"><path d="M208,32H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32Zm0,176H48V48H208V208ZM72,96V80a8,8,0,0,1,16,0V96a8,8,0,0,1-16,0Zm96,0V80a8,8,0,0,1,16,0V96a8,8,0,0,1-16,0Zm-48,0V80a8,8,0,0,1,16,0V96a8,8,0,0,1-16,0Z"/></svg>';
|
|
128
|
+
var ICON_MERGE = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"><path d="M200,40H56A16,16,0,0,0,40,56V200a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V56A16,16,0,0,0,200,40Zm0,16V96H136V56ZM56,56h64V96H56ZM56,200V112H200v88Z"/></svg>';
|
|
129
|
+
var ICON_SPLIT = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"><path d="M200,40H56A16,16,0,0,0,40,56V200a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V56A16,16,0,0,0,200,40Zm0,16V96H136V56ZM56,56h64V96H56ZM56,136V112h64v24Zm0,64V152h64v48Zm144,0H136V152h64Zm0-48H136V112h64Z"/></svg>';
|
|
130
|
+
var ICON_ALIGN_LEFT = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"><path d="M32,64a8,8,0,0,1,8-8H216a8,8,0,0,1,0,16H40A8,8,0,0,1,32,64Zm8,48H168a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16Zm176,24H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm-48,40H40a8,8,0,0,0,0,16H168a8,8,0,0,0,0-16Z"/></svg>';
|
|
131
|
+
var ICON_ALIGN_CENTER = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"><path d="M32,64a8,8,0,0,1,8-8H216a8,8,0,0,1,0,16H40A8,8,0,0,1,32,64ZM64,96a8,8,0,0,0,0,16H192a8,8,0,0,0,0-16Zm152,40H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm-24,40H64a8,8,0,0,0,0,16H192a8,8,0,0,0,0-16Z"/></svg>';
|
|
132
|
+
var ICON_ALIGN_RIGHT = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"><path d="M32,64a8,8,0,0,1,8-8H216a8,8,0,0,1,0,16H40A8,8,0,0,1,32,64ZM216,96H88a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm0,40H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm0,40H88a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Z"/></svg>';
|
|
133
|
+
var ICON_ALIGN_TOP = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"><path d="M40,40H216a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16Zm88,16a8,8,0,0,0-8,8v96a8,8,0,0,0,16,0V64A8,8,0,0,0,128,56Zm40,16a8,8,0,0,0-8,8v64a8,8,0,0,0,16,0V80A8,8,0,0,0,168,72ZM88,72a8,8,0,0,0-8,8v64a8,8,0,0,0,16,0V80A8,8,0,0,0,88,72Z"/></svg>';
|
|
134
|
+
var ICON_ALIGN_MIDDLE = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"><path d="M40,136H216a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16Zm88-80a8,8,0,0,0-8,8v32a8,8,0,0,0,16,0V64A8,8,0,0,0,128,56Zm0,104a8,8,0,0,0-8,8v32a8,8,0,0,0,16,0V168A8,8,0,0,0,128,160Zm40-80a8,8,0,0,0-8,8v16a8,8,0,0,0,16,0V88A8,8,0,0,0,168,80Zm0,56a8,8,0,0,0-8,8v16a8,8,0,0,0,16,0V144A8,8,0,0,0,168,136ZM88,80a8,8,0,0,0-8,8v16a8,8,0,0,0,16,0V88A8,8,0,0,0,88,80Zm0,56a8,8,0,0,0-8,8v16a8,8,0,0,0,16,0V144A8,8,0,0,0,88,136Z"/></svg>';
|
|
135
|
+
var ICON_ALIGN_BOTTOM = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"><path d="M40,232H216a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16Zm88-32a8,8,0,0,0,8-8V96a8,8,0,0,0-16,0v96A8,8,0,0,0,128,200Zm40-16a8,8,0,0,0,8-8V112a8,8,0,0,0-16,0v64A8,8,0,0,0,168,184ZM88,184a8,8,0,0,0,8-8V112a8,8,0,0,0-16,0v64A8,8,0,0,0,88,184Z"/></svg>';
|
|
136
|
+
var CELL_ICON = '<svg width="8" height="8" viewBox="0 0 8 8"><circle cx="2" cy="2" r="1.2" fill="currentColor"/><circle cx="6" cy="2" r="1.2" fill="currentColor"/><circle cx="2" cy="6" r="1.2" fill="currentColor"/><circle cx="6" cy="6" r="1.2" fill="currentColor"/></svg>';
|
|
137
|
+
var ICON_ROW_PLUS_TOP = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"><path d="M208,160H48a16,16,0,0,0-16,16v24a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V176A16,16,0,0,0,208,160Zm0,40H48V176H208v24Zm0-112H48a16,16,0,0,0-16,16v24a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V104A16,16,0,0,0,208,88Zm0,40H48V104H208v24ZM96,40a8,8,0,0,1,8-8h16V16a8,8,0,0,1,16,0V32h16a8,8,0,0,1,0,16H136V64a8,8,0,0,1-16,0V48H104A8,8,0,0,1,96,40Z"/></svg>';
|
|
138
|
+
var ICON_ROW_PLUS_BOTTOM = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"><path d="M208,112H48a16,16,0,0,0-16,16v24a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V128A16,16,0,0,0,208,112Zm0,40H48V128H208v24Zm0-112H48A16,16,0,0,0,32,56V80A16,16,0,0,0,48,96H208a16,16,0,0,0,16-16V56A16,16,0,0,0,208,40Zm0,40H48V56H208V80ZM160,216a8,8,0,0,1-8,8H136v16a8,8,0,0,1-16,0V224H104a8,8,0,0,1,0-16h16V192a8,8,0,0,1,16,0v16h16A8,8,0,0,1,160,216Z"/></svg>';
|
|
139
|
+
var ICON_DELETE_ROW = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"><path d="M208,136H48a16,16,0,0,0-16,16v40a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V152A16,16,0,0,0,208,136Zm0,56H48V152H208v40Zm0-144H48A16,16,0,0,0,32,64v40a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V64A16,16,0,0,0,208,48Zm0,56H48V64H208v40Z"/></svg>';
|
|
140
|
+
var ICON_COL_PLUS_LEFT = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"><path d="M128,32H104A16,16,0,0,0,88,48V208a16,16,0,0,0,16,16h24a16,16,0,0,0,16-16V48A16,16,0,0,0,128,32Zm0,176H104V48h24ZM200,32H176a16,16,0,0,0-16,16V208a16,16,0,0,0,16,16h24a16,16,0,0,0,16-16V48A16,16,0,0,0,200,32Zm0,176H176V48h24ZM72,128a8,8,0,0,1-8,8H48v16a8,8,0,0,1-16,0V136H16a8,8,0,0,1,0-16H32V104a8,8,0,0,1,16,0v16H64A8,8,0,0,1,72,128Z"/></svg>';
|
|
141
|
+
var ICON_COL_PLUS_RIGHT = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"><path d="M80,32H56A16,16,0,0,0,40,48V208a16,16,0,0,0,16,16H80a16,16,0,0,0,16-16V48A16,16,0,0,0,80,32Zm0,176H56V48H80ZM152,32H128a16,16,0,0,0-16,16V208a16,16,0,0,0,16,16h24a16,16,0,0,0,16-16V48A16,16,0,0,0,152,32Zm0,176H128V48h24Zm96-80a8,8,0,0,1-8,8H224v16a8,8,0,0,1-16,0V136H192a8,8,0,0,1,0-16h16V104a8,8,0,0,1,16,0v16h16A8,8,0,0,1,248,128Z"/></svg>';
|
|
142
|
+
var ICON_DELETE_COL = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"><path d="M104,32H64A16,16,0,0,0,48,48V208a16,16,0,0,0,16,16h40a16,16,0,0,0,16-16V48A16,16,0,0,0,104,32Zm0,176H64V48h40ZM192,32H152a16,16,0,0,0-16,16V208a16,16,0,0,0,16,16h40a16,16,0,0,0,16-16V48A16,16,0,0,0,192,32Zm0,176H152V48h40Z"/></svg>';
|
|
143
|
+
var CELL_COLORS = [
|
|
144
|
+
// Row 1 — Warm highlights
|
|
145
|
+
"#fef08a",
|
|
146
|
+
"#fed7aa",
|
|
147
|
+
"#fecaca",
|
|
148
|
+
"#fbcfe8",
|
|
149
|
+
// Row 2 — Cool highlights
|
|
150
|
+
"#a7f3d0",
|
|
151
|
+
"#a5f3fc",
|
|
152
|
+
"#bfdbfe",
|
|
153
|
+
"#c4b5fd",
|
|
154
|
+
// Row 3 — Light pastels
|
|
155
|
+
"#fef9c3",
|
|
156
|
+
"#ffedd5",
|
|
157
|
+
"#dbeafe",
|
|
158
|
+
"#ede9fe",
|
|
159
|
+
// Row 4 — Neutrals
|
|
160
|
+
"#e5e7eb",
|
|
161
|
+
"#d1d5db",
|
|
162
|
+
"#f3f4f6",
|
|
163
|
+
"#ffffff"
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
// src/TableView.ts
|
|
167
|
+
var tableViewMap = /* @__PURE__ */ new WeakMap();
|
|
168
|
+
var TableView = class {
|
|
169
|
+
node;
|
|
170
|
+
cellMinWidth;
|
|
171
|
+
defaultCellMinWidth;
|
|
172
|
+
view;
|
|
173
|
+
constrainToContainer;
|
|
174
|
+
dom;
|
|
175
|
+
table;
|
|
176
|
+
colgroup;
|
|
177
|
+
contentDOM;
|
|
178
|
+
wrapper;
|
|
179
|
+
colHandle;
|
|
180
|
+
rowHandle;
|
|
181
|
+
cellToolbar;
|
|
182
|
+
colorBtn = null;
|
|
183
|
+
alignBtn = null;
|
|
184
|
+
mergeBtn = null;
|
|
185
|
+
splitBtn = null;
|
|
186
|
+
headerBtn = null;
|
|
187
|
+
cellHandle;
|
|
188
|
+
cellHandleCell = null;
|
|
189
|
+
dropdown = null;
|
|
190
|
+
/** When true, the plugin skips showing the cell toolbar (row/col dropdown is open). */
|
|
191
|
+
suppressCellToolbar = false;
|
|
192
|
+
/** When true, column resize drag is active — all handles/menus are hidden. */
|
|
193
|
+
_resizeDragging = false;
|
|
194
|
+
hoveredCell = null;
|
|
195
|
+
hoveredRow = -1;
|
|
196
|
+
hoveredCol = -1;
|
|
197
|
+
hideTimeout = null;
|
|
198
|
+
// Bound handlers for cleanup
|
|
199
|
+
boundMouseMove;
|
|
200
|
+
boundMouseLeave;
|
|
201
|
+
boundCancelHide;
|
|
202
|
+
boundDocMouseDown;
|
|
203
|
+
boundDocKeyDown;
|
|
204
|
+
boundScroll;
|
|
205
|
+
constructor(node, cellMinWidth, view, defaultCellMinWidth = 100, constrainToContainer = true) {
|
|
206
|
+
this.node = node;
|
|
207
|
+
this.cellMinWidth = cellMinWidth;
|
|
208
|
+
this.defaultCellMinWidth = defaultCellMinWidth;
|
|
209
|
+
this.view = view;
|
|
210
|
+
this.constrainToContainer = constrainToContainer;
|
|
211
|
+
this.boundMouseMove = this.onMouseMove.bind(this);
|
|
212
|
+
this.boundMouseLeave = this.onMouseLeave.bind(this);
|
|
213
|
+
this.boundCancelHide = this.cancelHide.bind(this);
|
|
214
|
+
this.boundDocMouseDown = this.onDocMouseDown.bind(this);
|
|
215
|
+
this.boundDocKeyDown = this.onDocKeyDown.bind(this);
|
|
216
|
+
this.boundScroll = () => {
|
|
217
|
+
this.closeDropdown();
|
|
218
|
+
};
|
|
219
|
+
this.dom = document.createElement("div");
|
|
220
|
+
this.dom.className = "dm-table-container";
|
|
221
|
+
tableViewMap.set(this.dom, this);
|
|
222
|
+
this.colHandle = this.createHandle("dm-table-col-handle", "Column options", DOTS_H);
|
|
223
|
+
this.colHandle.addEventListener("click", (e) => {
|
|
224
|
+
e.stopPropagation();
|
|
225
|
+
this.onColClick();
|
|
226
|
+
});
|
|
227
|
+
this.dom.appendChild(this.colHandle);
|
|
228
|
+
this.rowHandle = this.createHandle("dm-table-row-handle", "Row options", DOTS_V);
|
|
229
|
+
this.rowHandle.addEventListener("click", (e) => {
|
|
230
|
+
e.stopPropagation();
|
|
231
|
+
this.onRowClick();
|
|
232
|
+
});
|
|
233
|
+
this.dom.appendChild(this.rowHandle);
|
|
234
|
+
this.cellToolbar = this.buildCellToolbar();
|
|
235
|
+
this.dom.appendChild(this.cellToolbar);
|
|
236
|
+
this.cellHandle = document.createElement("button");
|
|
237
|
+
this.cellHandle.type = "button";
|
|
238
|
+
this.cellHandle.className = "dm-table-cell-handle";
|
|
239
|
+
this.cellHandle.setAttribute("aria-label", "Cell options");
|
|
240
|
+
this.cellHandle.innerHTML = CELL_ICON;
|
|
241
|
+
this.cellHandle.addEventListener("mousedown", (e) => {
|
|
242
|
+
e.preventDefault();
|
|
243
|
+
e.stopPropagation();
|
|
244
|
+
});
|
|
245
|
+
this.cellHandle.addEventListener("click", (e) => {
|
|
246
|
+
e.stopPropagation();
|
|
247
|
+
this.onCellHandleClick();
|
|
248
|
+
});
|
|
249
|
+
this.dom.appendChild(this.cellHandle);
|
|
250
|
+
this.wrapper = document.createElement("div");
|
|
251
|
+
this.wrapper.className = "tableWrapper";
|
|
252
|
+
this.dom.appendChild(this.wrapper);
|
|
253
|
+
this.table = document.createElement("table");
|
|
254
|
+
this.wrapper.appendChild(this.table);
|
|
255
|
+
this.colgroup = document.createElement("colgroup");
|
|
256
|
+
this.updateColumns(node);
|
|
257
|
+
this.table.appendChild(this.colgroup);
|
|
258
|
+
this.contentDOM = document.createElement("tbody");
|
|
259
|
+
this.table.appendChild(this.contentDOM);
|
|
260
|
+
this.dom.addEventListener("mousemove", this.boundMouseMove);
|
|
261
|
+
this.dom.addEventListener("mouseleave", this.boundMouseLeave);
|
|
262
|
+
this.colHandle.addEventListener("mouseenter", this.boundCancelHide);
|
|
263
|
+
this.rowHandle.addEventListener("mouseenter", this.boundCancelHide);
|
|
264
|
+
this.cellHandle.addEventListener("mouseenter", this.boundCancelHide);
|
|
265
|
+
}
|
|
266
|
+
// ─── NodeView interface ───────────────────────────────────────────────
|
|
267
|
+
update(node) {
|
|
268
|
+
if (node.type !== this.node.type) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
this.node = node;
|
|
272
|
+
this.updateColumns(node);
|
|
273
|
+
this.hoveredCell = null;
|
|
274
|
+
this.hoveredRow = -1;
|
|
275
|
+
this.hoveredCol = -1;
|
|
276
|
+
if (this.cellHandleCell && !this.table.contains(this.cellHandleCell)) {
|
|
277
|
+
this.hideCellHandle();
|
|
278
|
+
}
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
destroy() {
|
|
282
|
+
this.dom.removeEventListener("mousemove", this.boundMouseMove);
|
|
283
|
+
this.dom.removeEventListener("mouseleave", this.boundMouseLeave);
|
|
284
|
+
this.colHandle.removeEventListener("mouseenter", this.boundCancelHide);
|
|
285
|
+
this.rowHandle.removeEventListener("mouseenter", this.boundCancelHide);
|
|
286
|
+
this.cellHandle.removeEventListener("mouseenter", this.boundCancelHide);
|
|
287
|
+
this.closeDropdown();
|
|
288
|
+
if (this.hideTimeout) clearTimeout(this.hideTimeout);
|
|
289
|
+
tableViewMap.delete(this.dom);
|
|
290
|
+
}
|
|
291
|
+
ignoreMutation(mutation) {
|
|
292
|
+
if (mutation.type === "selection") {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
if (mutation.type === "attributes") {
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
if (mutation instanceof MutationRecord && !this.contentDOM.contains(mutation.target)) {
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
// ─── Handle creation ──────────────────────────────────────────────────
|
|
304
|
+
createHandle(className, label, icon) {
|
|
305
|
+
const btn = document.createElement("button");
|
|
306
|
+
btn.className = className;
|
|
307
|
+
btn.type = "button";
|
|
308
|
+
btn.setAttribute("aria-label", label);
|
|
309
|
+
btn.innerHTML = icon;
|
|
310
|
+
btn.addEventListener("mousedown", (e) => {
|
|
311
|
+
e.preventDefault();
|
|
312
|
+
e.stopPropagation();
|
|
313
|
+
});
|
|
314
|
+
return btn;
|
|
315
|
+
}
|
|
316
|
+
// ─── Cell toolbar (floating strip for CellSelection) ──────────────────
|
|
317
|
+
buildCellToolbar() {
|
|
318
|
+
const toolbar = document.createElement("div");
|
|
319
|
+
toolbar.className = "dm-table-cell-toolbar";
|
|
320
|
+
toolbar.addEventListener("mousedown", (e) => {
|
|
321
|
+
e.preventDefault();
|
|
322
|
+
e.stopPropagation();
|
|
323
|
+
});
|
|
324
|
+
this.colorBtn = this.createToolbarButton(ICON_COLOR, "Cell color", CHEVRON_DOWN);
|
|
325
|
+
this.colorBtn.addEventListener("click", (e) => {
|
|
326
|
+
e.stopPropagation();
|
|
327
|
+
if (this.colorBtn) this.showColorDropdown(this.colorBtn);
|
|
328
|
+
});
|
|
329
|
+
toolbar.appendChild(this.colorBtn);
|
|
330
|
+
this.alignBtn = this.createToolbarButton(ICON_ALIGNMENT, "Alignment", CHEVRON_DOWN);
|
|
331
|
+
this.alignBtn.addEventListener("click", (e) => {
|
|
332
|
+
e.stopPropagation();
|
|
333
|
+
if (this.alignBtn) this.showAlignmentDropdown(this.alignBtn);
|
|
334
|
+
});
|
|
335
|
+
toolbar.appendChild(this.alignBtn);
|
|
336
|
+
const sep1 = document.createElement("span");
|
|
337
|
+
sep1.className = "dm-table-cell-toolbar-sep";
|
|
338
|
+
toolbar.appendChild(sep1);
|
|
339
|
+
this.mergeBtn = this.createToolbarButton(ICON_MERGE, "Merge cells");
|
|
340
|
+
this.mergeBtn.addEventListener("click", (e) => {
|
|
341
|
+
e.stopPropagation();
|
|
342
|
+
tables.mergeCells(this.view.state, this.view.dispatch);
|
|
343
|
+
});
|
|
344
|
+
toolbar.appendChild(this.mergeBtn);
|
|
345
|
+
this.splitBtn = this.createToolbarButton(ICON_SPLIT, "Split cell");
|
|
346
|
+
this.splitBtn.addEventListener("click", (e) => {
|
|
347
|
+
e.stopPropagation();
|
|
348
|
+
tables.splitCell(this.view.state, this.view.dispatch);
|
|
349
|
+
});
|
|
350
|
+
toolbar.appendChild(this.splitBtn);
|
|
351
|
+
const sep2 = document.createElement("span");
|
|
352
|
+
sep2.className = "dm-table-cell-toolbar-sep";
|
|
353
|
+
toolbar.appendChild(sep2);
|
|
354
|
+
this.headerBtn = this.createToolbarButton(ICON_HEADER, "Toggle header cell");
|
|
355
|
+
this.headerBtn.addEventListener("click", (e) => {
|
|
356
|
+
e.stopPropagation();
|
|
357
|
+
tables.toggleHeaderCell(this.view.state, this.view.dispatch);
|
|
358
|
+
});
|
|
359
|
+
toolbar.appendChild(this.headerBtn);
|
|
360
|
+
return toolbar;
|
|
361
|
+
}
|
|
362
|
+
createToolbarButton(icon, label, chevron) {
|
|
363
|
+
const btn = document.createElement("button");
|
|
364
|
+
btn.type = "button";
|
|
365
|
+
btn.className = "dm-table-cell-toolbar-btn";
|
|
366
|
+
btn.setAttribute("aria-label", label);
|
|
367
|
+
btn.innerHTML = icon + (chevron ? `<span class="dm-table-cell-toolbar-chevron">${chevron}</span>` : "");
|
|
368
|
+
return btn;
|
|
369
|
+
}
|
|
370
|
+
// ─── Hover tracking ──────────────────────────────────────────────────
|
|
371
|
+
onMouseMove(e) {
|
|
372
|
+
if (this._resizeDragging) return;
|
|
373
|
+
const target = e.target;
|
|
374
|
+
if (!(target instanceof HTMLElement)) return;
|
|
375
|
+
const cell = target.closest("td, th");
|
|
376
|
+
if (!cell || !this.table.contains(cell)) return;
|
|
377
|
+
if (cell === this.hoveredCell) return;
|
|
378
|
+
this.hoveredCell = cell;
|
|
379
|
+
const { row, col } = this.getCellIndices(cell);
|
|
380
|
+
this.hoveredRow = row;
|
|
381
|
+
this.hoveredCol = col;
|
|
382
|
+
this.positionHandles(cell);
|
|
383
|
+
this.showHandles();
|
|
384
|
+
this.cancelHide();
|
|
385
|
+
}
|
|
386
|
+
onMouseLeave() {
|
|
387
|
+
if (this.hideTimeout) clearTimeout(this.hideTimeout);
|
|
388
|
+
this.hideTimeout = setTimeout(() => {
|
|
389
|
+
this.hideHandles();
|
|
390
|
+
this.hoveredCell = null;
|
|
391
|
+
}, 200);
|
|
392
|
+
}
|
|
393
|
+
cancelHide() {
|
|
394
|
+
if (this.hideTimeout) {
|
|
395
|
+
clearTimeout(this.hideTimeout);
|
|
396
|
+
this.hideTimeout = null;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
showHandles() {
|
|
400
|
+
this.colHandle.style.display = "flex";
|
|
401
|
+
this.rowHandle.style.display = "flex";
|
|
402
|
+
}
|
|
403
|
+
hideHandles() {
|
|
404
|
+
if (this.dropdown) return;
|
|
405
|
+
this.colHandle.style.display = "";
|
|
406
|
+
this.rowHandle.style.display = "";
|
|
407
|
+
}
|
|
408
|
+
positionHandles(cell) {
|
|
409
|
+
const containerRect = this.dom.getBoundingClientRect();
|
|
410
|
+
const tableRect = this.table.getBoundingClientRect();
|
|
411
|
+
const cellRect = cell.getBoundingClientRect();
|
|
412
|
+
this.colHandle.style.left = String(cellRect.left - containerRect.left + cellRect.width / 2 - 12) + "px";
|
|
413
|
+
this.colHandle.style.top = String(tableRect.top - containerRect.top - 16) + "px";
|
|
414
|
+
this.rowHandle.style.left = String(tableRect.left - containerRect.left - 16) + "px";
|
|
415
|
+
if (cell.rowSpan > 1) {
|
|
416
|
+
this.rowHandle.style.top = String(cellRect.top - containerRect.top + cellRect.height / 2 - 12) + "px";
|
|
417
|
+
} else {
|
|
418
|
+
const tr = cell.closest("tr");
|
|
419
|
+
if (tr) {
|
|
420
|
+
const trRect = tr.getBoundingClientRect();
|
|
421
|
+
this.rowHandle.style.top = String(trRect.top - containerRect.top + trRect.height / 2 - 12) + "px";
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
getCellIndices(cell) {
|
|
426
|
+
const tr = cell.closest("tr");
|
|
427
|
+
if (!tr) return { row: 0, col: 0 };
|
|
428
|
+
const row = Array.from(this.contentDOM.querySelectorAll("tr")).indexOf(tr);
|
|
429
|
+
let col = 0;
|
|
430
|
+
let sibling = cell.previousElementSibling;
|
|
431
|
+
while (sibling) {
|
|
432
|
+
col += sibling.colSpan || 1;
|
|
433
|
+
sibling = sibling.previousElementSibling;
|
|
434
|
+
}
|
|
435
|
+
return { row, col };
|
|
436
|
+
}
|
|
437
|
+
// ─── Cell toolbar positioning (driven by CellSelection plugin) ────────
|
|
438
|
+
/** Called by the cellHandlePlugin when CellSelection changes. */
|
|
439
|
+
updateCellHandle(active) {
|
|
440
|
+
if (!active || this._resizeDragging) {
|
|
441
|
+
this.cellToolbar.style.display = "";
|
|
442
|
+
this.closeDropdown();
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const selectedCells = this.table.querySelectorAll(".selectedCell");
|
|
446
|
+
if (selectedCells.length === 0) {
|
|
447
|
+
this.cellToolbar.style.display = "";
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
let top = Infinity;
|
|
451
|
+
let left = Infinity;
|
|
452
|
+
let right = -Infinity;
|
|
453
|
+
selectedCells.forEach((c) => {
|
|
454
|
+
const r = c.getBoundingClientRect();
|
|
455
|
+
if (r.top < top) top = r.top;
|
|
456
|
+
if (r.left < left) left = r.left;
|
|
457
|
+
if (r.right > right) right = r.right;
|
|
458
|
+
});
|
|
459
|
+
const containerRect = this.dom.getBoundingClientRect();
|
|
460
|
+
this.cellToolbar.style.display = "flex";
|
|
461
|
+
const toolbarWidth = this.cellToolbar.offsetWidth;
|
|
462
|
+
const selectionCenter = (left + right) / 2;
|
|
463
|
+
let toolbarLeft = selectionCenter - containerRect.left - toolbarWidth / 2;
|
|
464
|
+
toolbarLeft = Math.max(0, Math.min(toolbarLeft, containerRect.width - toolbarWidth));
|
|
465
|
+
this.cellToolbar.style.left = String(toolbarLeft) + "px";
|
|
466
|
+
this.cellToolbar.style.top = String(top - containerRect.top - 36) + "px";
|
|
467
|
+
const canMerge = tables.mergeCells(this.view.state);
|
|
468
|
+
const canSplit = tables.splitCell(this.view.state);
|
|
469
|
+
if (this.mergeBtn) this.mergeBtn.disabled = !canMerge;
|
|
470
|
+
if (this.splitBtn) this.splitBtn.disabled = !canSplit;
|
|
471
|
+
const sel = this.view.state.selection;
|
|
472
|
+
if (sel instanceof tables.CellSelection) {
|
|
473
|
+
let hasCustomAlign = false;
|
|
474
|
+
let hasCustomColor = false;
|
|
475
|
+
let allHeaders = true;
|
|
476
|
+
sel.forEachCell((node) => {
|
|
477
|
+
if (node.attrs["textAlign"] || node.attrs["verticalAlign"]) hasCustomAlign = true;
|
|
478
|
+
if (node.attrs["background"]) hasCustomColor = true;
|
|
479
|
+
if (node.type.name !== "tableHeader") allHeaders = false;
|
|
480
|
+
});
|
|
481
|
+
this.alignBtn?.classList.toggle("dm-table-cell-toolbar-btn--active", hasCustomAlign);
|
|
482
|
+
this.colorBtn?.classList.toggle("dm-table-cell-toolbar-btn--active", hasCustomColor);
|
|
483
|
+
this.headerBtn?.classList.toggle("dm-table-cell-toolbar-btn--active", allHeaders);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
// ─── Cell handle (small circle for single-cell operations) ──────────
|
|
487
|
+
/** Hide all controls during column resize drag. Called by the plugin. */
|
|
488
|
+
hideForResize() {
|
|
489
|
+
this._resizeDragging = true;
|
|
490
|
+
this.cellHandle.style.display = "";
|
|
491
|
+
this.colHandle.style.display = "";
|
|
492
|
+
this.rowHandle.style.display = "";
|
|
493
|
+
this.cellToolbar.style.display = "";
|
|
494
|
+
this.closeDropdown();
|
|
495
|
+
}
|
|
496
|
+
/** Re-enable controls after column resize drag ends. Called by the plugin. */
|
|
497
|
+
showAfterResize() {
|
|
498
|
+
this._resizeDragging = false;
|
|
499
|
+
}
|
|
500
|
+
/** Show cell handle at the top-center of the given cell. Always repositions (no early return). */
|
|
501
|
+
showCellHandle(cell) {
|
|
502
|
+
if (this._resizeDragging) return;
|
|
503
|
+
this.cellHandleCell = cell;
|
|
504
|
+
const containerRect = this.dom.getBoundingClientRect();
|
|
505
|
+
const cellRect = cell.getBoundingClientRect();
|
|
506
|
+
this.cellHandle.style.left = String(cellRect.left - containerRect.left - 7) + "px";
|
|
507
|
+
this.cellHandle.style.top = String(cellRect.top - containerRect.top - 7) + "px";
|
|
508
|
+
this.cellHandle.style.display = "flex";
|
|
509
|
+
}
|
|
510
|
+
/** Hide cell handle. */
|
|
511
|
+
hideCellHandle() {
|
|
512
|
+
this.cellHandle.style.display = "";
|
|
513
|
+
this.cellHandleCell = null;
|
|
514
|
+
}
|
|
515
|
+
/** Click on cell handle → create CellSelection for that cell. */
|
|
516
|
+
onCellHandleClick() {
|
|
517
|
+
if (!this.cellHandleCell) return;
|
|
518
|
+
this.dismissOverlays();
|
|
519
|
+
const pos = this.view.posAtDOM(this.cellHandleCell, 0);
|
|
520
|
+
const $pos = this.view.state.doc.resolve(pos);
|
|
521
|
+
for (let d = $pos.depth; d > 0; d--) {
|
|
522
|
+
const node = $pos.node(d);
|
|
523
|
+
if (node.type.name === "tableCell" || node.type.name === "tableHeader") {
|
|
524
|
+
const cellPos = $pos.before(d);
|
|
525
|
+
const sel = tables.CellSelection.create(this.view.state.doc, cellPos, cellPos);
|
|
526
|
+
this.dispatchCellSelection(sel);
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
// ─── Handle clicks ───────────────────────────────────────────────────
|
|
532
|
+
/** Dismiss other floating overlays (bubble menu, etc.) */
|
|
533
|
+
dismissOverlays() {
|
|
534
|
+
this.dom.closest(".dm-editor")?.dispatchEvent(
|
|
535
|
+
new Event("dm:dismiss-overlays", { bubbles: false })
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
onColClick() {
|
|
539
|
+
this.suppressCellToolbar = true;
|
|
540
|
+
this.cellToolbar.style.display = "";
|
|
541
|
+
this.dismissOverlays();
|
|
542
|
+
this.selectColumn(this.hoveredCol);
|
|
543
|
+
this.showDropdown("column");
|
|
544
|
+
}
|
|
545
|
+
onRowClick() {
|
|
546
|
+
this.suppressCellToolbar = true;
|
|
547
|
+
this.cellToolbar.style.display = "";
|
|
548
|
+
this.dismissOverlays();
|
|
549
|
+
this.selectRow(this.hoveredRow);
|
|
550
|
+
this.showDropdown("row");
|
|
551
|
+
}
|
|
552
|
+
getTablePos() {
|
|
553
|
+
const pos = this.view.posAtDOM(this.table, 0);
|
|
554
|
+
const $pos = this.view.state.doc.resolve(pos);
|
|
555
|
+
for (let d = $pos.depth; d > 0; d--) {
|
|
556
|
+
if ($pos.node(d).type.name === "table") {
|
|
557
|
+
return $pos.before(d);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return pos;
|
|
561
|
+
}
|
|
562
|
+
/** Dispatch a CellSelection and focus the editor. */
|
|
563
|
+
dispatchCellSelection(sel) {
|
|
564
|
+
this.view.dispatch(
|
|
565
|
+
this.view.state.tr.setSelection(sel)
|
|
566
|
+
);
|
|
567
|
+
this.view.focus();
|
|
568
|
+
}
|
|
569
|
+
selectRow(row) {
|
|
570
|
+
const tablePos = this.getTablePos();
|
|
571
|
+
const tableStart = tablePos + 1;
|
|
572
|
+
const map = tables.TableMap.get(this.node);
|
|
573
|
+
if (row < 0 || row >= map.height) return;
|
|
574
|
+
const anchorOffset = map.map[row * map.width];
|
|
575
|
+
const headOffset = map.map[row * map.width + map.width - 1];
|
|
576
|
+
if (anchorOffset === void 0 || headOffset === void 0) return;
|
|
577
|
+
const sel = tables.CellSelection.create(this.view.state.doc, tableStart + anchorOffset, tableStart + headOffset);
|
|
578
|
+
this.dispatchCellSelection(sel);
|
|
579
|
+
}
|
|
580
|
+
selectColumn(col) {
|
|
581
|
+
const tablePos = this.getTablePos();
|
|
582
|
+
const tableStart = tablePos + 1;
|
|
583
|
+
const map = tables.TableMap.get(this.node);
|
|
584
|
+
if (col < 0 || col >= map.width) return;
|
|
585
|
+
const anchorOffset = map.map[col];
|
|
586
|
+
const headOffset = map.map[(map.height - 1) * map.width + col];
|
|
587
|
+
if (anchorOffset === void 0 || headOffset === void 0) return;
|
|
588
|
+
const sel = tables.CellSelection.create(this.view.state.doc, tableStart + anchorOffset, tableStart + headOffset);
|
|
589
|
+
this.dispatchCellSelection(sel);
|
|
590
|
+
}
|
|
591
|
+
setCursorInCell(row, col) {
|
|
592
|
+
const tablePos = this.getTablePos();
|
|
593
|
+
const tableStart = tablePos + 1;
|
|
594
|
+
const map = tables.TableMap.get(this.node);
|
|
595
|
+
if (row < 0 || row >= map.height || col < 0 || col >= map.width) return;
|
|
596
|
+
const cellOffset = map.map[row * map.width + col];
|
|
597
|
+
if (cellOffset === void 0) return;
|
|
598
|
+
const $pos = this.view.state.doc.resolve(tableStart + cellOffset + 1);
|
|
599
|
+
const sel = state.TextSelection.near($pos);
|
|
600
|
+
this.view.dispatch(this.view.state.tr.setSelection(sel));
|
|
601
|
+
}
|
|
602
|
+
// ─── Dropdown ────────────────────────────────────────────────────────
|
|
603
|
+
showDropdown(type) {
|
|
604
|
+
this.closeDropdown();
|
|
605
|
+
const dropdown = document.createElement("div");
|
|
606
|
+
dropdown.className = "dm-table-controls-dropdown";
|
|
607
|
+
dropdown.addEventListener("mouseenter", this.boundCancelHide);
|
|
608
|
+
dropdown.addEventListener("mousedown", (e) => {
|
|
609
|
+
e.preventDefault();
|
|
610
|
+
});
|
|
611
|
+
const items = type === "row" ? [
|
|
612
|
+
{ icon: ICON_ROW_PLUS_TOP, label: "Insert Row Above", action: () => {
|
|
613
|
+
this.execRowCmd(tables.addRowBefore);
|
|
614
|
+
} },
|
|
615
|
+
{ icon: ICON_ROW_PLUS_BOTTOM, label: "Insert Row Below", action: () => {
|
|
616
|
+
this.execRowCmd(tables.addRowAfter);
|
|
617
|
+
} },
|
|
618
|
+
{ icon: ICON_DELETE_ROW, label: "Delete Row", action: () => {
|
|
619
|
+
this.execRowCmd(tables.deleteRow);
|
|
620
|
+
} }
|
|
621
|
+
] : [
|
|
622
|
+
{ icon: ICON_COL_PLUS_LEFT, label: "Insert Column Left", action: () => {
|
|
623
|
+
this.execColCmd(tables.addColumnBefore);
|
|
624
|
+
} },
|
|
625
|
+
{ icon: ICON_COL_PLUS_RIGHT, label: "Insert Column Right", action: () => {
|
|
626
|
+
this.execColCmd(tables.addColumnAfter);
|
|
627
|
+
} },
|
|
628
|
+
{ icon: ICON_DELETE_COL, label: "Delete Column", action: () => {
|
|
629
|
+
this.execColCmd(tables.deleteColumn);
|
|
630
|
+
} }
|
|
631
|
+
];
|
|
632
|
+
for (const item of items) {
|
|
633
|
+
const btn = document.createElement("button");
|
|
634
|
+
btn.type = "button";
|
|
635
|
+
btn.innerHTML = `<span class="dm-table-controls-dropdown-icon">${item.icon}</span>${item.label}`;
|
|
636
|
+
btn.addEventListener("click", (e) => {
|
|
637
|
+
e.stopPropagation();
|
|
638
|
+
item.action();
|
|
639
|
+
this.closeDropdown();
|
|
640
|
+
this.hideHandles();
|
|
641
|
+
});
|
|
642
|
+
dropdown.appendChild(btn);
|
|
643
|
+
}
|
|
644
|
+
const handle = type === "row" ? this.rowHandle : this.colHandle;
|
|
645
|
+
const handleRect = handle.getBoundingClientRect();
|
|
646
|
+
dropdown.style.position = "fixed";
|
|
647
|
+
dropdown.style.left = String(handleRect.left) + "px";
|
|
648
|
+
dropdown.style.top = String(handleRect.bottom + 4) + "px";
|
|
649
|
+
document.body.appendChild(dropdown);
|
|
650
|
+
this.dropdown = dropdown;
|
|
651
|
+
this.addDropdownListeners();
|
|
652
|
+
}
|
|
653
|
+
// ─── Cell toolbar dropdowns ──────────────────────────────────────────
|
|
654
|
+
/** Shared open/toggle for toolbar dropdown buttons. */
|
|
655
|
+
openToolbarDropdown(triggerBtn, className, buildContent) {
|
|
656
|
+
if (this.dropdown && triggerBtn.classList.contains("dm-table-cell-toolbar-btn--open")) {
|
|
657
|
+
this.closeDropdown();
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
this.closeDropdown();
|
|
661
|
+
triggerBtn.classList.add("dm-table-cell-toolbar-btn--open");
|
|
662
|
+
const dropdown = document.createElement("div");
|
|
663
|
+
dropdown.className = className;
|
|
664
|
+
dropdown.addEventListener("mousedown", (e) => {
|
|
665
|
+
e.preventDefault();
|
|
666
|
+
});
|
|
667
|
+
buildContent(dropdown);
|
|
668
|
+
this.positionToolbarDropdown(dropdown, triggerBtn);
|
|
669
|
+
}
|
|
670
|
+
showColorDropdown(triggerBtn) {
|
|
671
|
+
this.openToolbarDropdown(triggerBtn, "dm-table-controls-dropdown dm-table-cell-dropdown", (dropdown) => {
|
|
672
|
+
const palette = document.createElement("div");
|
|
673
|
+
palette.className = "dm-color-palette";
|
|
674
|
+
palette.style.setProperty("--dm-palette-columns", "4");
|
|
675
|
+
const resetBtn = document.createElement("button");
|
|
676
|
+
resetBtn.type = "button";
|
|
677
|
+
resetBtn.className = "dm-color-palette-reset";
|
|
678
|
+
resetBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="14" height="14"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm88,104a87.56,87.56,0,0,1-20.41,56.28L71.72,60.41A88,88,0,0,1,216,128ZM40,128A87.56,87.56,0,0,1,60.41,71.72L184.28,195.59A88,88,0,0,1,40,128Z"/></svg> Default';
|
|
679
|
+
resetBtn.addEventListener("click", (e) => {
|
|
680
|
+
e.stopPropagation();
|
|
681
|
+
tables.setCellAttr("background", null)(this.view.state, this.view.dispatch);
|
|
682
|
+
this.closeDropdown();
|
|
683
|
+
});
|
|
684
|
+
palette.appendChild(resetBtn);
|
|
685
|
+
for (const color of CELL_COLORS) {
|
|
686
|
+
const swatch = document.createElement("button");
|
|
687
|
+
swatch.type = "button";
|
|
688
|
+
swatch.className = "dm-color-swatch";
|
|
689
|
+
swatch.style.backgroundColor = color;
|
|
690
|
+
swatch.setAttribute("aria-label", color);
|
|
691
|
+
swatch.addEventListener("click", (e) => {
|
|
692
|
+
e.stopPropagation();
|
|
693
|
+
tables.setCellAttr("background", color)(this.view.state, this.view.dispatch);
|
|
694
|
+
this.closeDropdown();
|
|
695
|
+
});
|
|
696
|
+
palette.appendChild(swatch);
|
|
697
|
+
}
|
|
698
|
+
dropdown.appendChild(palette);
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
showAlignmentDropdown(triggerBtn) {
|
|
702
|
+
this.openToolbarDropdown(triggerBtn, "dm-table-controls-dropdown dm-table-cell-align-dropdown", (dropdown) => {
|
|
703
|
+
const sel = this.view.state.selection;
|
|
704
|
+
const cellNode = this.view.state.doc.nodeAt(sel.$anchorCell.pos);
|
|
705
|
+
const curTextAlign = cellNode?.attrs["textAlign"] ?? null;
|
|
706
|
+
const curVerticalAlign = cellNode?.attrs["verticalAlign"] ?? null;
|
|
707
|
+
const hAligns = [
|
|
708
|
+
{ value: "left", label: "Align left", icon: ICON_ALIGN_LEFT },
|
|
709
|
+
{ value: "center", label: "Align center", icon: ICON_ALIGN_CENTER },
|
|
710
|
+
{ value: "right", label: "Align right", icon: ICON_ALIGN_RIGHT }
|
|
711
|
+
];
|
|
712
|
+
const vAligns = [
|
|
713
|
+
{ value: "top", label: "Align top", icon: ICON_ALIGN_TOP },
|
|
714
|
+
{ value: "middle", label: "Align middle", icon: ICON_ALIGN_MIDDLE },
|
|
715
|
+
{ value: "bottom", label: "Align bottom", icon: ICON_ALIGN_BOTTOM }
|
|
716
|
+
];
|
|
717
|
+
for (const a of hAligns) {
|
|
718
|
+
const isActive = curTextAlign === a.value || !curTextAlign && a.value === "left";
|
|
719
|
+
dropdown.appendChild(this.createAlignItem(a.icon, a.label, isActive, () => {
|
|
720
|
+
tables.setCellAttr("textAlign", a.value === "left" ? null : a.value)(this.view.state, this.view.dispatch);
|
|
721
|
+
this.closeDropdown();
|
|
722
|
+
}));
|
|
723
|
+
}
|
|
724
|
+
const sep = document.createElement("div");
|
|
725
|
+
sep.className = "dm-table-cell-dropdown-separator";
|
|
726
|
+
dropdown.appendChild(sep);
|
|
727
|
+
for (const a of vAligns) {
|
|
728
|
+
const isActive = curVerticalAlign === a.value || !curVerticalAlign && a.value === "top";
|
|
729
|
+
dropdown.appendChild(this.createAlignItem(a.icon, a.label, isActive, () => {
|
|
730
|
+
tables.setCellAttr("verticalAlign", a.value === "top" ? null : a.value)(this.view.state, this.view.dispatch);
|
|
731
|
+
this.closeDropdown();
|
|
732
|
+
}));
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
createAlignItem(icon, label, active, onClick) {
|
|
737
|
+
const btn = document.createElement("button");
|
|
738
|
+
btn.type = "button";
|
|
739
|
+
btn.className = "dm-table-align-item" + (active ? " dm-table-align-item--active" : "");
|
|
740
|
+
btn.innerHTML = `<span class="dm-table-align-item-icon">${icon}</span><span>${label}</span>`;
|
|
741
|
+
btn.addEventListener("click", (e) => {
|
|
742
|
+
e.stopPropagation();
|
|
743
|
+
onClick();
|
|
744
|
+
});
|
|
745
|
+
return btn;
|
|
746
|
+
}
|
|
747
|
+
positionToolbarDropdown(dropdown, triggerBtn) {
|
|
748
|
+
const btnRect = triggerBtn.getBoundingClientRect();
|
|
749
|
+
dropdown.style.position = "fixed";
|
|
750
|
+
dropdown.style.top = String(btnRect.bottom + 4) + "px";
|
|
751
|
+
document.body.appendChild(dropdown);
|
|
752
|
+
this.dropdown = dropdown;
|
|
753
|
+
const dropdownWidth = dropdown.offsetWidth;
|
|
754
|
+
let leftPos = btnRect.left;
|
|
755
|
+
if (leftPos + dropdownWidth > window.innerWidth) {
|
|
756
|
+
leftPos = window.innerWidth - dropdownWidth - 4;
|
|
757
|
+
}
|
|
758
|
+
dropdown.style.left = String(Math.max(0, leftPos)) + "px";
|
|
759
|
+
this.addDropdownListeners();
|
|
760
|
+
}
|
|
761
|
+
addDropdownListeners() {
|
|
762
|
+
document.addEventListener("mousedown", this.boundDocMouseDown, true);
|
|
763
|
+
document.addEventListener("keydown", this.boundDocKeyDown);
|
|
764
|
+
window.addEventListener("scroll", this.boundScroll, true);
|
|
765
|
+
}
|
|
766
|
+
removeDropdownListeners() {
|
|
767
|
+
document.removeEventListener("mousedown", this.boundDocMouseDown, true);
|
|
768
|
+
document.removeEventListener("keydown", this.boundDocKeyDown);
|
|
769
|
+
window.removeEventListener("scroll", this.boundScroll, true);
|
|
770
|
+
}
|
|
771
|
+
closeDropdown() {
|
|
772
|
+
if (!this.dropdown) return;
|
|
773
|
+
this.dropdown.remove();
|
|
774
|
+
this.dropdown = null;
|
|
775
|
+
this.suppressCellToolbar = false;
|
|
776
|
+
this.cellToolbar.querySelectorAll(".dm-table-cell-toolbar-btn--open").forEach(
|
|
777
|
+
(el) => {
|
|
778
|
+
el.classList.remove("dm-table-cell-toolbar-btn--open");
|
|
779
|
+
}
|
|
780
|
+
);
|
|
781
|
+
this.removeDropdownListeners();
|
|
782
|
+
}
|
|
783
|
+
onDocMouseDown(e) {
|
|
784
|
+
const target = e.target;
|
|
785
|
+
if (this.dropdown?.contains(target) || this.cellToolbar.contains(target) || this.colHandle.contains(target) || this.rowHandle.contains(target)) {
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
this.closeDropdown();
|
|
789
|
+
}
|
|
790
|
+
onDocKeyDown(e) {
|
|
791
|
+
if (e.key === "Escape") {
|
|
792
|
+
this.closeDropdown();
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
execRowCmd(cmd) {
|
|
796
|
+
this.setCursorInCell(this.hoveredRow, 0);
|
|
797
|
+
const state = this.view.state;
|
|
798
|
+
if (cmd === tables.deleteRow && tables.isInTable(state)) {
|
|
799
|
+
const rect = tables.selectedRect(state);
|
|
800
|
+
if (rect.top === 0 && rect.bottom === rect.map.height) {
|
|
801
|
+
tables.deleteTable(state, this.view.dispatch);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
cmd(state, this.view.dispatch);
|
|
806
|
+
}
|
|
807
|
+
execColCmd(cmd) {
|
|
808
|
+
this.setCursorInCell(0, this.hoveredCol);
|
|
809
|
+
if (this.constrainToContainer && (cmd === tables.addColumnBefore || cmd === tables.addColumnAfter)) {
|
|
810
|
+
constrainedAddColumn(cmd, this.view, this.cellMinWidth, this.defaultCellMinWidth);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
const state = this.view.state;
|
|
814
|
+
if (cmd === tables.deleteColumn && tables.isInTable(state)) {
|
|
815
|
+
const rect = tables.selectedRect(state);
|
|
816
|
+
if (rect.left === 0 && rect.right === rect.map.width) {
|
|
817
|
+
tables.deleteTable(state, this.view.dispatch);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
cmd(state, this.view.dispatch);
|
|
822
|
+
}
|
|
823
|
+
// ─── Column management ───────────────────────────────────────────────
|
|
824
|
+
/**
|
|
825
|
+
* Update colgroup col elements based on cell widths.
|
|
826
|
+
* Matches prosemirror-tables' updateColumnsOnResize behavior:
|
|
827
|
+
* - Reuses existing col elements (avoids DOM churn during resize)
|
|
828
|
+
* - Uses defaultCellMinWidth for totalWidth calc (matches columnResizing plugin)
|
|
829
|
+
* - Columns without explicit widths get empty style.width (table-layout: fixed distributes)
|
|
830
|
+
*/
|
|
831
|
+
updateColumns(node) {
|
|
832
|
+
let totalWidth = 0;
|
|
833
|
+
let fixedWidth = true;
|
|
834
|
+
let nextDOM = this.colgroup.firstChild;
|
|
835
|
+
const firstRow = node.firstChild;
|
|
836
|
+
if (!firstRow) return;
|
|
837
|
+
for (let i = 0; i < firstRow.childCount; i++) {
|
|
838
|
+
const cell = firstRow.child(i);
|
|
839
|
+
const colspan = cell.attrs["colspan"] || 1;
|
|
840
|
+
const colwidth = cell.attrs["colwidth"];
|
|
841
|
+
for (let j = 0; j < colspan; j++) {
|
|
842
|
+
const hasWidth = colwidth?.[j];
|
|
843
|
+
const cssWidth = hasWidth ? String(hasWidth) + "px" : "";
|
|
844
|
+
totalWidth += hasWidth ?? this.defaultCellMinWidth;
|
|
845
|
+
if (!hasWidth) fixedWidth = false;
|
|
846
|
+
if (!nextDOM) {
|
|
847
|
+
const colEl = document.createElement("col");
|
|
848
|
+
colEl.style.width = cssWidth;
|
|
849
|
+
this.colgroup.appendChild(colEl);
|
|
850
|
+
} else {
|
|
851
|
+
if (nextDOM.style.width !== cssWidth) {
|
|
852
|
+
nextDOM.style.width = cssWidth;
|
|
853
|
+
}
|
|
854
|
+
nextDOM = nextDOM.nextElementSibling;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
while (nextDOM) {
|
|
859
|
+
const after = nextDOM.nextElementSibling;
|
|
860
|
+
nextDOM.remove();
|
|
861
|
+
nextDOM = after;
|
|
862
|
+
}
|
|
863
|
+
if (fixedWidth && totalWidth > 0) {
|
|
864
|
+
this.table.style.width = String(totalWidth) + "px";
|
|
865
|
+
this.table.style.minWidth = "";
|
|
866
|
+
} else {
|
|
867
|
+
this.table.style.width = "";
|
|
868
|
+
this.table.style.minWidth = this.constrainToContainer ? "" : String(totalWidth) + "px";
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
function createTable(schema, rows, cols, withHeaderRow, cellContent) {
|
|
873
|
+
const types = tables.tableNodeTypes(schema);
|
|
874
|
+
const headerCells = [];
|
|
875
|
+
const cells = [];
|
|
876
|
+
for (let col = 0; col < cols; col++) {
|
|
877
|
+
const cell = cellContent ? types.cell.createChecked(null, cellContent) : types.cell.createAndFill();
|
|
878
|
+
if (cell) {
|
|
879
|
+
cells.push(cell);
|
|
880
|
+
}
|
|
881
|
+
if (withHeaderRow) {
|
|
882
|
+
const headerCell = cellContent ? types.header_cell.createChecked(null, cellContent) : types.header_cell.createAndFill();
|
|
883
|
+
if (headerCell) {
|
|
884
|
+
headerCells.push(headerCell);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
const tableRows = [];
|
|
889
|
+
for (let row = 0; row < rows; row++) {
|
|
890
|
+
const rowCells = withHeaderRow && row === 0 ? headerCells : cells;
|
|
891
|
+
tableRows.push(types.row.createChecked(null, rowCells));
|
|
892
|
+
}
|
|
893
|
+
return types.table.createChecked(null, tableRows);
|
|
894
|
+
}
|
|
895
|
+
function deleteTableWhenAllCellsSelected({
|
|
896
|
+
state: state$1,
|
|
897
|
+
dispatch
|
|
898
|
+
}) {
|
|
899
|
+
const { selection } = state$1;
|
|
900
|
+
if (!(selection instanceof tables.CellSelection)) {
|
|
901
|
+
return false;
|
|
902
|
+
}
|
|
903
|
+
let cellCount = 0;
|
|
904
|
+
const table = tables.findTable(selection.$anchorCell);
|
|
905
|
+
if (!table) {
|
|
906
|
+
return false;
|
|
907
|
+
}
|
|
908
|
+
table.node.descendants((node) => {
|
|
909
|
+
const role = node.type.spec["tableRole"];
|
|
910
|
+
if (role === "cell" || role === "header_cell") {
|
|
911
|
+
cellCount++;
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
const selectionCellCount = selection.ranges.length;
|
|
915
|
+
if (cellCount !== selectionCellCount) {
|
|
916
|
+
return false;
|
|
917
|
+
}
|
|
918
|
+
if (dispatch) {
|
|
919
|
+
const tr = state$1.tr;
|
|
920
|
+
tr.delete(table.pos, table.pos + table.node.nodeSize);
|
|
921
|
+
const $pos = tr.doc.resolve(Math.min(table.pos, tr.doc.content.size));
|
|
922
|
+
const newSelection = state.TextSelection.near($pos);
|
|
923
|
+
tr.setSelection(newSelection);
|
|
924
|
+
dispatch(tr);
|
|
925
|
+
}
|
|
926
|
+
return true;
|
|
927
|
+
}
|
|
928
|
+
function createResizeSuppressionPlugin(options) {
|
|
929
|
+
const { resizeBehavior, cellMinWidth, defaultCellMinWidth, constrainToContainer } = options;
|
|
930
|
+
return new state.Plugin({
|
|
931
|
+
props: {
|
|
932
|
+
handleDOMEvents: {
|
|
933
|
+
mousedown: (view, event) => {
|
|
934
|
+
if (event.button !== 0) return false;
|
|
935
|
+
const resizeState = tables.columnResizingPluginKey.getState(view.state);
|
|
936
|
+
if (!resizeState || resizeState.activeHandle === -1) {
|
|
937
|
+
view.dom.classList.add("dm-mouse-drag");
|
|
938
|
+
document.addEventListener("mouseup", () => {
|
|
939
|
+
view.dom.classList.remove("dm-mouse-drag");
|
|
940
|
+
}, { once: true });
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
if (resizeBehavior === "redistribute" || resizeBehavior === "independent") {
|
|
944
|
+
if (resizeBehavior === "independent") {
|
|
945
|
+
freezeColumnWidths(view, resizeState.activeHandle, cellMinWidth, defaultCellMinWidth);
|
|
946
|
+
}
|
|
947
|
+
const win = view.dom.ownerDocument.defaultView ?? window;
|
|
948
|
+
win.addEventListener("mouseup", () => {
|
|
949
|
+
const st = tables.columnResizingPluginKey.getState(view.state);
|
|
950
|
+
if (st && st.activeHandle > -1 && !st.dragging) {
|
|
951
|
+
view.dispatch(view.state.tr.setMeta(tables.columnResizingPluginKey, { setHandle: -1 }));
|
|
952
|
+
}
|
|
953
|
+
}, { once: true });
|
|
954
|
+
return false;
|
|
955
|
+
}
|
|
956
|
+
return handleNeighborResize(view, event, resizeState.activeHandle, cellMinWidth, defaultCellMinWidth, constrainToContainer);
|
|
957
|
+
},
|
|
958
|
+
mousemove: (view, event) => {
|
|
959
|
+
if (event.buttons !== 1) return false;
|
|
960
|
+
const resizeState = tables.columnResizingPluginKey.getState(view.state);
|
|
961
|
+
if (resizeState?.dragging) return false;
|
|
962
|
+
return true;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
function handleNeighborResize(view, event, activeHandle, cellMinWidth, defaultCellMinWidth, constrainToContainer) {
|
|
969
|
+
freezeColumnWidths(view, activeHandle, cellMinWidth, defaultCellMinWidth);
|
|
970
|
+
const state = view.state;
|
|
971
|
+
const resizeState = tables.columnResizingPluginKey.getState(state);
|
|
972
|
+
const handle = resizeState?.activeHandle ?? -1;
|
|
973
|
+
if (handle === -1) return false;
|
|
974
|
+
const $cell = state.doc.resolve(handle);
|
|
975
|
+
const table = $cell.node(-1);
|
|
976
|
+
const map = tables.TableMap.get(table);
|
|
977
|
+
const tableStart = $cell.start(-1);
|
|
978
|
+
const nodeAfter = $cell.nodeAfter;
|
|
979
|
+
if (!nodeAfter) return false;
|
|
980
|
+
const draggedCol = map.colCount($cell.pos - tableStart) + (nodeAfter.attrs["colspan"] || 1) - 1;
|
|
981
|
+
const neighborCol = draggedCol + 1;
|
|
982
|
+
if (neighborCol >= map.width) {
|
|
983
|
+
if (!constrainToContainer) return false;
|
|
984
|
+
return handleLastColumnResize(view, event, table, map, tableStart, draggedCol, cellMinWidth, defaultCellMinWidth);
|
|
985
|
+
}
|
|
986
|
+
const startWidth = readColWidth(table, map, draggedCol, defaultCellMinWidth);
|
|
987
|
+
const neighborStartWidth = readColWidth(table, map, neighborCol, defaultCellMinWidth);
|
|
988
|
+
const startX = event.clientX;
|
|
989
|
+
view.dispatch(state.tr.setMeta(tables.columnResizingPluginKey, {
|
|
990
|
+
setDragging: { startX, startWidth }
|
|
991
|
+
}));
|
|
992
|
+
const tableDom = findTableDom(view, tableStart);
|
|
993
|
+
const colgroup = tableDom?.querySelector("colgroup")?.children;
|
|
994
|
+
if (!colgroup) return false;
|
|
995
|
+
const cols = colgroup;
|
|
996
|
+
const win = view.dom.ownerDocument.defaultView ?? window;
|
|
997
|
+
function move(e) {
|
|
998
|
+
if (!e.buttons) {
|
|
999
|
+
finish(e);
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
const offset = e.clientX - startX;
|
|
1003
|
+
const clamped = Math.max(
|
|
1004
|
+
-(startWidth - cellMinWidth),
|
|
1005
|
+
Math.min(offset, neighborStartWidth - cellMinWidth)
|
|
1006
|
+
);
|
|
1007
|
+
const newDraggedW = startWidth + clamped;
|
|
1008
|
+
const newNeighborW = neighborStartWidth - clamped;
|
|
1009
|
+
cols[draggedCol].style.width = String(newDraggedW) + "px";
|
|
1010
|
+
cols[neighborCol].style.width = String(newNeighborW) + "px";
|
|
1011
|
+
}
|
|
1012
|
+
function finish(e) {
|
|
1013
|
+
win.removeEventListener("mouseup", finish);
|
|
1014
|
+
win.removeEventListener("mousemove", move);
|
|
1015
|
+
const pluginState = tables.columnResizingPluginKey.getState(view.state);
|
|
1016
|
+
if (!pluginState?.dragging) return;
|
|
1017
|
+
const offset = e.clientX - startX;
|
|
1018
|
+
const clamped = Math.max(
|
|
1019
|
+
-(startWidth - cellMinWidth),
|
|
1020
|
+
Math.min(offset, neighborStartWidth - cellMinWidth)
|
|
1021
|
+
);
|
|
1022
|
+
const finalDraggedW = Math.round(startWidth + clamped);
|
|
1023
|
+
const finalNeighborW = Math.round(neighborStartWidth - clamped);
|
|
1024
|
+
const curState = view.state;
|
|
1025
|
+
const curHandle = pluginState.activeHandle;
|
|
1026
|
+
const $curCell = curState.doc.resolve(curHandle);
|
|
1027
|
+
const curTable = $curCell.node(-1);
|
|
1028
|
+
const curMap = tables.TableMap.get(curTable);
|
|
1029
|
+
const curStart = $curCell.start(-1);
|
|
1030
|
+
const tr = curState.tr;
|
|
1031
|
+
storeColWidth(tr, curTable, curMap, curStart, draggedCol, finalDraggedW);
|
|
1032
|
+
storeColWidth(tr, curTable, curMap, curStart, neighborCol, finalNeighborW);
|
|
1033
|
+
tr.setMeta(tables.columnResizingPluginKey, { setDragging: null });
|
|
1034
|
+
view.dispatch(tr);
|
|
1035
|
+
view.dispatch(view.state.tr.setMeta(tables.columnResizingPluginKey, { setHandle: -1 }));
|
|
1036
|
+
}
|
|
1037
|
+
win.addEventListener("mouseup", finish);
|
|
1038
|
+
win.addEventListener("mousemove", move);
|
|
1039
|
+
event.preventDefault();
|
|
1040
|
+
return true;
|
|
1041
|
+
}
|
|
1042
|
+
function handleLastColumnResize(view, event, table, map, tableStart, draggedCol, cellMinWidth, defaultCellMinWidth) {
|
|
1043
|
+
const startWidth = readColWidth(table, map, draggedCol, defaultCellMinWidth);
|
|
1044
|
+
const startX = event.clientX;
|
|
1045
|
+
const tableDom = findTableDom(view, tableStart);
|
|
1046
|
+
const colgroupChildren = tableDom?.querySelector("colgroup")?.children;
|
|
1047
|
+
if (!colgroupChildren) return false;
|
|
1048
|
+
const cols = colgroupChildren;
|
|
1049
|
+
let totalWidth = 0;
|
|
1050
|
+
for (let c = 0; c < map.width; c++) {
|
|
1051
|
+
totalWidth += readColWidth(table, map, c, defaultCellMinWidth);
|
|
1052
|
+
}
|
|
1053
|
+
const containerWidth = getContainerWidth(view, tableStart);
|
|
1054
|
+
const maxGrow = containerWidth > 0 ? containerWidth - totalWidth : 0;
|
|
1055
|
+
view.dispatch(view.state.tr.setMeta(tables.columnResizingPluginKey, {
|
|
1056
|
+
setDragging: { startX, startWidth }
|
|
1057
|
+
}));
|
|
1058
|
+
const win = view.dom.ownerDocument.defaultView ?? window;
|
|
1059
|
+
function move(e) {
|
|
1060
|
+
if (!e.buttons) {
|
|
1061
|
+
finish(e);
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
const offset = e.clientX - startX;
|
|
1065
|
+
const clamped = Math.max(-(startWidth - cellMinWidth), Math.min(offset, maxGrow));
|
|
1066
|
+
cols[draggedCol].style.width = String(startWidth + clamped) + "px";
|
|
1067
|
+
if (tableDom) tableDom.style.width = String(totalWidth + clamped) + "px";
|
|
1068
|
+
}
|
|
1069
|
+
function finish(e) {
|
|
1070
|
+
win.removeEventListener("mouseup", finish);
|
|
1071
|
+
win.removeEventListener("mousemove", move);
|
|
1072
|
+
const pluginState = tables.columnResizingPluginKey.getState(view.state);
|
|
1073
|
+
if (!pluginState?.dragging) return;
|
|
1074
|
+
const offset = e.clientX - startX;
|
|
1075
|
+
const clamped = Math.max(-(startWidth - cellMinWidth), Math.min(offset, maxGrow));
|
|
1076
|
+
const finalWidth = Math.round(startWidth + clamped);
|
|
1077
|
+
const curState = view.state;
|
|
1078
|
+
const $curCell = curState.doc.resolve(pluginState.activeHandle);
|
|
1079
|
+
const curTable = $curCell.node(-1);
|
|
1080
|
+
const curMap = tables.TableMap.get(curTable);
|
|
1081
|
+
const curStart = $curCell.start(-1);
|
|
1082
|
+
const tr = curState.tr;
|
|
1083
|
+
storeColWidth(tr, curTable, curMap, curStart, draggedCol, finalWidth);
|
|
1084
|
+
tr.setMeta(tables.columnResizingPluginKey, { setDragging: null });
|
|
1085
|
+
view.dispatch(tr);
|
|
1086
|
+
view.dispatch(view.state.tr.setMeta(tables.columnResizingPluginKey, { setHandle: -1 }));
|
|
1087
|
+
}
|
|
1088
|
+
win.addEventListener("mouseup", finish);
|
|
1089
|
+
win.addEventListener("mousemove", move);
|
|
1090
|
+
event.preventDefault();
|
|
1091
|
+
return true;
|
|
1092
|
+
}
|
|
1093
|
+
function readColWidth(table, map, col, fallback) {
|
|
1094
|
+
const offset = map.map[col] ?? 0;
|
|
1095
|
+
const cell = table.nodeAt(offset);
|
|
1096
|
+
if (!cell) return fallback;
|
|
1097
|
+
const idx = col - map.colCount(offset);
|
|
1098
|
+
const colwidth = cell.attrs["colwidth"];
|
|
1099
|
+
return colwidth?.[idx] ?? fallback;
|
|
1100
|
+
}
|
|
1101
|
+
function storeColWidth(tr, table, map, tableStart, col, width) {
|
|
1102
|
+
for (let row = 0; row < map.height; row++) {
|
|
1103
|
+
const mapIndex = row * map.width + col;
|
|
1104
|
+
if (row > 0 && map.map[mapIndex] === map.map[mapIndex - map.width]) continue;
|
|
1105
|
+
const pos = map.map[mapIndex] ?? 0;
|
|
1106
|
+
const cellNode = table.nodeAt(pos);
|
|
1107
|
+
if (!cellNode) continue;
|
|
1108
|
+
const attrs = cellNode.attrs;
|
|
1109
|
+
const colspan = attrs["colspan"] || 1;
|
|
1110
|
+
const index = colspan === 1 ? 0 : col - map.colCount(pos);
|
|
1111
|
+
const colwidth = attrs["colwidth"];
|
|
1112
|
+
if (colwidth?.[index] === width) continue;
|
|
1113
|
+
const newColwidth = colwidth ? colwidth.slice() : new Array(colspan).fill(0);
|
|
1114
|
+
newColwidth[index] = width;
|
|
1115
|
+
tr.setNodeMarkup(tableStart + pos, null, { ...attrs, colwidth: newColwidth });
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
function freezeColumnWidths(view, handlePos, cellMinWidth, defaultCellMinWidth) {
|
|
1119
|
+
const state = view.state;
|
|
1120
|
+
const $cell = state.doc.resolve(handlePos);
|
|
1121
|
+
let tableDepth = -1;
|
|
1122
|
+
for (let d = $cell.depth; d > 0; d--) {
|
|
1123
|
+
if ($cell.node(d).type.name === "table") {
|
|
1124
|
+
tableDepth = d;
|
|
1125
|
+
break;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
if (tableDepth === -1) return;
|
|
1129
|
+
const table = $cell.node(tableDepth);
|
|
1130
|
+
const tableStart = $cell.start(tableDepth);
|
|
1131
|
+
const map = tables.TableMap.get(table);
|
|
1132
|
+
const firstRow = table.firstChild;
|
|
1133
|
+
if (!firstRow) return;
|
|
1134
|
+
const colNeedsWidth = [];
|
|
1135
|
+
let anyNeedsWidth = false;
|
|
1136
|
+
for (let col = 0; col < map.width; col++) {
|
|
1137
|
+
const cellOffset = map.map[col] ?? 0;
|
|
1138
|
+
const cellNode = table.nodeAt(cellOffset);
|
|
1139
|
+
if (!cellNode) {
|
|
1140
|
+
colNeedsWidth.push(true);
|
|
1141
|
+
anyNeedsWidth = true;
|
|
1142
|
+
continue;
|
|
1143
|
+
}
|
|
1144
|
+
const colWithinCell = col - map.colCount(cellOffset);
|
|
1145
|
+
const colwidth = cellNode.attrs["colwidth"];
|
|
1146
|
+
if (colwidth?.[colWithinCell]) {
|
|
1147
|
+
colNeedsWidth.push(false);
|
|
1148
|
+
} else {
|
|
1149
|
+
colNeedsWidth.push(true);
|
|
1150
|
+
anyNeedsWidth = true;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
if (!anyNeedsWidth) return;
|
|
1154
|
+
const measuredWidths = new Array(map.width);
|
|
1155
|
+
for (let col = 0; col < map.width; col++) {
|
|
1156
|
+
const cellOffset = map.map[col] ?? 0;
|
|
1157
|
+
const cellNode = table.nodeAt(cellOffset);
|
|
1158
|
+
if (!cellNode) {
|
|
1159
|
+
measuredWidths[col] = defaultCellMinWidth;
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
const colspan = cellNode.attrs["colspan"] || 1;
|
|
1163
|
+
const colwidth = cellNode.attrs["colwidth"];
|
|
1164
|
+
const colWithinCell = col - map.colCount(cellOffset);
|
|
1165
|
+
if (!colNeedsWidth[col]) {
|
|
1166
|
+
measuredWidths[col] = colwidth?.[colWithinCell] ?? defaultCellMinWidth;
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1169
|
+
try {
|
|
1170
|
+
const dom = view.domAtPos(tableStart + cellOffset);
|
|
1171
|
+
const cellDom = dom.node.childNodes[dom.offset];
|
|
1172
|
+
if (cellDom) {
|
|
1173
|
+
let domWidth = cellDom.offsetWidth;
|
|
1174
|
+
let parts = colspan;
|
|
1175
|
+
if (colwidth) {
|
|
1176
|
+
for (let j = 0; j < colspan; j++) {
|
|
1177
|
+
const cw = colwidth[j];
|
|
1178
|
+
if (cw) {
|
|
1179
|
+
domWidth -= cw;
|
|
1180
|
+
parts--;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
measuredWidths[col] = Math.max(cellMinWidth, Math.round(domWidth / parts));
|
|
1185
|
+
} else {
|
|
1186
|
+
measuredWidths[col] = defaultCellMinWidth;
|
|
1187
|
+
}
|
|
1188
|
+
} catch {
|
|
1189
|
+
measuredWidths[col] = defaultCellMinWidth;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
try {
|
|
1193
|
+
const tableDom = findTableDom(view, tableStart);
|
|
1194
|
+
if (tableDom) {
|
|
1195
|
+
const actualWidth = Math.floor(tableDom.getBoundingClientRect().width) - 1;
|
|
1196
|
+
if (actualWidth > 0) {
|
|
1197
|
+
let measuredTotal = 0;
|
|
1198
|
+
for (let col = 0; col < map.width; col++) measuredTotal += measuredWidths[col] ?? 0;
|
|
1199
|
+
const diff = measuredTotal - actualWidth;
|
|
1200
|
+
if (diff > 0) {
|
|
1201
|
+
for (let col = map.width - 1; col >= 0; col--) {
|
|
1202
|
+
if (colNeedsWidth[col]) {
|
|
1203
|
+
measuredWidths[col] = Math.max(cellMinWidth, (measuredWidths[col] ?? 0) - diff);
|
|
1204
|
+
break;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
} catch {
|
|
1211
|
+
}
|
|
1212
|
+
const cellColwidths = /* @__PURE__ */ new Map();
|
|
1213
|
+
for (let col = 0; col < map.width; col++) {
|
|
1214
|
+
if (!colNeedsWidth[col]) continue;
|
|
1215
|
+
const width = measuredWidths[col] ?? 0;
|
|
1216
|
+
for (let row = 0; row < map.height; row++) {
|
|
1217
|
+
const mapIndex = row * map.width + col;
|
|
1218
|
+
if (row > 0 && map.map[mapIndex] === map.map[mapIndex - map.width]) continue;
|
|
1219
|
+
const pos = map.map[mapIndex] ?? 0;
|
|
1220
|
+
const cellNode = table.nodeAt(pos);
|
|
1221
|
+
if (!cellNode) continue;
|
|
1222
|
+
const attrs = cellNode.attrs;
|
|
1223
|
+
const colspan = attrs["colspan"] || 1;
|
|
1224
|
+
const index = colspan === 1 ? 0 : col - map.colCount(pos);
|
|
1225
|
+
if (!cellColwidths.has(pos)) {
|
|
1226
|
+
const existing = attrs["colwidth"];
|
|
1227
|
+
cellColwidths.set(pos, existing ? existing.slice() : new Array(colspan).fill(0));
|
|
1228
|
+
}
|
|
1229
|
+
const arr = cellColwidths.get(pos);
|
|
1230
|
+
if (arr) arr[index] = width;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
const tr = state.tr;
|
|
1234
|
+
for (const [pos, colwidth] of cellColwidths) {
|
|
1235
|
+
const cellNode = table.nodeAt(pos);
|
|
1236
|
+
if (!cellNode) continue;
|
|
1237
|
+
tr.setNodeMarkup(tableStart + pos, null, {
|
|
1238
|
+
...cellNode.attrs,
|
|
1239
|
+
colwidth
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
if (tr.docChanged) {
|
|
1243
|
+
view.dispatch(tr);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
var pluginKey = new state.PluginKey("cellSelection");
|
|
1247
|
+
function domElementAt(view, pos) {
|
|
1248
|
+
const { node } = view.domAtPos(pos);
|
|
1249
|
+
return node instanceof HTMLElement ? node : node.parentElement;
|
|
1250
|
+
}
|
|
1251
|
+
function createCellSelectionPlugin() {
|
|
1252
|
+
return new state.Plugin({
|
|
1253
|
+
key: pluginKey,
|
|
1254
|
+
state: {
|
|
1255
|
+
init() {
|
|
1256
|
+
return view.DecorationSet.empty;
|
|
1257
|
+
},
|
|
1258
|
+
apply(_tr, _set, _oldState, newState) {
|
|
1259
|
+
const sel = newState.selection;
|
|
1260
|
+
if (sel instanceof tables.CellSelection) return view.DecorationSet.empty;
|
|
1261
|
+
const $from = sel.$from;
|
|
1262
|
+
for (let d = $from.depth; d > 0; d--) {
|
|
1263
|
+
const name = $from.node(d).type.name;
|
|
1264
|
+
if (name === "tableCell" || name === "tableHeader") {
|
|
1265
|
+
const pos = $from.before(d);
|
|
1266
|
+
const deco = view.Decoration.node(pos, pos + $from.node(d).nodeSize, { class: "dm-cell-focused" });
|
|
1267
|
+
return view.DecorationSet.create(newState.doc, [deco]);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
return view.DecorationSet.empty;
|
|
1271
|
+
}
|
|
1272
|
+
},
|
|
1273
|
+
props: {
|
|
1274
|
+
decorations(state) {
|
|
1275
|
+
return pluginKey.getState(state);
|
|
1276
|
+
}
|
|
1277
|
+
},
|
|
1278
|
+
view: () => {
|
|
1279
|
+
let lastToolbarView = null;
|
|
1280
|
+
let lastHandleView = null;
|
|
1281
|
+
let resizingView = null;
|
|
1282
|
+
return {
|
|
1283
|
+
update: (view) => {
|
|
1284
|
+
const draggingCell = view.dom.querySelector(".column-resize-dragging");
|
|
1285
|
+
if (draggingCell) {
|
|
1286
|
+
const container = draggingCell.closest(".dm-table-container");
|
|
1287
|
+
const tv = container ? tableViewMap.get(container) : void 0;
|
|
1288
|
+
if (tv && tv !== resizingView) {
|
|
1289
|
+
tv.hideForResize();
|
|
1290
|
+
resizingView = tv;
|
|
1291
|
+
}
|
|
1292
|
+
return;
|
|
1293
|
+
} else if (resizingView) {
|
|
1294
|
+
resizingView.showAfterResize();
|
|
1295
|
+
resizingView = null;
|
|
1296
|
+
}
|
|
1297
|
+
const sel = view.state.selection;
|
|
1298
|
+
if (sel instanceof tables.CellSelection) {
|
|
1299
|
+
const anchorPos = sel.$anchorCell.pos;
|
|
1300
|
+
const el = domElementAt(view, anchorPos + 1);
|
|
1301
|
+
const container = el?.closest(".dm-table-container");
|
|
1302
|
+
const tv = container ? tableViewMap.get(container) : void 0;
|
|
1303
|
+
if (tv) {
|
|
1304
|
+
if (!tv.suppressCellToolbar) {
|
|
1305
|
+
tv.updateCellHandle(true);
|
|
1306
|
+
}
|
|
1307
|
+
tv.hideCellHandle();
|
|
1308
|
+
lastToolbarView = tv;
|
|
1309
|
+
}
|
|
1310
|
+
if (lastHandleView && lastHandleView !== tv) {
|
|
1311
|
+
lastHandleView.hideCellHandle();
|
|
1312
|
+
}
|
|
1313
|
+
lastHandleView = null;
|
|
1314
|
+
} else {
|
|
1315
|
+
if (lastToolbarView) {
|
|
1316
|
+
lastToolbarView.updateCellHandle(false);
|
|
1317
|
+
lastToolbarView = null;
|
|
1318
|
+
}
|
|
1319
|
+
const $from = sel.$from;
|
|
1320
|
+
let inCell = false;
|
|
1321
|
+
for (let d = $from.depth; d > 0; d--) {
|
|
1322
|
+
const name = $from.node(d).type.name;
|
|
1323
|
+
if (name === "tableCell" || name === "tableHeader") {
|
|
1324
|
+
inCell = true;
|
|
1325
|
+
break;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
if (inCell && sel.empty) {
|
|
1329
|
+
const domEl = domElementAt(view, $from.pos);
|
|
1330
|
+
const cellEl = domEl?.closest("td, th");
|
|
1331
|
+
const container = cellEl?.closest(".dm-table-container");
|
|
1332
|
+
const tv = container ? tableViewMap.get(container) : void 0;
|
|
1333
|
+
if (tv && cellEl) {
|
|
1334
|
+
tv.showCellHandle(cellEl);
|
|
1335
|
+
if (lastHandleView && lastHandleView !== tv) {
|
|
1336
|
+
lastHandleView.hideCellHandle();
|
|
1337
|
+
}
|
|
1338
|
+
lastHandleView = tv;
|
|
1339
|
+
}
|
|
1340
|
+
} else {
|
|
1341
|
+
if (lastHandleView) {
|
|
1342
|
+
lastHandleView.hideCellHandle();
|
|
1343
|
+
lastHandleView = null;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
var TableRow = core.Node.create({
|
|
1353
|
+
name: "tableRow",
|
|
1354
|
+
content: "(tableCell | tableHeader)*",
|
|
1355
|
+
tableRole: "row",
|
|
1356
|
+
addOptions() {
|
|
1357
|
+
return {
|
|
1358
|
+
HTMLAttributes: {}
|
|
1359
|
+
};
|
|
1360
|
+
},
|
|
1361
|
+
parseHTML() {
|
|
1362
|
+
return [{ tag: "tr" }];
|
|
1363
|
+
},
|
|
1364
|
+
renderHTML({ HTMLAttributes }) {
|
|
1365
|
+
return ["tr", { ...this.options.HTMLAttributes, ...HTMLAttributes }, 0];
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
// src/helpers/cellAttributes.ts
|
|
1370
|
+
function cellAttributes() {
|
|
1371
|
+
return {
|
|
1372
|
+
colspan: {
|
|
1373
|
+
default: 1,
|
|
1374
|
+
parseHTML: (element) => {
|
|
1375
|
+
const colspan = element.getAttribute("colspan");
|
|
1376
|
+
return colspan ? Number(colspan) : 1;
|
|
1377
|
+
},
|
|
1378
|
+
renderHTML: (attrs) => {
|
|
1379
|
+
const colspan = attrs["colspan"];
|
|
1380
|
+
if (colspan === 1) return null;
|
|
1381
|
+
return { colspan };
|
|
1382
|
+
}
|
|
1383
|
+
},
|
|
1384
|
+
rowspan: {
|
|
1385
|
+
default: 1,
|
|
1386
|
+
parseHTML: (element) => {
|
|
1387
|
+
const rowspan = element.getAttribute("rowspan");
|
|
1388
|
+
return rowspan ? Number(rowspan) : 1;
|
|
1389
|
+
},
|
|
1390
|
+
renderHTML: (attrs) => {
|
|
1391
|
+
const rowspan = attrs["rowspan"];
|
|
1392
|
+
if (rowspan === 1) return null;
|
|
1393
|
+
return { rowspan };
|
|
1394
|
+
}
|
|
1395
|
+
},
|
|
1396
|
+
colwidth: {
|
|
1397
|
+
default: null,
|
|
1398
|
+
parseHTML: (element) => {
|
|
1399
|
+
const colwidth = element.getAttribute("data-colwidth");
|
|
1400
|
+
return colwidth ? colwidth.split(",").map(Number) : null;
|
|
1401
|
+
},
|
|
1402
|
+
renderHTML: (attrs) => {
|
|
1403
|
+
const colwidth = attrs["colwidth"];
|
|
1404
|
+
if (!colwidth) return null;
|
|
1405
|
+
return { "data-colwidth": colwidth.join(",") };
|
|
1406
|
+
}
|
|
1407
|
+
},
|
|
1408
|
+
background: {
|
|
1409
|
+
default: null,
|
|
1410
|
+
parseHTML: (element) => {
|
|
1411
|
+
return element.getAttribute("data-background") ?? (element.style.backgroundColor || null);
|
|
1412
|
+
},
|
|
1413
|
+
renderHTML: (attrs) => {
|
|
1414
|
+
const bg = attrs["background"];
|
|
1415
|
+
if (!bg) return null;
|
|
1416
|
+
return { "data-background": bg, style: `background-color: ${bg}` };
|
|
1417
|
+
}
|
|
1418
|
+
},
|
|
1419
|
+
textAlign: {
|
|
1420
|
+
default: null,
|
|
1421
|
+
parseHTML: (element) => {
|
|
1422
|
+
return element.getAttribute("data-text-align") ?? null;
|
|
1423
|
+
},
|
|
1424
|
+
renderHTML: (attrs) => {
|
|
1425
|
+
const align = attrs["textAlign"];
|
|
1426
|
+
if (!align) return null;
|
|
1427
|
+
return { "data-text-align": align };
|
|
1428
|
+
}
|
|
1429
|
+
},
|
|
1430
|
+
verticalAlign: {
|
|
1431
|
+
default: null,
|
|
1432
|
+
parseHTML: (element) => {
|
|
1433
|
+
return element.getAttribute("data-vertical-align") ?? null;
|
|
1434
|
+
},
|
|
1435
|
+
renderHTML: (attrs) => {
|
|
1436
|
+
const align = attrs["verticalAlign"];
|
|
1437
|
+
if (!align) return null;
|
|
1438
|
+
return { "data-vertical-align": align };
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// src/TableCell.ts
|
|
1445
|
+
var TableCell = core.Node.create({
|
|
1446
|
+
name: "tableCell",
|
|
1447
|
+
content: "block+",
|
|
1448
|
+
tableRole: "cell",
|
|
1449
|
+
isolating: true,
|
|
1450
|
+
addOptions() {
|
|
1451
|
+
return {
|
|
1452
|
+
HTMLAttributes: {}
|
|
1453
|
+
};
|
|
1454
|
+
},
|
|
1455
|
+
addAttributes() {
|
|
1456
|
+
return cellAttributes();
|
|
1457
|
+
},
|
|
1458
|
+
parseHTML() {
|
|
1459
|
+
return [{ tag: "td" }];
|
|
1460
|
+
},
|
|
1461
|
+
renderHTML({ HTMLAttributes }) {
|
|
1462
|
+
return ["td", { ...this.options.HTMLAttributes, ...HTMLAttributes }, 0];
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
var TableHeader = core.Node.create({
|
|
1466
|
+
name: "tableHeader",
|
|
1467
|
+
content: "block+",
|
|
1468
|
+
tableRole: "header_cell",
|
|
1469
|
+
isolating: true,
|
|
1470
|
+
addOptions() {
|
|
1471
|
+
return {
|
|
1472
|
+
HTMLAttributes: {}
|
|
1473
|
+
};
|
|
1474
|
+
},
|
|
1475
|
+
addAttributes() {
|
|
1476
|
+
return cellAttributes();
|
|
1477
|
+
},
|
|
1478
|
+
parseHTML() {
|
|
1479
|
+
return [{ tag: "th" }];
|
|
1480
|
+
},
|
|
1481
|
+
renderHTML({ HTMLAttributes }) {
|
|
1482
|
+
return ["th", { ...this.options.HTMLAttributes, ...HTMLAttributes }, 0];
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
|
|
1486
|
+
// src/Table.ts
|
|
1487
|
+
var Table = core.Node.create({
|
|
1488
|
+
name: "table",
|
|
1489
|
+
group: "block",
|
|
1490
|
+
content: "tableRow+",
|
|
1491
|
+
tableRole: "table",
|
|
1492
|
+
isolating: true,
|
|
1493
|
+
addOptions() {
|
|
1494
|
+
return {
|
|
1495
|
+
HTMLAttributes: {},
|
|
1496
|
+
cellMinWidth: 25,
|
|
1497
|
+
defaultCellMinWidth: 100,
|
|
1498
|
+
resizeBehavior: "neighbor",
|
|
1499
|
+
constrainToContainer: true,
|
|
1500
|
+
allowTableNodeSelection: false,
|
|
1501
|
+
View: TableView
|
|
1502
|
+
};
|
|
1503
|
+
},
|
|
1504
|
+
parseHTML() {
|
|
1505
|
+
return [{ tag: "table" }];
|
|
1506
|
+
},
|
|
1507
|
+
renderHTML({ HTMLAttributes }) {
|
|
1508
|
+
return ["table", { ...this.options.HTMLAttributes, ...HTMLAttributes }, ["tbody", 0]];
|
|
1509
|
+
},
|
|
1510
|
+
addExtensions() {
|
|
1511
|
+
return [TableRow, TableCell, TableHeader];
|
|
1512
|
+
},
|
|
1513
|
+
addNodeView() {
|
|
1514
|
+
const ViewClass = this.options.View;
|
|
1515
|
+
const cellMinWidth = this.options.cellMinWidth;
|
|
1516
|
+
const defaultCellMinWidth = this.options.defaultCellMinWidth;
|
|
1517
|
+
const constrainToContainer = this.options.constrainToContainer;
|
|
1518
|
+
if (!ViewClass) {
|
|
1519
|
+
return void 0;
|
|
1520
|
+
}
|
|
1521
|
+
return ((node, view) => new ViewClass(node, cellMinWidth, view, defaultCellMinWidth, constrainToContainer));
|
|
1522
|
+
},
|
|
1523
|
+
addCommands() {
|
|
1524
|
+
return {
|
|
1525
|
+
insertTable: (options) => ({ state: state$1, tr, dispatch }) => {
|
|
1526
|
+
const $from = state$1.selection.$from;
|
|
1527
|
+
for (let d = $from.depth; d > 0; d--) {
|
|
1528
|
+
const node = $from.node(d);
|
|
1529
|
+
if (node.type.name === "table") return false;
|
|
1530
|
+
if (node.type.spec.code) return false;
|
|
1531
|
+
}
|
|
1532
|
+
const rows = options?.rows ?? 3;
|
|
1533
|
+
const cols = options?.cols ?? 3;
|
|
1534
|
+
const withHeaderRow = options?.withHeaderRow ?? true;
|
|
1535
|
+
const table = createTable(state$1.schema, rows, cols, withHeaderRow);
|
|
1536
|
+
if (!dispatch) {
|
|
1537
|
+
return true;
|
|
1538
|
+
}
|
|
1539
|
+
const offset = tr.selection.from + 1;
|
|
1540
|
+
tr.replaceSelectionWith(table).scrollIntoView().setSelection(state.TextSelection.near(tr.doc.resolve(offset)));
|
|
1541
|
+
dispatch(tr);
|
|
1542
|
+
return true;
|
|
1543
|
+
},
|
|
1544
|
+
deleteTable: () => ({ state, dispatch }) => {
|
|
1545
|
+
return tables.deleteTable(state, dispatch);
|
|
1546
|
+
},
|
|
1547
|
+
addRowBefore: () => ({ state, dispatch }) => {
|
|
1548
|
+
return tables.addRowBefore(state, dispatch);
|
|
1549
|
+
},
|
|
1550
|
+
addRowAfter: () => ({ state, dispatch }) => {
|
|
1551
|
+
return tables.addRowAfter(state, dispatch);
|
|
1552
|
+
},
|
|
1553
|
+
deleteRow: () => ({ state, dispatch }) => {
|
|
1554
|
+
if (!tables.isInTable(state)) return false;
|
|
1555
|
+
const rect = tables.selectedRect(state);
|
|
1556
|
+
if (rect.top === 0 && rect.bottom === rect.map.height) {
|
|
1557
|
+
return tables.deleteTable(state, dispatch);
|
|
1558
|
+
}
|
|
1559
|
+
return tables.deleteRow(state, dispatch);
|
|
1560
|
+
},
|
|
1561
|
+
addColumnBefore: () => ({ state, dispatch, editor }) => {
|
|
1562
|
+
if (!this.options.constrainToContainer || !dispatch) {
|
|
1563
|
+
return tables.addColumnBefore(state, dispatch);
|
|
1564
|
+
}
|
|
1565
|
+
const view = editor.view;
|
|
1566
|
+
return constrainedAddColumn(tables.addColumnBefore, view, this.options.cellMinWidth, this.options.defaultCellMinWidth);
|
|
1567
|
+
},
|
|
1568
|
+
addColumnAfter: () => ({ state, dispatch, editor }) => {
|
|
1569
|
+
if (!this.options.constrainToContainer || !dispatch) {
|
|
1570
|
+
return tables.addColumnAfter(state, dispatch);
|
|
1571
|
+
}
|
|
1572
|
+
const view = editor.view;
|
|
1573
|
+
return constrainedAddColumn(tables.addColumnAfter, view, this.options.cellMinWidth, this.options.defaultCellMinWidth);
|
|
1574
|
+
},
|
|
1575
|
+
deleteColumn: () => ({ state: state$1, dispatch }) => {
|
|
1576
|
+
if (!tables.isInTable(state$1)) return false;
|
|
1577
|
+
const rect = tables.selectedRect(state$1);
|
|
1578
|
+
if (rect.left === 0 && rect.right === rect.map.width) {
|
|
1579
|
+
return tables.deleteTable(state$1, dispatch);
|
|
1580
|
+
}
|
|
1581
|
+
if (!dispatch) return true;
|
|
1582
|
+
let captured;
|
|
1583
|
+
tables.deleteColumn(state$1, (tr) => {
|
|
1584
|
+
captured = tr;
|
|
1585
|
+
});
|
|
1586
|
+
if (!captured) return false;
|
|
1587
|
+
const table = captured.doc.nodeAt(rect.tableStart - 1);
|
|
1588
|
+
if (table) {
|
|
1589
|
+
const map = tables.TableMap.get(table);
|
|
1590
|
+
const targetCol = Math.min(rect.left, map.width - 1);
|
|
1591
|
+
const cellOffset = map.map[targetCol];
|
|
1592
|
+
if (cellOffset !== void 0) {
|
|
1593
|
+
const $pos = captured.doc.resolve(rect.tableStart + cellOffset + 1);
|
|
1594
|
+
captured.setSelection(state.TextSelection.near($pos));
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
dispatch(captured);
|
|
1598
|
+
return true;
|
|
1599
|
+
},
|
|
1600
|
+
toggleHeaderRow: () => ({ state, dispatch }) => {
|
|
1601
|
+
return tables.toggleHeader("row")(state, dispatch);
|
|
1602
|
+
},
|
|
1603
|
+
toggleHeaderColumn: () => ({ state, dispatch }) => {
|
|
1604
|
+
return tables.toggleHeader("column")(state, dispatch);
|
|
1605
|
+
},
|
|
1606
|
+
toggleHeaderCell: () => ({ state, dispatch }) => {
|
|
1607
|
+
return tables.toggleHeaderCell(state, dispatch);
|
|
1608
|
+
},
|
|
1609
|
+
mergeCells: () => ({ state, dispatch }) => {
|
|
1610
|
+
return tables.mergeCells(state, dispatch);
|
|
1611
|
+
},
|
|
1612
|
+
splitCell: () => ({ state, dispatch }) => {
|
|
1613
|
+
return tables.splitCell(state, dispatch);
|
|
1614
|
+
},
|
|
1615
|
+
setCellAttribute: (name, value) => ({ state, dispatch }) => {
|
|
1616
|
+
return tables.setCellAttr(name, value)(state, dispatch);
|
|
1617
|
+
},
|
|
1618
|
+
goToNextCell: () => ({ state, dispatch }) => {
|
|
1619
|
+
return tables.goToNextCell(1)(state, dispatch);
|
|
1620
|
+
},
|
|
1621
|
+
goToPreviousCell: () => ({ state, dispatch }) => {
|
|
1622
|
+
return tables.goToNextCell(-1)(state, dispatch);
|
|
1623
|
+
},
|
|
1624
|
+
fixTables: () => ({ state, dispatch }) => {
|
|
1625
|
+
if (dispatch) {
|
|
1626
|
+
const tr = tables.fixTables(state);
|
|
1627
|
+
if (tr) dispatch(tr);
|
|
1628
|
+
}
|
|
1629
|
+
return true;
|
|
1630
|
+
},
|
|
1631
|
+
setCellSelection: (position) => ({ tr, dispatch }) => {
|
|
1632
|
+
const selection = tables.CellSelection.create(
|
|
1633
|
+
tr.doc,
|
|
1634
|
+
position.anchorCell,
|
|
1635
|
+
position.headCell
|
|
1636
|
+
);
|
|
1637
|
+
tr.setSelection(selection);
|
|
1638
|
+
if (dispatch) {
|
|
1639
|
+
dispatch(tr);
|
|
1640
|
+
}
|
|
1641
|
+
return true;
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1644
|
+
},
|
|
1645
|
+
addKeyboardShortcuts() {
|
|
1646
|
+
const editor = this.editor;
|
|
1647
|
+
const isInListItem = () => {
|
|
1648
|
+
if (!editor) return false;
|
|
1649
|
+
const { $from } = editor.state.selection;
|
|
1650
|
+
for (let d = $from.depth; d >= 0; d--) {
|
|
1651
|
+
const name = $from.node(d).type.name;
|
|
1652
|
+
if (name === "listItem" || name === "taskItem") return true;
|
|
1653
|
+
}
|
|
1654
|
+
return false;
|
|
1655
|
+
};
|
|
1656
|
+
const deleteTableHandler = () => {
|
|
1657
|
+
if (!editor) return false;
|
|
1658
|
+
return deleteTableWhenAllCellsSelected({
|
|
1659
|
+
state: editor.state,
|
|
1660
|
+
dispatch: editor.view.dispatch
|
|
1661
|
+
});
|
|
1662
|
+
};
|
|
1663
|
+
return {
|
|
1664
|
+
Tab: () => {
|
|
1665
|
+
if (!editor || isInListItem()) return false;
|
|
1666
|
+
if (editor.commands["goToNextCell"]?.()) {
|
|
1667
|
+
return true;
|
|
1668
|
+
}
|
|
1669
|
+
if (editor.commands["addRowAfter"]?.()) {
|
|
1670
|
+
editor.commands["goToNextCell"]?.();
|
|
1671
|
+
return true;
|
|
1672
|
+
}
|
|
1673
|
+
return false;
|
|
1674
|
+
},
|
|
1675
|
+
"Shift-Tab": () => {
|
|
1676
|
+
if (!editor || isInListItem()) return false;
|
|
1677
|
+
return editor.commands["goToPreviousCell"]?.() ?? false;
|
|
1678
|
+
},
|
|
1679
|
+
Backspace: deleteTableHandler,
|
|
1680
|
+
"Mod-Backspace": deleteTableHandler,
|
|
1681
|
+
Delete: deleteTableHandler,
|
|
1682
|
+
"Mod-Delete": deleteTableHandler
|
|
1683
|
+
};
|
|
1684
|
+
},
|
|
1685
|
+
addToolbarItems() {
|
|
1686
|
+
return [
|
|
1687
|
+
{
|
|
1688
|
+
type: "button",
|
|
1689
|
+
name: "insertTable",
|
|
1690
|
+
command: "insertTable",
|
|
1691
|
+
icon: "table",
|
|
1692
|
+
label: "Insert Table",
|
|
1693
|
+
group: "insert",
|
|
1694
|
+
priority: 140
|
|
1695
|
+
}
|
|
1696
|
+
];
|
|
1697
|
+
},
|
|
1698
|
+
addProseMirrorPlugins() {
|
|
1699
|
+
return [
|
|
1700
|
+
createResizeSuppressionPlugin({
|
|
1701
|
+
resizeBehavior: this.options.resizeBehavior,
|
|
1702
|
+
cellMinWidth: this.options.cellMinWidth,
|
|
1703
|
+
defaultCellMinWidth: this.options.defaultCellMinWidth,
|
|
1704
|
+
constrainToContainer: this.options.constrainToContainer
|
|
1705
|
+
}),
|
|
1706
|
+
tables.columnResizing({
|
|
1707
|
+
cellMinWidth: this.options.cellMinWidth,
|
|
1708
|
+
defaultCellMinWidth: this.options.defaultCellMinWidth
|
|
1709
|
+
}),
|
|
1710
|
+
tables.tableEditing({
|
|
1711
|
+
allowTableNodeSelection: this.options.allowTableNodeSelection
|
|
1712
|
+
}),
|
|
1713
|
+
createCellSelectionPlugin()
|
|
1714
|
+
];
|
|
1715
|
+
}
|
|
1716
|
+
});
|
|
1717
|
+
|
|
1718
|
+
Object.defineProperty(exports, "CellSelection", {
|
|
1719
|
+
enumerable: true,
|
|
1720
|
+
get: function () { return tables.CellSelection; }
|
|
1721
|
+
});
|
|
1722
|
+
Object.defineProperty(exports, "TableMap", {
|
|
1723
|
+
enumerable: true,
|
|
1724
|
+
get: function () { return tables.TableMap; }
|
|
1725
|
+
});
|
|
1726
|
+
exports.Table = Table;
|
|
1727
|
+
exports.TableCell = TableCell;
|
|
1728
|
+
exports.TableHeader = TableHeader;
|
|
1729
|
+
exports.TableRow = TableRow;
|
|
1730
|
+
exports.TableView = TableView;
|
|
1731
|
+
exports.createTable = createTable;
|
|
1732
|
+
exports.deleteTableWhenAllCellsSelected = deleteTableWhenAllCellsSelected;
|
|
1733
|
+
//# sourceMappingURL=index.cjs.map
|
|
1734
|
+
//# sourceMappingURL=index.cjs.map
|