@kerebron/extension-tables 0.0.1
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 +23 -0
- package/README.md +34 -0
- package/assets/tables.css +48 -0
- package/esm/ExtensionTables.d.ts +10 -0
- package/esm/ExtensionTables.d.ts.map +1 -0
- package/esm/ExtensionTables.js +27 -0
- package/esm/NodeTable.d.ts +24 -0
- package/esm/NodeTable.d.ts.map +1 -0
- package/esm/NodeTable.js +115 -0
- package/esm/NodeTableCell.d.ts +16 -0
- package/esm/NodeTableCell.d.ts.map +1 -0
- package/esm/NodeTableCell.js +77 -0
- package/esm/NodeTableHeader.d.ts +16 -0
- package/esm/NodeTableHeader.d.ts.map +1 -0
- package/esm/NodeTableHeader.js +75 -0
- package/esm/NodeTableRow.d.ts +16 -0
- package/esm/NodeTableRow.d.ts.map +1 -0
- package/esm/NodeTableRow.js +47 -0
- package/esm/_dnt.shims.d.ts +6 -0
- package/esm/_dnt.shims.d.ts.map +1 -0
- package/esm/_dnt.shims.js +61 -0
- package/esm/package.json +3 -0
- package/esm/utilities/CellSelection.d.ts +53 -0
- package/esm/utilities/CellSelection.d.ts.map +1 -0
- package/esm/utilities/CellSelection.js +382 -0
- package/esm/utilities/TableMap.d.ts +92 -0
- package/esm/utilities/TableMap.d.ts.map +1 -0
- package/esm/utilities/TableMap.js +335 -0
- package/esm/utilities/TableView.d.ts +21 -0
- package/esm/utilities/TableView.d.ts.map +1 -0
- package/esm/utilities/TableView.js +108 -0
- package/esm/utilities/columnResizing.d.ts +50 -0
- package/esm/utilities/columnResizing.d.ts.map +1 -0
- package/esm/utilities/columnResizing.js +307 -0
- package/esm/utilities/commands.d.ts +166 -0
- package/esm/utilities/commands.d.ts.map +1 -0
- package/esm/utilities/commands.js +702 -0
- package/esm/utilities/copypaste.d.ts +35 -0
- package/esm/utilities/copypaste.d.ts.map +1 -0
- package/esm/utilities/copypaste.js +283 -0
- package/esm/utilities/createCell.d.ts +3 -0
- package/esm/utilities/createCell.d.ts.map +1 -0
- package/esm/utilities/createCell.js +6 -0
- package/esm/utilities/createTable.d.ts +3 -0
- package/esm/utilities/createTable.d.ts.map +1 -0
- package/esm/utilities/createTable.js +30 -0
- package/esm/utilities/fixTables.d.ts +18 -0
- package/esm/utilities/fixTables.d.ts.map +1 -0
- package/esm/utilities/fixTables.js +146 -0
- package/esm/utilities/getTableNodeTypes.d.ts +5 -0
- package/esm/utilities/getTableNodeTypes.d.ts.map +1 -0
- package/esm/utilities/getTableNodeTypes.js +14 -0
- package/esm/utilities/input.d.ts +21 -0
- package/esm/utilities/input.d.ts.map +1 -0
- package/esm/utilities/input.js +241 -0
- package/esm/utilities/tableEditing.d.ts +23 -0
- package/esm/utilities/tableEditing.d.ts.map +1 -0
- package/esm/utilities/tableEditing.js +63 -0
- package/esm/utilities/tableNodeTypes.d.ts +14 -0
- package/esm/utilities/tableNodeTypes.d.ts.map +1 -0
- package/esm/utilities/tableNodeTypes.js +16 -0
- package/esm/utilities/util.d.ts +73 -0
- package/esm/utilities/util.d.ts.map +1 -0
- package/esm/utilities/util.js +155 -0
- package/package.json +30 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Node, ResolvedPos, Slice } from 'prosemirror-model';
|
|
2
|
+
import { EditorState, Selection, Transaction } from 'prosemirror-state';
|
|
3
|
+
import { DecorationSource } from 'prosemirror-view';
|
|
4
|
+
import { Mappable } from 'prosemirror-transform';
|
|
5
|
+
/**
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
export interface CellSelectionJSON {
|
|
9
|
+
type: string;
|
|
10
|
+
anchor: number;
|
|
11
|
+
head: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A [`Selection`](http://prosemirror.net/docs/ref/#state.Selection)
|
|
15
|
+
* subclass that represents a cell selection spanning part of a table.
|
|
16
|
+
* With the plugin enabled, these will be created when the user
|
|
17
|
+
* selects across cells, and will be drawn by giving selected cells a
|
|
18
|
+
* `selectedCell` CSS class.
|
|
19
|
+
*
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
export declare class CellSelection extends Selection {
|
|
23
|
+
$anchorCell: ResolvedPos;
|
|
24
|
+
$headCell: ResolvedPos;
|
|
25
|
+
constructor($anchorCell: ResolvedPos, $headCell?: ResolvedPos);
|
|
26
|
+
map(doc: Node, mapping: Mappable): CellSelection | Selection;
|
|
27
|
+
content(): Slice;
|
|
28
|
+
replace(tr: Transaction, content?: Slice): void;
|
|
29
|
+
replaceWith(tr: Transaction, node: Node): void;
|
|
30
|
+
forEachCell(f: (node: Node, pos: number) => void): void;
|
|
31
|
+
isColSelection(): boolean;
|
|
32
|
+
static colSelection($anchorCell: ResolvedPos, $headCell?: ResolvedPos): CellSelection;
|
|
33
|
+
isRowSelection(): boolean;
|
|
34
|
+
eq(other: unknown): boolean;
|
|
35
|
+
static rowSelection($anchorCell: ResolvedPos, $headCell?: ResolvedPos): CellSelection;
|
|
36
|
+
toJSON(): CellSelectionJSON;
|
|
37
|
+
static fromJSON(doc: Node, json: CellSelectionJSON): CellSelection;
|
|
38
|
+
static create(doc: Node, anchorCell: number, headCell?: number): CellSelection;
|
|
39
|
+
getBookmark(): CellBookmark;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
export declare class CellBookmark {
|
|
45
|
+
anchor: number;
|
|
46
|
+
head: number;
|
|
47
|
+
constructor(anchor: number, head: number);
|
|
48
|
+
map(mapping: Mappable): CellBookmark;
|
|
49
|
+
resolve(doc: Node): CellSelection | Selection;
|
|
50
|
+
}
|
|
51
|
+
export declare function drawCellSelection(state: EditorState): DecorationSource | null;
|
|
52
|
+
export declare function normalizeSelection(state: EditorState, tr: Transaction | undefined, allowTableNodeSelection: boolean): Transaction | undefined;
|
|
53
|
+
//# sourceMappingURL=CellSelection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CellSelection.d.ts","sourceRoot":"","sources":["../../src/utilities/CellSelection.ts"],"names":[],"mappings":"AAKA,OAAO,EAAY,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EACL,WAAW,EAEX,SAAS,EAGT,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAA6B,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAE/E,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAIjD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;GAQG;AACH,qBAAa,aAAc,SAAQ,SAAS;IAGnC,WAAW,EAAE,WAAW,CAAC;IAIzB,SAAS,EAAE,WAAW,CAAC;gBAMlB,WAAW,EAAE,WAAW,EAAE,SAAS,GAAE,WAAyB;IAgCnE,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,GAAG,aAAa,GAAG,SAAS;IAoB5D,OAAO,IAAI,KAAK;IA6EhB,OAAO,CAAC,EAAE,EAAE,WAAW,EAAE,OAAO,GAAE,KAAmB,GAAG,IAAI;IAmB5D,WAAW,CAAC,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI;IAI9C,WAAW,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAkBvD,cAAc,IAAI,OAAO;WAelB,YAAY,CACxB,WAAW,EAAE,WAAW,EACxB,SAAS,GAAE,WAAyB,GACnC,aAAa;IAmCT,cAAc,IAAI,OAAO;IAczB,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;WAUpB,YAAY,CACxB,WAAW,EAAE,WAAW,EACxB,SAAS,GAAE,WAAyB,GACnC,aAAa;IAiCT,MAAM,IAAI,iBAAiB;IAQlC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,iBAAiB,GAAG,aAAa;IAIlE,MAAM,CAAC,MAAM,CACX,GAAG,EAAE,IAAI,EACT,UAAU,EAAE,MAAM,EAClB,QAAQ,GAAE,MAAmB,GAC5B,aAAa;IAIhB,WAAW,IAAI,YAAY;CAG5B;AAMD;;GAEG;AACH,qBAAa,YAAY;IAEd,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,MAAM;gBADZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM;IAGrB,GAAG,CAAC,OAAO,EAAE,QAAQ,GAAG,YAAY;IAIpC,OAAO,CAAC,GAAG,EAAE,IAAI,GAAG,aAAa,GAAG,SAAS;CAa9C;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,WAAW,GAAG,gBAAgB,GAAG,IAAI,CAS7E;AAgDD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,WAAW,EAClB,EAAE,EAAE,WAAW,GAAG,SAAS,EAC3B,uBAAuB,EAAE,OAAO,GAC/B,WAAW,GAAG,SAAS,CAwBzB"}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
// This file defines a ProseMirror selection subclass that models
|
|
2
|
+
// table cell selections. The table plugin needs to be active to wire
|
|
3
|
+
// in the user interaction part of table selections (so that you
|
|
4
|
+
// actually get such selections when you select across cells).
|
|
5
|
+
import { Fragment, Slice } from 'prosemirror-model';
|
|
6
|
+
import { NodeSelection, Selection, SelectionRange, TextSelection, } from 'prosemirror-state';
|
|
7
|
+
import { Decoration, DecorationSet } from 'prosemirror-view';
|
|
8
|
+
import { TableMap } from './TableMap.js';
|
|
9
|
+
import { inSameTable, pointsAtCell, removeColSpan } from './util.js';
|
|
10
|
+
/**
|
|
11
|
+
* A [`Selection`](http://prosemirror.net/docs/ref/#state.Selection)
|
|
12
|
+
* subclass that represents a cell selection spanning part of a table.
|
|
13
|
+
* With the plugin enabled, these will be created when the user
|
|
14
|
+
* selects across cells, and will be drawn by giving selected cells a
|
|
15
|
+
* `selectedCell` CSS class.
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
export class CellSelection extends Selection {
|
|
20
|
+
// A table selection is identified by its anchor and head cells. The
|
|
21
|
+
// positions given to this constructor should point _before_ two
|
|
22
|
+
// cells in the same table. They may be the same, to select a single
|
|
23
|
+
// cell.
|
|
24
|
+
constructor($anchorCell, $headCell = $anchorCell) {
|
|
25
|
+
const table = $anchorCell.node(-1);
|
|
26
|
+
const map = TableMap.get(table);
|
|
27
|
+
const tableStart = $anchorCell.start(-1);
|
|
28
|
+
const rect = map.rectBetween($anchorCell.pos - tableStart, $headCell.pos - tableStart);
|
|
29
|
+
const doc = $anchorCell.node(0);
|
|
30
|
+
const cells = map
|
|
31
|
+
.cellsInRect(rect)
|
|
32
|
+
.filter((p) => p != $headCell.pos - tableStart);
|
|
33
|
+
// Make the head cell the first range, so that it counts as the
|
|
34
|
+
// primary part of the selection
|
|
35
|
+
cells.unshift($headCell.pos - tableStart);
|
|
36
|
+
const ranges = cells.map((pos) => {
|
|
37
|
+
const cell = table.nodeAt(pos);
|
|
38
|
+
if (!cell) {
|
|
39
|
+
throw RangeError(`No cell with offset ${pos} found`);
|
|
40
|
+
}
|
|
41
|
+
const from = tableStart + pos + 1;
|
|
42
|
+
return new SelectionRange(doc.resolve(from), doc.resolve(from + cell.content.size));
|
|
43
|
+
});
|
|
44
|
+
super(ranges[0].$from, ranges[0].$to, ranges);
|
|
45
|
+
// A resolved position pointing _in front of_ the anchor cell (the one
|
|
46
|
+
// that doesn't move when extending the selection).
|
|
47
|
+
Object.defineProperty(this, "$anchorCell", {
|
|
48
|
+
enumerable: true,
|
|
49
|
+
configurable: true,
|
|
50
|
+
writable: true,
|
|
51
|
+
value: void 0
|
|
52
|
+
});
|
|
53
|
+
// A resolved position pointing in front of the head cell (the one
|
|
54
|
+
// moves when extending the selection).
|
|
55
|
+
Object.defineProperty(this, "$headCell", {
|
|
56
|
+
enumerable: true,
|
|
57
|
+
configurable: true,
|
|
58
|
+
writable: true,
|
|
59
|
+
value: void 0
|
|
60
|
+
});
|
|
61
|
+
this.$anchorCell = $anchorCell;
|
|
62
|
+
this.$headCell = $headCell;
|
|
63
|
+
}
|
|
64
|
+
map(doc, mapping) {
|
|
65
|
+
const $anchorCell = doc.resolve(mapping.map(this.$anchorCell.pos));
|
|
66
|
+
const $headCell = doc.resolve(mapping.map(this.$headCell.pos));
|
|
67
|
+
if (pointsAtCell($anchorCell) &&
|
|
68
|
+
pointsAtCell($headCell) &&
|
|
69
|
+
inSameTable($anchorCell, $headCell)) {
|
|
70
|
+
const tableChanged = this.$anchorCell.node(-1) != $anchorCell.node(-1);
|
|
71
|
+
if (tableChanged && this.isRowSelection()) {
|
|
72
|
+
return CellSelection.rowSelection($anchorCell, $headCell);
|
|
73
|
+
}
|
|
74
|
+
else if (tableChanged && this.isColSelection()) {
|
|
75
|
+
return CellSelection.colSelection($anchorCell, $headCell);
|
|
76
|
+
}
|
|
77
|
+
else
|
|
78
|
+
return new CellSelection($anchorCell, $headCell);
|
|
79
|
+
}
|
|
80
|
+
return TextSelection.between($anchorCell, $headCell);
|
|
81
|
+
}
|
|
82
|
+
// Returns a rectangular slice of table rows containing the selected
|
|
83
|
+
// cells.
|
|
84
|
+
content() {
|
|
85
|
+
const table = this.$anchorCell.node(-1);
|
|
86
|
+
const map = TableMap.get(table);
|
|
87
|
+
const tableStart = this.$anchorCell.start(-1);
|
|
88
|
+
const rect = map.rectBetween(this.$anchorCell.pos - tableStart, this.$headCell.pos - tableStart);
|
|
89
|
+
const seen = {};
|
|
90
|
+
const rows = [];
|
|
91
|
+
for (let row = rect.top; row < rect.bottom; row++) {
|
|
92
|
+
const rowContent = [];
|
|
93
|
+
for (let index = row * map.width + rect.left, col = rect.left; col < rect.right; col++, index++) {
|
|
94
|
+
const pos = map.map[index];
|
|
95
|
+
if (seen[pos])
|
|
96
|
+
continue;
|
|
97
|
+
seen[pos] = true;
|
|
98
|
+
const cellRect = map.findCell(pos);
|
|
99
|
+
let cell = table.nodeAt(pos);
|
|
100
|
+
if (!cell) {
|
|
101
|
+
throw RangeError(`No cell with offset ${pos} found`);
|
|
102
|
+
}
|
|
103
|
+
const extraLeft = rect.left - cellRect.left;
|
|
104
|
+
const extraRight = cellRect.right - rect.right;
|
|
105
|
+
if (extraLeft > 0 || extraRight > 0) {
|
|
106
|
+
let attrs = cell.attrs;
|
|
107
|
+
if (extraLeft > 0) {
|
|
108
|
+
attrs = removeColSpan(attrs, 0, extraLeft);
|
|
109
|
+
}
|
|
110
|
+
if (extraRight > 0) {
|
|
111
|
+
attrs = removeColSpan(attrs, attrs.colspan - extraRight, extraRight);
|
|
112
|
+
}
|
|
113
|
+
if (cellRect.left < rect.left) {
|
|
114
|
+
cell = cell.type.createAndFill(attrs);
|
|
115
|
+
if (!cell) {
|
|
116
|
+
throw RangeError(`Could not create cell with attrs ${JSON.stringify(attrs)}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
cell = cell.type.create(attrs, cell.content);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (cellRect.top < rect.top || cellRect.bottom > rect.bottom) {
|
|
124
|
+
const attrs = {
|
|
125
|
+
...cell.attrs,
|
|
126
|
+
rowspan: Math.min(cellRect.bottom, rect.bottom) -
|
|
127
|
+
Math.max(cellRect.top, rect.top),
|
|
128
|
+
};
|
|
129
|
+
if (cellRect.top < rect.top) {
|
|
130
|
+
cell = cell.type.createAndFill(attrs);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
cell = cell.type.create(attrs, cell.content);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
rowContent.push(cell);
|
|
137
|
+
}
|
|
138
|
+
rows.push(table.child(row).copy(Fragment.from(rowContent)));
|
|
139
|
+
}
|
|
140
|
+
const fragment = this.isColSelection() && this.isRowSelection()
|
|
141
|
+
? table
|
|
142
|
+
: rows;
|
|
143
|
+
return new Slice(Fragment.from(fragment), 1, 1);
|
|
144
|
+
}
|
|
145
|
+
replace(tr, content = Slice.empty) {
|
|
146
|
+
const mapFrom = tr.steps.length, ranges = this.ranges;
|
|
147
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
148
|
+
const { $from, $to } = ranges[i], mapping = tr.mapping.slice(mapFrom);
|
|
149
|
+
tr.replace(mapping.map($from.pos), mapping.map($to.pos), i ? Slice.empty : content);
|
|
150
|
+
}
|
|
151
|
+
const sel = Selection.findFrom(tr.doc.resolve(tr.mapping.slice(mapFrom).map(this.to)), -1);
|
|
152
|
+
if (sel)
|
|
153
|
+
tr.setSelection(sel);
|
|
154
|
+
}
|
|
155
|
+
replaceWith(tr, node) {
|
|
156
|
+
this.replace(tr, new Slice(Fragment.from(node), 0, 0));
|
|
157
|
+
}
|
|
158
|
+
forEachCell(f) {
|
|
159
|
+
const table = this.$anchorCell.node(-1);
|
|
160
|
+
const map = TableMap.get(table);
|
|
161
|
+
const tableStart = this.$anchorCell.start(-1);
|
|
162
|
+
const cells = map.cellsInRect(map.rectBetween(this.$anchorCell.pos - tableStart, this.$headCell.pos - tableStart));
|
|
163
|
+
for (let i = 0; i < cells.length; i++) {
|
|
164
|
+
f(table.nodeAt(cells[i]), tableStart + cells[i]);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// True if this selection goes all the way from the top to the
|
|
168
|
+
// bottom of the table.
|
|
169
|
+
isColSelection() {
|
|
170
|
+
const anchorTop = this.$anchorCell.index(-1);
|
|
171
|
+
const headTop = this.$headCell.index(-1);
|
|
172
|
+
if (Math.min(anchorTop, headTop) > 0)
|
|
173
|
+
return false;
|
|
174
|
+
const anchorBottom = anchorTop + this.$anchorCell.nodeAfter.attrs.rowspan;
|
|
175
|
+
const headBottom = headTop + this.$headCell.nodeAfter.attrs.rowspan;
|
|
176
|
+
return (Math.max(anchorBottom, headBottom) == this.$headCell.node(-1).childCount);
|
|
177
|
+
}
|
|
178
|
+
// Returns the smallest column selection that covers the given anchor
|
|
179
|
+
// and head cell.
|
|
180
|
+
static colSelection($anchorCell, $headCell = $anchorCell) {
|
|
181
|
+
const table = $anchorCell.node(-1);
|
|
182
|
+
const map = TableMap.get(table);
|
|
183
|
+
const tableStart = $anchorCell.start(-1);
|
|
184
|
+
const anchorRect = map.findCell($anchorCell.pos - tableStart);
|
|
185
|
+
const headRect = map.findCell($headCell.pos - tableStart);
|
|
186
|
+
const doc = $anchorCell.node(0);
|
|
187
|
+
if (anchorRect.top <= headRect.top) {
|
|
188
|
+
if (anchorRect.top > 0) {
|
|
189
|
+
$anchorCell = doc.resolve(tableStart + map.map[anchorRect.left]);
|
|
190
|
+
}
|
|
191
|
+
if (headRect.bottom < map.height) {
|
|
192
|
+
$headCell = doc.resolve(tableStart +
|
|
193
|
+
map.map[map.width * (map.height - 1) + headRect.right - 1]);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
if (headRect.top > 0) {
|
|
198
|
+
$headCell = doc.resolve(tableStart + map.map[headRect.left]);
|
|
199
|
+
}
|
|
200
|
+
if (anchorRect.bottom < map.height) {
|
|
201
|
+
$anchorCell = doc.resolve(tableStart +
|
|
202
|
+
map.map[map.width * (map.height - 1) + anchorRect.right - 1]);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return new CellSelection($anchorCell, $headCell);
|
|
206
|
+
}
|
|
207
|
+
// True if this selection goes all the way from the left to the
|
|
208
|
+
// right of the table.
|
|
209
|
+
isRowSelection() {
|
|
210
|
+
const table = this.$anchorCell.node(-1);
|
|
211
|
+
const map = TableMap.get(table);
|
|
212
|
+
const tableStart = this.$anchorCell.start(-1);
|
|
213
|
+
const anchorLeft = map.colCount(this.$anchorCell.pos - tableStart);
|
|
214
|
+
const headLeft = map.colCount(this.$headCell.pos - tableStart);
|
|
215
|
+
if (Math.min(anchorLeft, headLeft) > 0)
|
|
216
|
+
return false;
|
|
217
|
+
const anchorRight = anchorLeft + this.$anchorCell.nodeAfter.attrs.colspan;
|
|
218
|
+
const headRight = headLeft + this.$headCell.nodeAfter.attrs.colspan;
|
|
219
|
+
return Math.max(anchorRight, headRight) == map.width;
|
|
220
|
+
}
|
|
221
|
+
eq(other) {
|
|
222
|
+
return (other instanceof CellSelection &&
|
|
223
|
+
other.$anchorCell.pos == this.$anchorCell.pos &&
|
|
224
|
+
other.$headCell.pos == this.$headCell.pos);
|
|
225
|
+
}
|
|
226
|
+
// Returns the smallest row selection that covers the given anchor
|
|
227
|
+
// and head cell.
|
|
228
|
+
static rowSelection($anchorCell, $headCell = $anchorCell) {
|
|
229
|
+
const table = $anchorCell.node(-1);
|
|
230
|
+
const map = TableMap.get(table);
|
|
231
|
+
const tableStart = $anchorCell.start(-1);
|
|
232
|
+
const anchorRect = map.findCell($anchorCell.pos - tableStart);
|
|
233
|
+
const headRect = map.findCell($headCell.pos - tableStart);
|
|
234
|
+
const doc = $anchorCell.node(0);
|
|
235
|
+
if (anchorRect.left <= headRect.left) {
|
|
236
|
+
if (anchorRect.left > 0) {
|
|
237
|
+
$anchorCell = doc.resolve(tableStart + map.map[anchorRect.top * map.width]);
|
|
238
|
+
}
|
|
239
|
+
if (headRect.right < map.width) {
|
|
240
|
+
$headCell = doc.resolve(tableStart + map.map[map.width * (headRect.top + 1) - 1]);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
if (headRect.left > 0) {
|
|
245
|
+
$headCell = doc.resolve(tableStart + map.map[headRect.top * map.width]);
|
|
246
|
+
}
|
|
247
|
+
if (anchorRect.right < map.width) {
|
|
248
|
+
$anchorCell = doc.resolve(tableStart + map.map[map.width * (anchorRect.top + 1) - 1]);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return new CellSelection($anchorCell, $headCell);
|
|
252
|
+
}
|
|
253
|
+
toJSON() {
|
|
254
|
+
return {
|
|
255
|
+
type: 'cell',
|
|
256
|
+
anchor: this.$anchorCell.pos,
|
|
257
|
+
head: this.$headCell.pos,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
static fromJSON(doc, json) {
|
|
261
|
+
return new CellSelection(doc.resolve(json.anchor), doc.resolve(json.head));
|
|
262
|
+
}
|
|
263
|
+
static create(doc, anchorCell, headCell = anchorCell) {
|
|
264
|
+
return new CellSelection(doc.resolve(anchorCell), doc.resolve(headCell));
|
|
265
|
+
}
|
|
266
|
+
getBookmark() {
|
|
267
|
+
return new CellBookmark(this.$anchorCell.pos, this.$headCell.pos);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
CellSelection.prototype.visible = false;
|
|
271
|
+
Selection.jsonID('cell', CellSelection);
|
|
272
|
+
/**
|
|
273
|
+
* @public
|
|
274
|
+
*/
|
|
275
|
+
export class CellBookmark {
|
|
276
|
+
constructor(anchor, head) {
|
|
277
|
+
Object.defineProperty(this, "anchor", {
|
|
278
|
+
enumerable: true,
|
|
279
|
+
configurable: true,
|
|
280
|
+
writable: true,
|
|
281
|
+
value: anchor
|
|
282
|
+
});
|
|
283
|
+
Object.defineProperty(this, "head", {
|
|
284
|
+
enumerable: true,
|
|
285
|
+
configurable: true,
|
|
286
|
+
writable: true,
|
|
287
|
+
value: head
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
map(mapping) {
|
|
291
|
+
return new CellBookmark(mapping.map(this.anchor), mapping.map(this.head));
|
|
292
|
+
}
|
|
293
|
+
resolve(doc) {
|
|
294
|
+
const $anchorCell = doc.resolve(this.anchor), $headCell = doc.resolve(this.head);
|
|
295
|
+
if ($anchorCell.parent.type.spec.tableRole == 'row' &&
|
|
296
|
+
$headCell.parent.type.spec.tableRole == 'row' &&
|
|
297
|
+
$anchorCell.index() < $anchorCell.parent.childCount &&
|
|
298
|
+
$headCell.index() < $headCell.parent.childCount &&
|
|
299
|
+
inSameTable($anchorCell, $headCell)) {
|
|
300
|
+
return new CellSelection($anchorCell, $headCell);
|
|
301
|
+
}
|
|
302
|
+
else
|
|
303
|
+
return Selection.near($headCell, 1);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
export function drawCellSelection(state) {
|
|
307
|
+
if (!(state.selection instanceof CellSelection))
|
|
308
|
+
return null;
|
|
309
|
+
const cells = [];
|
|
310
|
+
state.selection.forEachCell((node, pos) => {
|
|
311
|
+
cells.push(Decoration.node(pos, pos + node.nodeSize, { class: 'selectedCell' }));
|
|
312
|
+
});
|
|
313
|
+
return DecorationSet.create(state.doc, cells);
|
|
314
|
+
}
|
|
315
|
+
function isCellBoundarySelection({ $from, $to }) {
|
|
316
|
+
if ($from.pos == $to.pos || $from.pos < $to.pos - 6)
|
|
317
|
+
return false; // Cheap elimination
|
|
318
|
+
let afterFrom = $from.pos;
|
|
319
|
+
let beforeTo = $to.pos;
|
|
320
|
+
let depth = $from.depth;
|
|
321
|
+
for (; depth >= 0; depth--, afterFrom++) {
|
|
322
|
+
if ($from.after(depth + 1) < $from.end(depth))
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
for (let d = $to.depth; d >= 0; d--, beforeTo--) {
|
|
326
|
+
if ($to.before(d + 1) > $to.start(d))
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
return (afterFrom == beforeTo &&
|
|
330
|
+
/row|table/.test($from.node(depth).type.spec.tableRole));
|
|
331
|
+
}
|
|
332
|
+
function isTextSelectionAcrossCells({ $from, $to }) {
|
|
333
|
+
let fromCellBoundaryNode;
|
|
334
|
+
let toCellBoundaryNode;
|
|
335
|
+
for (let i = $from.depth; i > 0; i--) {
|
|
336
|
+
const node = $from.node(i);
|
|
337
|
+
if (node.type.spec.tableRole === 'cell' ||
|
|
338
|
+
node.type.spec.tableRole === 'header_cell') {
|
|
339
|
+
fromCellBoundaryNode = node;
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
for (let i = $to.depth; i > 0; i--) {
|
|
344
|
+
const node = $to.node(i);
|
|
345
|
+
if (node.type.spec.tableRole === 'cell' ||
|
|
346
|
+
node.type.spec.tableRole === 'header_cell') {
|
|
347
|
+
toCellBoundaryNode = node;
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return fromCellBoundaryNode !== toCellBoundaryNode && $to.parentOffset === 0;
|
|
352
|
+
}
|
|
353
|
+
export function normalizeSelection(state, tr, allowTableNodeSelection) {
|
|
354
|
+
const sel = (tr || state).selection;
|
|
355
|
+
const doc = (tr || state).doc;
|
|
356
|
+
let normalize;
|
|
357
|
+
let role;
|
|
358
|
+
if (sel instanceof NodeSelection && (role = sel.node.type.spec.tableRole)) {
|
|
359
|
+
if (role == 'cell' || role == 'header_cell') {
|
|
360
|
+
normalize = CellSelection.create(doc, sel.from);
|
|
361
|
+
}
|
|
362
|
+
else if (role == 'row') {
|
|
363
|
+
const $cell = doc.resolve(sel.from + 1);
|
|
364
|
+
normalize = CellSelection.rowSelection($cell, $cell);
|
|
365
|
+
}
|
|
366
|
+
else if (!allowTableNodeSelection) {
|
|
367
|
+
const map = TableMap.get(sel.node);
|
|
368
|
+
const start = sel.from + 1;
|
|
369
|
+
const lastCell = start + map.map[map.width * map.height - 1];
|
|
370
|
+
normalize = CellSelection.create(doc, start + 1, lastCell);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
else if (sel instanceof TextSelection && isCellBoundarySelection(sel)) {
|
|
374
|
+
normalize = TextSelection.create(doc, sel.from);
|
|
375
|
+
}
|
|
376
|
+
else if (sel instanceof TextSelection && isTextSelectionAcrossCells(sel)) {
|
|
377
|
+
normalize = TextSelection.create(doc, sel.$from.start(), sel.$from.end());
|
|
378
|
+
}
|
|
379
|
+
if (normalize)
|
|
380
|
+
(tr || (tr = state.tr)).setSelection(normalize);
|
|
381
|
+
return tr;
|
|
382
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Node } from 'prosemirror-model';
|
|
2
|
+
/**
|
|
3
|
+
* @public
|
|
4
|
+
*/
|
|
5
|
+
export type ColWidths = number[];
|
|
6
|
+
/**
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
export type Problem = {
|
|
10
|
+
type: 'colwidth mismatch';
|
|
11
|
+
pos: number;
|
|
12
|
+
colwidth: ColWidths;
|
|
13
|
+
} | {
|
|
14
|
+
type: 'collision';
|
|
15
|
+
pos: number;
|
|
16
|
+
row: number;
|
|
17
|
+
n: number;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'missing';
|
|
20
|
+
row: number;
|
|
21
|
+
n: number;
|
|
22
|
+
} | {
|
|
23
|
+
type: 'overlong_rowspan';
|
|
24
|
+
pos: number;
|
|
25
|
+
n: number;
|
|
26
|
+
} | {
|
|
27
|
+
type: 'zero_sized';
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
export interface Rect {
|
|
33
|
+
left: number;
|
|
34
|
+
top: number;
|
|
35
|
+
right: number;
|
|
36
|
+
bottom: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A table map describes the structure of a given table. To avoid
|
|
40
|
+
* recomputing them all the time, they are cached per table node. To
|
|
41
|
+
* be able to do that, positions saved in the map are relative to the
|
|
42
|
+
* start of the table, rather than the start of the document.
|
|
43
|
+
*
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
46
|
+
export declare class TableMap {
|
|
47
|
+
/**
|
|
48
|
+
* The number of columns
|
|
49
|
+
*/
|
|
50
|
+
width: number;
|
|
51
|
+
/**
|
|
52
|
+
* The number of rows
|
|
53
|
+
*/
|
|
54
|
+
height: number;
|
|
55
|
+
/**
|
|
56
|
+
* A width * height array with the start position of
|
|
57
|
+
* the cell covering that part of the table in each slot
|
|
58
|
+
*/
|
|
59
|
+
map: number[];
|
|
60
|
+
/**
|
|
61
|
+
* An optional array of problems (cell overlap or non-rectangular
|
|
62
|
+
* shape) for the table, used by the table normalizer.
|
|
63
|
+
*/
|
|
64
|
+
problems: Problem[] | null;
|
|
65
|
+
constructor(
|
|
66
|
+
/**
|
|
67
|
+
* The number of columns
|
|
68
|
+
*/
|
|
69
|
+
width: number,
|
|
70
|
+
/**
|
|
71
|
+
* The number of rows
|
|
72
|
+
*/
|
|
73
|
+
height: number,
|
|
74
|
+
/**
|
|
75
|
+
* A width * height array with the start position of
|
|
76
|
+
* the cell covering that part of the table in each slot
|
|
77
|
+
*/
|
|
78
|
+
map: number[],
|
|
79
|
+
/**
|
|
80
|
+
* An optional array of problems (cell overlap or non-rectangular
|
|
81
|
+
* shape) for the table, used by the table normalizer.
|
|
82
|
+
*/
|
|
83
|
+
problems: Problem[] | null);
|
|
84
|
+
findCell(pos: number): Rect;
|
|
85
|
+
colCount(pos: number): number;
|
|
86
|
+
nextCell(pos: number, axis: 'horiz' | 'vert', dir: number): null | number;
|
|
87
|
+
rectBetween(a: number, b: number): Rect;
|
|
88
|
+
cellsInRect(rect: Rect): number[];
|
|
89
|
+
positionAt(row: number, col: number, table: Node): number;
|
|
90
|
+
static get(table: Node): TableMap;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=TableMap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TableMap.d.ts","sourceRoot":"","sources":["../../src/utilities/TableMap.ts"],"names":[],"mappings":"AAUA,OAAO,EAAS,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAGhD;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC;AAEjC;;GAEG;AACH,MAAM,MAAM,OAAO,GACf;IACA,IAAI,EAAE,mBAAmB,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,SAAS,CAAC;CACrB,GACC;IACA,IAAI,EAAE,WAAW,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,EAAE,MAAM,CAAC;CACX,GACC;IACA,IAAI,EAAE,SAAS,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,EAAE,MAAM,CAAC;CACX,GACC;IACA,IAAI,EAAE,kBAAkB,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,EAAE,MAAM,CAAC;CACX,GACC;IACA,IAAI,EAAE,YAAY,CAAC;CACpB,CAAC;AA+BJ;;GAEG;AACH,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,qBAAa,QAAQ;IAEjB;;OAEG;IACI,KAAK,EAAE,MAAM;IACpB;;OAEG;IACI,MAAM,EAAE,MAAM;IACrB;;;OAGG;IACI,GAAG,EAAE,MAAM,EAAE;IACpB;;;OAGG;IACI,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAI;;IAjBjC;;OAEG;IACI,KAAK,EAAE,MAAM;IACpB;;OAEG;IACI,MAAM,EAAE,MAAM;IACrB;;;OAGG;IACI,GAAG,EAAE,MAAM,EAAE;IACpB;;;OAGG;IACI,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAI;IAInC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IA2B3B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAW7B,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM;IAYzE,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAuBvC,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE;IAyBjC,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,MAAM;IAezD,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,GAAG,QAAQ;CAGlC"}
|