smartrte-core 0.2.1 → 1.0.0-beta.2
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/CHANGELOG.md +16 -0
- package/README.md +23 -0
- package/dist/command.d.ts +8 -8
- package/dist/editor.d.ts +32 -0
- package/dist/editor.js +121 -0
- package/dist/foundation/annotations/index.d.ts +2 -0
- package/dist/foundation/annotations/index.js +2 -0
- package/dist/foundation/annotations/range.d.ts +54 -0
- package/dist/foundation/annotations/range.js +158 -0
- package/dist/foundation/annotations/types.d.ts +33 -0
- package/dist/foundation/annotations/types.js +1 -0
- package/dist/foundation/atom/commands.d.ts +11 -0
- package/dist/foundation/atom/commands.js +118 -0
- package/dist/foundation/atom/composition.d.ts +8 -0
- package/dist/foundation/atom/composition.js +22 -0
- package/dist/foundation/atom/declarations.d.ts +2 -0
- package/dist/foundation/atom/declarations.js +17 -0
- package/dist/foundation/atom/formats.d.ts +24 -0
- package/dist/foundation/atom/formats.js +105 -0
- package/dist/foundation/atom/index.d.ts +10 -0
- package/dist/foundation/atom/index.js +10 -0
- package/dist/foundation/atom/lifecycle.d.ts +12 -0
- package/dist/foundation/atom/lifecycle.js +41 -0
- package/dist/foundation/atom/persistence.d.ts +3 -0
- package/dist/foundation/atom/persistence.js +20 -0
- package/dist/foundation/atom/plugin.d.ts +20 -0
- package/dist/foundation/atom/plugin.js +55 -0
- package/dist/foundation/atom/schema.d.ts +3 -0
- package/dist/foundation/atom/schema.js +29 -0
- package/dist/foundation/atom/security.d.ts +10 -0
- package/dist/foundation/atom/security.js +20 -0
- package/dist/foundation/atom/selection.d.ts +3 -0
- package/dist/foundation/atom/selection.js +4 -0
- package/dist/foundation/atom/types.d.ts +47 -0
- package/dist/foundation/atom/types.js +1 -0
- package/dist/foundation/block/commands.d.ts +17 -0
- package/dist/foundation/block/commands.js +197 -0
- package/dist/foundation/block/formats.d.ts +33 -0
- package/dist/foundation/block/formats.js +57 -0
- package/dist/foundation/block/index.d.ts +5 -0
- package/dist/foundation/block/index.js +5 -0
- package/dist/foundation/block/input.d.ts +19 -0
- package/dist/foundation/block/input.js +67 -0
- package/dist/foundation/block/plugin.d.ts +26 -0
- package/dist/foundation/block/plugin.js +79 -0
- package/dist/foundation/block/schema.d.ts +49 -0
- package/dist/foundation/block/schema.js +27 -0
- package/dist/foundation/block/types.d.ts +40 -0
- package/dist/foundation/block/types.js +1 -0
- package/dist/foundation/boundaries.d.ts +11 -0
- package/dist/foundation/boundaries.js +117 -0
- package/dist/foundation/clipboard/detection.d.ts +3 -0
- package/dist/foundation/clipboard/detection.js +29 -0
- package/dist/foundation/clipboard/diagnostics.d.ts +24 -0
- package/dist/foundation/clipboard/diagnostics.js +59 -0
- package/dist/foundation/clipboard/index.d.ts +8 -0
- package/dist/foundation/clipboard/index.js +8 -0
- package/dist/foundation/clipboard/insertion.d.ts +22 -0
- package/dist/foundation/clipboard/insertion.js +210 -0
- package/dist/foundation/clipboard/normalizers.d.ts +8 -0
- package/dist/foundation/clipboard/normalizers.js +229 -0
- package/dist/foundation/clipboard/pipeline.d.ts +13 -0
- package/dist/foundation/clipboard/pipeline.js +58 -0
- package/dist/foundation/clipboard/sanitizer.d.ts +3 -0
- package/dist/foundation/clipboard/sanitizer.js +58 -0
- package/dist/foundation/clipboard/serialization.d.ts +5 -0
- package/dist/foundation/clipboard/serialization.js +33 -0
- package/dist/foundation/clipboard/transfer.d.ts +11 -0
- package/dist/foundation/clipboard/transfer.js +108 -0
- package/dist/foundation/clipboard/types.d.ts +65 -0
- package/dist/foundation/clipboard/types.js +1 -0
- package/dist/foundation/collab/index.d.ts +3 -0
- package/dist/foundation/collab/index.js +3 -0
- package/dist/foundation/collab/rebase.d.ts +28 -0
- package/dist/foundation/collab/rebase.js +82 -0
- package/dist/foundation/collab/transform.d.ts +73 -0
- package/dist/foundation/collab/transform.js +251 -0
- package/dist/foundation/collab/types.d.ts +52 -0
- package/dist/foundation/collab/types.js +1 -0
- package/dist/foundation/comments/fromSelection.d.ts +16 -0
- package/dist/foundation/comments/fromSelection.js +24 -0
- package/dist/foundation/comments/index.d.ts +3 -0
- package/dist/foundation/comments/index.js +3 -0
- package/dist/foundation/comments/rebase.d.ts +31 -0
- package/dist/foundation/comments/rebase.js +43 -0
- package/dist/foundation/comments/types.d.ts +37 -0
- package/dist/foundation/comments/types.js +1 -0
- package/dist/foundation/diff/documentDiff.d.ts +12 -0
- package/dist/foundation/diff/documentDiff.js +148 -0
- package/dist/foundation/diff/index.d.ts +3 -0
- package/dist/foundation/diff/index.js +3 -0
- package/dist/foundation/diff/textDiff.d.ts +12 -0
- package/dist/foundation/diff/textDiff.js +114 -0
- package/dist/foundation/diff/types.d.ts +74 -0
- package/dist/foundation/diff/types.js +1 -0
- package/dist/foundation/editor.d.ts +138 -0
- package/dist/foundation/editor.js +398 -0
- package/dist/foundation/formats/codec.d.ts +45 -0
- package/dist/foundation/formats/codec.js +1 -0
- package/dist/foundation/formats/docx/export.d.ts +4 -0
- package/dist/foundation/formats/docx/export.js +285 -0
- package/dist/foundation/formats/docx/import.d.ts +9 -0
- package/dist/foundation/formats/docx/import.js +37 -0
- package/dist/foundation/formats/docx/nestingGuard.d.ts +40 -0
- package/dist/foundation/formats/docx/nestingGuard.js +66 -0
- package/dist/foundation/formats/docx/portableAtoms.d.ts +4 -0
- package/dist/foundation/formats/docx/portableAtoms.js +66 -0
- package/dist/foundation/formats/docx/styledImport.d.ts +10 -0
- package/dist/foundation/formats/docx/styledImport.js +205 -0
- package/dist/foundation/formats/featureCodecs.d.ts +37 -0
- package/dist/foundation/formats/featureCodecs.js +69 -0
- package/dist/foundation/formats/fidelity.d.ts +31 -0
- package/dist/foundation/formats/fidelity.js +123 -0
- package/dist/foundation/formats/index.d.ts +7 -0
- package/dist/foundation/formats/index.js +7 -0
- package/dist/foundation/formats/pdf/format.d.ts +29 -0
- package/dist/foundation/formats/pdf/format.js +190 -0
- package/dist/foundation/history.d.ts +20 -0
- package/dist/foundation/history.js +121 -0
- package/dist/foundation/identity.d.ts +8 -0
- package/dist/foundation/identity.js +30 -0
- package/dist/foundation/index.d.ts +30 -0
- package/dist/foundation/index.js +30 -0
- package/dist/foundation/list/commands.d.ts +28 -0
- package/dist/foundation/list/commands.js +422 -0
- package/dist/foundation/list/formats.d.ts +21 -0
- package/dist/foundation/list/formats.js +813 -0
- package/dist/foundation/list/index.d.ts +8 -0
- package/dist/foundation/list/index.js +8 -0
- package/dist/foundation/list/input.d.ts +35 -0
- package/dist/foundation/list/input.js +320 -0
- package/dist/foundation/list/plugin.d.ts +21 -0
- package/dist/foundation/list/plugin.js +71 -0
- package/dist/foundation/list/presets.d.ts +16 -0
- package/dist/foundation/list/presets.js +21 -0
- package/dist/foundation/list/schema.d.ts +35 -0
- package/dist/foundation/list/schema.js +29 -0
- package/dist/foundation/list/shadow.d.ts +54 -0
- package/dist/foundation/list/shadow.js +75 -0
- package/dist/foundation/list/shadowCorpus.d.ts +10 -0
- package/dist/foundation/list/shadowCorpus.js +41 -0
- package/dist/foundation/list/types.d.ts +50 -0
- package/dist/foundation/list/types.js +1 -0
- package/dist/foundation/mapping.d.ts +11 -0
- package/dist/foundation/mapping.js +40 -0
- package/dist/foundation/marks/canonical.d.ts +9 -0
- package/dist/foundation/marks/canonical.js +72 -0
- package/dist/foundation/marks/commands.d.ts +21 -0
- package/dist/foundation/marks/commands.js +136 -0
- package/dist/foundation/marks/dom.d.ts +2 -0
- package/dist/foundation/marks/dom.js +46 -0
- package/dist/foundation/marks/editor.d.ts +8 -0
- package/dist/foundation/marks/editor.js +50 -0
- package/dist/foundation/marks/formats.d.ts +37 -0
- package/dist/foundation/marks/formats.js +49 -0
- package/dist/foundation/marks/hardBreak.d.ts +8 -0
- package/dist/foundation/marks/hardBreak.js +80 -0
- package/dist/foundation/marks/index.d.ts +11 -0
- package/dist/foundation/marks/index.js +11 -0
- package/dist/foundation/marks/normalization.d.ts +2 -0
- package/dist/foundation/marks/normalization.js +49 -0
- package/dist/foundation/marks/plugin.d.ts +14 -0
- package/dist/foundation/marks/plugin.js +63 -0
- package/dist/foundation/marks/resolveMarkRun.d.ts +8 -0
- package/dist/foundation/marks/resolveMarkRun.js +47 -0
- package/dist/foundation/marks/schema.d.ts +167 -0
- package/dist/foundation/marks/schema.js +50 -0
- package/dist/foundation/marks/stored.d.ts +2 -0
- package/dist/foundation/marks/stored.js +31 -0
- package/dist/foundation/marks/types.d.ts +40 -0
- package/dist/foundation/marks/types.js +1 -0
- package/dist/foundation/modelDom.d.ts +38 -0
- package/dist/foundation/modelDom.js +327 -0
- package/dist/foundation/normalization.d.ts +22 -0
- package/dist/foundation/normalization.js +132 -0
- package/dist/foundation/operations.d.ts +28 -0
- package/dist/foundation/operations.js +568 -0
- package/dist/foundation/plugin/builtins.d.ts +10 -0
- package/dist/foundation/plugin/builtins.js +27 -0
- package/dist/foundation/plugin/dispatch.d.ts +33 -0
- package/dist/foundation/plugin/dispatch.js +34 -0
- package/dist/foundation/plugin/index.d.ts +4 -0
- package/dist/foundation/plugin/index.js +4 -0
- package/dist/foundation/plugin/registry.d.ts +36 -0
- package/dist/foundation/plugin/registry.js +115 -0
- package/dist/foundation/plugin/types.d.ts +141 -0
- package/dist/foundation/plugin/types.js +13 -0
- package/dist/foundation/positions.d.ts +20 -0
- package/dist/foundation/positions.js +121 -0
- package/dist/foundation/schema.d.ts +57 -0
- package/dist/foundation/schema.js +371 -0
- package/dist/foundation/schemaBuilder.d.ts +41 -0
- package/dist/foundation/schemaBuilder.js +96 -0
- package/dist/foundation/scope/index.d.ts +2 -0
- package/dist/foundation/scope/index.js +2 -0
- package/dist/foundation/scope/resolveScope.d.ts +13 -0
- package/dist/foundation/scope/resolveScope.js +860 -0
- package/dist/foundation/scope/types.d.ts +122 -0
- package/dist/foundation/scope/types.js +1 -0
- package/dist/foundation/security/urlPolicy.d.ts +21 -0
- package/dist/foundation/security/urlPolicy.js +75 -0
- package/dist/foundation/structural/contentTarget.d.ts +21 -0
- package/dist/foundation/structural/contentTarget.js +51 -0
- package/dist/foundation/structural/index.d.ts +2 -0
- package/dist/foundation/structural/index.js +2 -0
- package/dist/foundation/structural/move.d.ts +7 -0
- package/dist/foundation/structural/move.js +35 -0
- package/dist/foundation/suggestions/index.d.ts +3 -0
- package/dist/foundation/suggestions/index.js +3 -0
- package/dist/foundation/suggestions/inline.d.ts +64 -0
- package/dist/foundation/suggestions/inline.js +116 -0
- package/dist/foundation/suggestions/structural.d.ts +40 -0
- package/dist/foundation/suggestions/structural.js +47 -0
- package/dist/foundation/suggestions/types.d.ts +19 -0
- package/dist/foundation/suggestions/types.js +1 -0
- package/dist/foundation/surface/index.d.ts +3 -0
- package/dist/foundation/surface/index.js +3 -0
- package/dist/foundation/surface/input.d.ts +127 -0
- package/dist/foundation/surface/input.js +1761 -0
- package/dist/foundation/surface/renderer.d.ts +84 -0
- package/dist/foundation/surface/renderer.js +974 -0
- package/dist/foundation/surface/types.d.ts +48 -0
- package/dist/foundation/surface/types.js +1 -0
- package/dist/foundation/table/commands.d.ts +31 -0
- package/dist/foundation/table/commands.js +470 -0
- package/dist/foundation/table/formats.d.ts +27 -0
- package/dist/foundation/table/formats.js +40 -0
- package/dist/foundation/table/grid.d.ts +8 -0
- package/dist/foundation/table/grid.js +178 -0
- package/dist/foundation/table/index.d.ts +6 -0
- package/dist/foundation/table/index.js +6 -0
- package/dist/foundation/table/plugin.d.ts +23 -0
- package/dist/foundation/table/plugin.js +119 -0
- package/dist/foundation/table/schema.d.ts +2 -0
- package/dist/foundation/table/schema.js +38 -0
- package/dist/foundation/table/selection.d.ts +7 -0
- package/dist/foundation/table/selection.js +35 -0
- package/dist/foundation/table/types.d.ts +109 -0
- package/dist/foundation/table/types.js +1 -0
- package/dist/foundation/transactions.d.ts +41 -0
- package/dist/foundation/transactions.js +90 -0
- package/dist/foundation/types.d.ts +261 -0
- package/dist/foundation/types.js +1 -0
- package/dist/foundation/versioning/diffVersions.d.ts +5 -0
- package/dist/foundation/versioning/diffVersions.js +3 -0
- package/dist/foundation/versioning/index.d.ts +2 -0
- package/dist/foundation/versioning/index.js +2 -0
- package/dist/foundation/versioning/types.d.ts +17 -0
- package/dist/foundation/versioning/types.js +1 -0
- package/dist/history.d.ts +20 -0
- package/dist/history.js +165 -0
- package/dist/html/compatibility.js +7 -1
- package/dist/index.d.ts +1 -14
- package/dist/index.js +9 -14
- package/dist/legacy/index.d.ts +45 -0
- package/dist/legacy/index.js +45 -0
- package/dist/legacyCommands/alignment.d.ts +20 -0
- package/dist/{commands → legacyCommands}/alignment.js +17 -0
- package/dist/legacyCommands/blocks.d.ts +14 -0
- package/dist/legacyCommands/blocks.js +105 -0
- package/dist/legacyCommands/checklist.d.ts +11 -0
- package/dist/legacyCommands/checklist.js +45 -0
- package/dist/legacyCommands/code.d.ts +15 -0
- package/dist/{commands → legacyCommands}/code.js +25 -1
- package/dist/legacyCommands/formula.d.ts +7 -0
- package/dist/legacyCommands/formula.js +67 -0
- package/dist/legacyCommands/inlineAtoms.d.ts +7 -0
- package/dist/legacyCommands/inlineAtoms.js +58 -0
- package/dist/legacyCommands/list.d.ts +38 -0
- package/dist/legacyCommands/list.js +477 -0
- package/dist/{commands → legacyCommands}/marks.d.ts +3 -0
- package/dist/{commands → legacyCommands}/marks.js +28 -11
- package/dist/legacyCommands/media.d.ts +31 -0
- package/dist/legacyCommands/media.js +205 -0
- package/dist/legacyCommands/move.d.ts +14 -0
- package/dist/legacyCommands/move.js +91 -0
- package/dist/legacyCommands/table.d.ts +91 -0
- package/dist/legacyCommands/table.js +547 -0
- package/dist/listPresets.d.ts +12 -0
- package/dist/listPresets.js +6 -0
- package/dist/listScope.d.ts +27 -0
- package/dist/listScope.js +104 -0
- package/dist/markdown/compatibility.d.ts +2 -0
- package/dist/markdown/compatibility.js +94 -2
- package/dist/marks.d.ts +5 -5
- package/dist/model.d.ts +65 -9
- package/dist/plugin.d.ts +15 -0
- package/dist/plugin.js +31 -0
- package/dist/plugins/alignment.d.ts +3 -0
- package/dist/plugins/alignment.js +6 -0
- package/dist/plugins/basicFormatting.d.ts +9 -0
- package/dist/plugins/basicFormatting.js +24 -0
- package/dist/plugins/blockType.d.ts +3 -0
- package/dist/plugins/blockType.js +6 -0
- package/dist/plugins/blockquote.d.ts +3 -0
- package/dist/plugins/blockquote.js +6 -0
- package/dist/plugins/checklist.d.ts +3 -0
- package/dist/plugins/checklist.js +10 -0
- package/dist/plugins/codeBlock.d.ts +3 -0
- package/dist/plugins/codeBlock.js +6 -0
- package/dist/plugins/formula.d.ts +3 -0
- package/dist/plugins/formula.js +9 -0
- package/dist/plugins/list.d.ts +3 -0
- package/dist/plugins/list.js +10 -0
- package/dist/plugins/media.d.ts +3 -0
- package/dist/plugins/media.js +12 -0
- package/dist/plugins/move.d.ts +3 -0
- package/dist/plugins/move.js +9 -0
- package/dist/plugins/table.d.ts +3 -0
- package/dist/plugins/table.js +22 -0
- package/dist/preset.d.ts +12 -0
- package/dist/preset.js +40 -0
- package/dist/schema.d.ts +22 -0
- package/dist/schema.js +333 -0
- package/dist/security/urlPolicy.d.ts +1 -17
- package/dist/security/urlPolicy.js +3 -69
- package/dist/selection.d.ts +1 -1
- package/dist/selectionMapping.d.ts +4 -0
- package/dist/selectionMapping.js +222 -0
- package/dist/table.d.ts +3 -3
- package/dist/transaction.d.ts +35 -12
- package/dist/transaction.js +174 -28
- package/dist/tree.d.ts +8 -2
- package/dist/tree.js +44 -0
- package/package.json +27 -8
- package/dist/commands/alignment.d.ts +0 -18
- package/dist/commands/code.d.ts +0 -13
- package/dist/commands/list.d.ts +0 -20
- package/dist/commands/list.js +0 -52
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { createNodeId, isTextNode } from "../identity.js";
|
|
2
|
+
const positiveInt = (value) => Number.isInteger(value) && Number(value) > 0 ? Number(value) : 1;
|
|
3
|
+
const role = (node, wanted) => node.type === wanted;
|
|
4
|
+
const rowsOf = (table) => (table.children || []).filter((node) => !isTextNode(node) && role(node, "table_row"));
|
|
5
|
+
const cellsOf = (row) => (row.children || []).filter((node) => !isTextNode(node) && role(node, "table_cell"));
|
|
6
|
+
const intersects = (cell, rect) => cell.top < rect.bottom && cell.bottom > rect.top && cell.left < rect.right && cell.right > rect.left;
|
|
7
|
+
const contained = (cell, rect) => cell.top >= rect.top && cell.bottom <= rect.bottom && cell.left >= rect.left && cell.right <= rect.right;
|
|
8
|
+
const cache = new WeakMap();
|
|
9
|
+
export const occupancyGridFor = (table) => {
|
|
10
|
+
const cached = cache.get(table);
|
|
11
|
+
if (cached)
|
|
12
|
+
return cached;
|
|
13
|
+
const rows = rowsOf(table);
|
|
14
|
+
const slots = Array.from({ length: rows.length }, () => []);
|
|
15
|
+
const anchors = [];
|
|
16
|
+
rows.forEach((row, rowIndex) => {
|
|
17
|
+
let column = 0;
|
|
18
|
+
cellsOf(row).forEach((node, childIndex) => {
|
|
19
|
+
while (slots[rowIndex]?.[column])
|
|
20
|
+
column += 1;
|
|
21
|
+
const rowspan = positiveInt(node.attrs?.rowspan);
|
|
22
|
+
const colspan = positiveInt(node.attrs?.colspan);
|
|
23
|
+
const cell = {
|
|
24
|
+
cellId: node.id, top: rowIndex, left: column,
|
|
25
|
+
bottom: rowIndex + rowspan, right: column + colspan,
|
|
26
|
+
isAnchor: true, rowIndex, childIndex, node,
|
|
27
|
+
};
|
|
28
|
+
anchors.push(cell);
|
|
29
|
+
for (let r = cell.top; r < cell.bottom; r += 1) {
|
|
30
|
+
slots[r] || (slots[r] = []);
|
|
31
|
+
for (let c = cell.left; c < cell.right; c += 1)
|
|
32
|
+
if (!slots[r][c])
|
|
33
|
+
slots[r][c] = r === cell.top && c === cell.left
|
|
34
|
+
? cell
|
|
35
|
+
: { ...cell, isAnchor: false };
|
|
36
|
+
}
|
|
37
|
+
column += colspan;
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
const columns = slots.reduce((width, row) => Math.max(width, row.length), 0);
|
|
41
|
+
const grid = Object.freeze({
|
|
42
|
+
tableId: table.id,
|
|
43
|
+
rows: rows.length,
|
|
44
|
+
columns,
|
|
45
|
+
anchors: Object.freeze(anchors),
|
|
46
|
+
at: (row, col) => slots[row]?.[col] || null,
|
|
47
|
+
anchorsIn: (rect) => anchors.filter((cell) => contained(cell, rect)),
|
|
48
|
+
coveredIn: (rect) => anchors.filter((cell) => intersects(cell, rect) && !contained(cell, rect)),
|
|
49
|
+
isRectangular: (rect) => {
|
|
50
|
+
if (rect.top < 0 || rect.left < 0 || rect.bottom > rows.length || rect.right > columns || rect.top >= rect.bottom || rect.left >= rect.right)
|
|
51
|
+
return false;
|
|
52
|
+
for (let row = rect.top; row < rect.bottom; row += 1)
|
|
53
|
+
for (let col = rect.left; col < rect.right; col += 1) {
|
|
54
|
+
const cell = slots[row]?.[col];
|
|
55
|
+
if (!cell || !contained(cell, rect))
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
cache.set(table, grid);
|
|
62
|
+
return grid;
|
|
63
|
+
};
|
|
64
|
+
export const validateTableGeometry = (table) => {
|
|
65
|
+
const issues = [];
|
|
66
|
+
const rows = rowsOf(table);
|
|
67
|
+
const slots = Array.from({ length: rows.length }, () => []);
|
|
68
|
+
rows.forEach((row, rowIndex) => {
|
|
69
|
+
let column = 0;
|
|
70
|
+
cellsOf(row).forEach((cell) => {
|
|
71
|
+
while (slots[rowIndex]?.[column])
|
|
72
|
+
column += 1;
|
|
73
|
+
const rowspan = positiveInt(cell.attrs?.rowspan);
|
|
74
|
+
const colspan = positiveInt(cell.attrs?.colspan);
|
|
75
|
+
if (rowIndex + rowspan > rows.length)
|
|
76
|
+
issues.push({ code: "overhang", row: rowIndex, column, cellId: cell.id, message: "Cell rowspan extends beyond the table." });
|
|
77
|
+
for (let r = rowIndex; r < Math.min(rows.length, rowIndex + rowspan); r += 1)
|
|
78
|
+
for (let c = column; c < column + colspan; c += 1) {
|
|
79
|
+
if (slots[r]?.[c])
|
|
80
|
+
issues.push({ code: "overlap", row: r, column: c, cellId: cell.id, message: "Two cells claim one logical coordinate." });
|
|
81
|
+
else
|
|
82
|
+
slots[r][c] = cell.id;
|
|
83
|
+
}
|
|
84
|
+
column += colspan;
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
const width = slots.reduce((max, row) => Math.max(max, row.length), 0);
|
|
88
|
+
slots.forEach((row, rowIndex) => {
|
|
89
|
+
if (row.length !== width)
|
|
90
|
+
issues.push({ code: "unequal-width", row: rowIndex, message: "Rows do not materialize the same logical width." });
|
|
91
|
+
for (let col = 0; col < width; col += 1)
|
|
92
|
+
if (!row[col])
|
|
93
|
+
issues.push({ code: "hole", row: rowIndex, column: col, message: "Logical coordinate is unclaimed." });
|
|
94
|
+
});
|
|
95
|
+
const grid = occupancyGridFor(table);
|
|
96
|
+
let leadingHeaderRows = 0;
|
|
97
|
+
while (leadingHeaderRows < grid.rows && Array.from({ length: grid.columns }, (_, col) => grid.at(leadingHeaderRows, col)?.node.attrs?.header === true).every(Boolean))
|
|
98
|
+
leadingHeaderRows += 1;
|
|
99
|
+
let leadingHeaderColumns = 0;
|
|
100
|
+
while (leadingHeaderColumns < grid.columns && Array.from({ length: grid.rows }, (_, row) => grid.at(row, leadingHeaderColumns)?.node.attrs?.header === true).every(Boolean))
|
|
101
|
+
leadingHeaderColumns += 1;
|
|
102
|
+
for (let row = 0; row < grid.rows; row += 1) {
|
|
103
|
+
for (let col = 0; col < grid.columns; col += 1) {
|
|
104
|
+
const header = grid.at(row, col)?.node.attrs?.header === true;
|
|
105
|
+
const expected = row < leadingHeaderRows || col < leadingHeaderColumns;
|
|
106
|
+
if (header !== expected)
|
|
107
|
+
issues.push({ code: "noncontiguous-header", row, column: col, cellId: grid.at(row, col)?.cellId, message: "Header cells must be the union of contiguous leading rows and columns." });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return issues;
|
|
111
|
+
};
|
|
112
|
+
const emptyCell = (cellId = createNodeId(), paragraphId = createNodeId()) => ({
|
|
113
|
+
type: "table_cell", id: cellId, attrs: { colspan: 1, rowspan: 1, header: false },
|
|
114
|
+
children: [{ type: "paragraph", id: paragraphId, children: [] }],
|
|
115
|
+
});
|
|
116
|
+
export const repairTableGeometry = (table) => {
|
|
117
|
+
const repairs = validateTableGeometry(table);
|
|
118
|
+
if (!repairs.length)
|
|
119
|
+
return { table, repairs };
|
|
120
|
+
const sourceRows = rowsOf(table);
|
|
121
|
+
const slots = Array.from({ length: sourceRows.length }, () => []);
|
|
122
|
+
const placements = [];
|
|
123
|
+
sourceRows.forEach((row, rowIndex) => {
|
|
124
|
+
let column = 0;
|
|
125
|
+
cellsOf(row).forEach((cell) => {
|
|
126
|
+
const rowspan = Math.min(positiveInt(cell.attrs?.rowspan), sourceRows.length - rowIndex);
|
|
127
|
+
const colspan = positiveInt(cell.attrs?.colspan);
|
|
128
|
+
const fits = (left) => {
|
|
129
|
+
for (let r = rowIndex; r < rowIndex + rowspan; r += 1)
|
|
130
|
+
for (let c = left; c < left + colspan; c += 1)
|
|
131
|
+
if (slots[r]?.[c])
|
|
132
|
+
return false;
|
|
133
|
+
return true;
|
|
134
|
+
};
|
|
135
|
+
while (!fits(column))
|
|
136
|
+
column += 1;
|
|
137
|
+
const normalized = { ...cell, attrs: { ...(cell.attrs || {}), rowspan, colspan, header: cell.attrs?.header === true } };
|
|
138
|
+
placements.push({ row: rowIndex, column, rowspan, colspan, cell: normalized });
|
|
139
|
+
for (let r = rowIndex; r < rowIndex + rowspan; r += 1)
|
|
140
|
+
for (let c = column; c < column + colspan; c += 1)
|
|
141
|
+
slots[r][c] = normalized;
|
|
142
|
+
column += colspan;
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
let width = slots.reduce((max, row) => Math.max(max, row.length), 0);
|
|
146
|
+
for (let row = 0; row < sourceRows.length; row += 1)
|
|
147
|
+
for (let col = 0; col < width; col += 1)
|
|
148
|
+
if (!slots[row][col]) {
|
|
149
|
+
const cell = emptyCell();
|
|
150
|
+
placements.push({ row, column: col, rowspan: 1, colspan: 1, cell });
|
|
151
|
+
slots[row][col] = cell;
|
|
152
|
+
}
|
|
153
|
+
width = slots.reduce((max, row) => Math.max(max, row.length), width);
|
|
154
|
+
// Preserve only header cells that already form complete leading rows or
|
|
155
|
+
// columns. A stray Word header in the middle is demoted; promoting all rows
|
|
156
|
+
// before it changes document semantics and surprises screen-reader users.
|
|
157
|
+
let headerRows = 0;
|
|
158
|
+
while (headerRows < sourceRows.length && Array.from({ length: width }, (_, col) => slots[headerRows]?.[col]?.attrs?.header === true).every(Boolean))
|
|
159
|
+
headerRows += 1;
|
|
160
|
+
let headerColumns = 0;
|
|
161
|
+
while (headerColumns < width && Array.from({ length: sourceRows.length }, (_, row) => slots[row]?.[headerColumns]?.attrs?.header === true).every(Boolean))
|
|
162
|
+
headerColumns += 1;
|
|
163
|
+
const keepHeader = new Set();
|
|
164
|
+
for (let row = 0; row < sourceRows.length; row += 1)
|
|
165
|
+
for (let col = 0; col < width; col += 1) {
|
|
166
|
+
const owner = slots[row]?.[col];
|
|
167
|
+
if (owner?.attrs?.header === true && (row < headerRows || col < headerColumns))
|
|
168
|
+
keepHeader.add(owner.id);
|
|
169
|
+
}
|
|
170
|
+
const rows = sourceRows.map((row, rowIndex) => ({
|
|
171
|
+
...row,
|
|
172
|
+
children: placements.filter((placement) => placement.row === rowIndex).sort((a, b) => a.column - b.column).map(({ cell, rowspan, colspan }) => ({
|
|
173
|
+
...cell,
|
|
174
|
+
attrs: { ...(cell.attrs || {}), rowspan, colspan, header: keepHeader.has(cell.id) },
|
|
175
|
+
})),
|
|
176
|
+
}));
|
|
177
|
+
return { table: { ...table, children: rows }, repairs };
|
|
178
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ContextMenuContribution, type PluginCommand } from "../plugin/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Wraps the fourteen existing, already-tested table commands
|
|
4
|
+
* (table/commands.ts's tableCommands) with the description Phase 10's doc
|
|
5
|
+
* generation needs. Every one already emits Phase 8c's fine-grained
|
|
6
|
+
* operations (insertNode/removeNode/moveNode/setNodeAttributes scoped to
|
|
7
|
+
* the affected row/cell, plus a cell-scoped replaceNode for merge) - this
|
|
8
|
+
* wrap does not touch that, it only adds metadata around commands that are
|
|
9
|
+
* already, individually, exactly what a PluginCommand's `run` needs.
|
|
10
|
+
*/
|
|
11
|
+
export declare const tablePluginCommands: Record<string, PluginCommand>;
|
|
12
|
+
/**
|
|
13
|
+
* Phase 11.5 §2.3: table.insertRow/table.insertColumn need caller-generated
|
|
14
|
+
* ids (rowId/cellIds/paragraphIds) that can't live in a static `params` -
|
|
15
|
+
* the dispatching UI must merge fresh ids in at invocation time, the same
|
|
16
|
+
* way surface/input.ts's Tab handling special-cases list.indent/outdent's
|
|
17
|
+
* dynamic params. table.splitCell has the same requirement plus only
|
|
18
|
+
* applies to an already-merged cell, which needs more than a scope-kind
|
|
19
|
+
* check to detect - left out of this first pass rather than shown
|
|
20
|
+
* unconditionally. table.setHeader/moveRow/moveColumn already have
|
|
21
|
+
* dedicated toolbar buttons; not duplicated here.
|
|
22
|
+
*/
|
|
23
|
+
export declare const tableContextMenuContributions: readonly ContextMenuContribution[];
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { definePluginCommand } from "../plugin/types.js";
|
|
2
|
+
import { tableCommands } from "./commands.js";
|
|
3
|
+
/**
|
|
4
|
+
* Wraps the fourteen existing, already-tested table commands
|
|
5
|
+
* (table/commands.ts's tableCommands) with the description Phase 10's doc
|
|
6
|
+
* generation needs. Every one already emits Phase 8c's fine-grained
|
|
7
|
+
* operations (insertNode/removeNode/moveNode/setNodeAttributes scoped to
|
|
8
|
+
* the affected row/cell, plus a cell-scoped replaceNode for merge) - this
|
|
9
|
+
* wrap does not touch that, it only adds metadata around commands that are
|
|
10
|
+
* already, individually, exactly what a PluginCommand's `run` needs.
|
|
11
|
+
*/
|
|
12
|
+
export const tablePluginCommands = {
|
|
13
|
+
"table.insert": definePluginCommand({
|
|
14
|
+
id: "table.insert", run: tableCommands["table.insert"],
|
|
15
|
+
description: "Inserts a new table with the given row/column count at the current position.",
|
|
16
|
+
options: {
|
|
17
|
+
rows: { description: "Number of rows.", required: true },
|
|
18
|
+
columns: { description: "Number of columns.", required: true },
|
|
19
|
+
withHeader: { description: "Whether the first row is a header row." },
|
|
20
|
+
placement: { description: "\"before\" or \"after\" the current block." },
|
|
21
|
+
ids: { description: "Caller-provided ids for the table, its rows, cells, and paragraphs.", required: true },
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
"table.remove": definePluginCommand({
|
|
25
|
+
id: "table.remove", run: tableCommands["table.remove"],
|
|
26
|
+
description: "Removes the selected table entirely.",
|
|
27
|
+
}),
|
|
28
|
+
"table.insertRow": definePluginCommand({
|
|
29
|
+
id: "table.insertRow", run: tableCommands["table.insertRow"],
|
|
30
|
+
description: "Inserts a new row into the selected table, bumping the rowspan of any cell whose span crosses the insertion point.",
|
|
31
|
+
options: {
|
|
32
|
+
position: { description: "\"before\" or \"after\" the current row." },
|
|
33
|
+
rowIndex: { description: "Explicit row index to insert at, instead of relative to the current row." },
|
|
34
|
+
rowId: { description: "Caller-provided id for the new row.", required: true },
|
|
35
|
+
cellIds: { description: "Caller-provided ids for the new row's cells." },
|
|
36
|
+
paragraphIds: { description: "Caller-provided ids for the new cells' paragraphs." },
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
"table.removeRow": definePluginCommand({
|
|
40
|
+
id: "table.removeRow", run: tableCommands["table.removeRow"],
|
|
41
|
+
description: "Removes a row from the selected table, relocating any cell anchored there whose rowspan reaches further down.",
|
|
42
|
+
options: { rowIndex: { description: "The row index to remove; defaults to the current row." } },
|
|
43
|
+
}),
|
|
44
|
+
"table.insertColumn": definePluginCommand({
|
|
45
|
+
id: "table.insertColumn", run: tableCommands["table.insertColumn"],
|
|
46
|
+
description: "Inserts a new column into the selected table, one operation per affected row.",
|
|
47
|
+
options: {
|
|
48
|
+
position: { description: "\"before\" or \"after\" the current column." },
|
|
49
|
+
columnIndex: { description: "Explicit column index to insert at." },
|
|
50
|
+
cellIds: { description: "Caller-provided ids for the new column's cells, one per row needing one." },
|
|
51
|
+
paragraphIds: { description: "Caller-provided ids for the new cells' paragraphs." },
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
"table.removeColumn": definePluginCommand({
|
|
55
|
+
id: "table.removeColumn", run: tableCommands["table.removeColumn"],
|
|
56
|
+
description: "Removes a column from the selected table, one operation per affected row.",
|
|
57
|
+
options: { columnIndex: { description: "The column index to remove; defaults to the current column." } },
|
|
58
|
+
}),
|
|
59
|
+
"table.mergeCells": definePluginCommand({
|
|
60
|
+
id: "table.mergeCells", run: tableCommands["table.mergeCells"],
|
|
61
|
+
description: "Merges the selected rectangular cell range into one cell, concatenating simple inline content or preserving block content.",
|
|
62
|
+
}),
|
|
63
|
+
"table.splitCell": definePluginCommand({
|
|
64
|
+
id: "table.splitCell", run: tableCommands["table.splitCell"],
|
|
65
|
+
description: "Splits a previously-merged cell back into its individual cells.",
|
|
66
|
+
options: { cellIds: { description: "Caller-provided ids for the newly-created cells.", required: true }, paragraphIds: { description: "Caller-provided ids for the new cells' paragraphs.", required: true } },
|
|
67
|
+
}),
|
|
68
|
+
"table.setHeader": definePluginCommand({
|
|
69
|
+
id: "table.setHeader", run: tableCommands["table.setHeader"],
|
|
70
|
+
description: "Sets which leading rows and/or columns of the table are headers.",
|
|
71
|
+
options: { target: { description: "\"row\", \"column\", \"both\", or \"none\".", required: true } },
|
|
72
|
+
}),
|
|
73
|
+
"table.setCellAttributes": definePluginCommand({
|
|
74
|
+
id: "table.setCellAttributes", run: tableCommands["table.setCellAttributes"],
|
|
75
|
+
description: "Sets attributes on the selected cell(s).",
|
|
76
|
+
options: { attrs: { description: "The attributes to set.", required: true } },
|
|
77
|
+
}),
|
|
78
|
+
"table.setColumnWidth": definePluginCommand({
|
|
79
|
+
id: "table.setColumnWidth", run: tableCommands["table.setColumnWidth"],
|
|
80
|
+
description: "Sets a single column's width.",
|
|
81
|
+
options: { index: { description: "The column index.", required: true }, width: { description: "The new width in pixels.", required: true } },
|
|
82
|
+
}),
|
|
83
|
+
"table.setRowHeight": definePluginCommand({
|
|
84
|
+
id: "table.setRowHeight", run: tableCommands["table.setRowHeight"],
|
|
85
|
+
description: "Sets a single row's height.",
|
|
86
|
+
options: { index: { description: "The row index.", required: true }, height: { description: "The new height in pixels.", required: true } },
|
|
87
|
+
}),
|
|
88
|
+
"table.moveRow": definePluginCommand({
|
|
89
|
+
id: "table.moveRow", run: tableCommands["table.moveRow"],
|
|
90
|
+
description: "Moves a row up or down, refusing moves that would split a rowspan.",
|
|
91
|
+
options: { direction: { description: "\"up\" or \"down\".", required: true }, index: { description: "The row index; defaults to the current row." } },
|
|
92
|
+
}),
|
|
93
|
+
"table.moveColumn": definePluginCommand({
|
|
94
|
+
id: "table.moveColumn", run: tableCommands["table.moveColumn"],
|
|
95
|
+
description: "Moves a column left or right, refusing moves that would split a colspan.",
|
|
96
|
+
options: { direction: { description: "\"left\" or \"right\".", required: true }, index: { description: "The column index; defaults to the current column." } },
|
|
97
|
+
}),
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Phase 11.5 §2.3: table.insertRow/table.insertColumn need caller-generated
|
|
101
|
+
* ids (rowId/cellIds/paragraphIds) that can't live in a static `params` -
|
|
102
|
+
* the dispatching UI must merge fresh ids in at invocation time, the same
|
|
103
|
+
* way surface/input.ts's Tab handling special-cases list.indent/outdent's
|
|
104
|
+
* dynamic params. table.splitCell has the same requirement plus only
|
|
105
|
+
* applies to an already-merged cell, which needs more than a scope-kind
|
|
106
|
+
* check to detect - left out of this first pass rather than shown
|
|
107
|
+
* unconditionally. table.setHeader/moveRow/moveColumn already have
|
|
108
|
+
* dedicated toolbar buttons; not duplicated here.
|
|
109
|
+
*/
|
|
110
|
+
export const tableContextMenuContributions = [
|
|
111
|
+
{ id: "table.contextMenu.insertRowAbove", commandId: "table.insertRow", params: { position: "before" }, label: "Insert row above", scopeKinds: ["table-grid"] },
|
|
112
|
+
{ id: "table.contextMenu.insertRowBelow", commandId: "table.insertRow", params: { position: "after" }, label: "Insert row below", scopeKinds: ["table-grid"] },
|
|
113
|
+
{ id: "table.contextMenu.removeRow", commandId: "table.removeRow", label: "Delete row", scopeKinds: ["table-grid"] },
|
|
114
|
+
{ id: "table.contextMenu.insertColumnLeft", commandId: "table.insertColumn", params: { position: "before" }, label: "Insert column left", scopeKinds: ["table-grid"] },
|
|
115
|
+
{ id: "table.contextMenu.insertColumnRight", commandId: "table.insertColumn", params: { position: "after" }, label: "Insert column right", scopeKinds: ["table-grid"] },
|
|
116
|
+
{ id: "table.contextMenu.removeColumn", commandId: "table.removeColumn", label: "Delete column", scopeKinds: ["table-grid"] },
|
|
117
|
+
{ id: "table.contextMenu.mergeCells", commandId: "table.mergeCells", label: "Merge cells", scopeKinds: ["table-grid"] },
|
|
118
|
+
{ id: "table.contextMenu.removeTable", commandId: "table.remove", label: "Delete table", scopeKinds: ["table-grid"] },
|
|
119
|
+
];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const positiveInt = { default: 1, validate: (value) => Number.isInteger(value) && Number(value) >= 1 };
|
|
2
|
+
const optionalString = { validate: (value) => typeof value === "string" };
|
|
3
|
+
export const tableNodeSpecs = [
|
|
4
|
+
{
|
|
5
|
+
type: "table", group: "block", semanticRole: "table", content: "table_row+", isolating: false,
|
|
6
|
+
attributes: {
|
|
7
|
+
columnWidths: { validate: (value) => Array.isArray(value) && value.every((width) => Number.isFinite(width) && Number(width) > 0) },
|
|
8
|
+
caption: optionalString,
|
|
9
|
+
layout: { validate: (value) => value === "auto" || value === "fixed" },
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
// "table_cell*", not "+": a row every one of whose columns is covered by
|
|
13
|
+
// an earlier row's rowspan legitimately has zero own cells (e.g. after
|
|
14
|
+
// merging a selection that spans that row's full width). Row count still
|
|
15
|
+
// has to match grid.rows for rowspan bookkeeping, so the row node itself
|
|
16
|
+
// must exist even when empty - it cannot simply be deleted. See
|
|
17
|
+
// docs/bugs/table-row-empty-rowspan-coverage-rejected.md.
|
|
18
|
+
{ type: "table_row", group: "block", semanticRole: "table-row", content: "table_cell*", attributes: { height: { validate: (value) => Number.isFinite(value) && Number(value) > 0 } } },
|
|
19
|
+
{
|
|
20
|
+
type: "table_cell", group: "block", semanticRole: "table-cell", content: "block+", isolating: true,
|
|
21
|
+
attributes: {
|
|
22
|
+
colspan: positiveInt, rowspan: positiveInt,
|
|
23
|
+
header: { default: false, validate: (value) => typeof value === "boolean" },
|
|
24
|
+
background: optionalString,
|
|
25
|
+
// `borders`: legacy uniform (all 4 sides) CSS border shorthand -
|
|
26
|
+
// still the only shape HTML paste-import ever produces. `borderTop`/
|
|
27
|
+
// `borderRight`/`borderBottom`/`borderLeft`: per-side overrides (the
|
|
28
|
+
// "which sides" control the border-options UI added) - each, when
|
|
29
|
+
// set, wins over `borders` for that one side only; see
|
|
30
|
+
// surface/renderer.ts's per-side application order. Independent
|
|
31
|
+
// optional strings rather than one structured attr so an unset side
|
|
32
|
+
// round-trips as simply absent, matching every other optional cell
|
|
33
|
+
// style attr in this schema.
|
|
34
|
+
borders: optionalString, borderTop: optionalString, borderRight: optionalString, borderBottom: optionalString, borderLeft: optionalString,
|
|
35
|
+
verticalAlign: { validate: (value) => value === "top" || value === "middle" || value === "bottom" },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PositionLookup } from "../scope/types.js";
|
|
2
|
+
import type { SmartElementNode, SmartSelection } from "../types.js";
|
|
3
|
+
import type { CellSelectionRect, Rect } from "./types.js";
|
|
4
|
+
/** Expands a logical rectangle until every intersected merged cell is fully contained. */
|
|
5
|
+
export declare const snapTableCellRect: (table: SmartElementNode, input: Rect) => CellSelectionRect;
|
|
6
|
+
/** Model cell selections store direction as anchor/head positions while the overlay consumes the snapped rectangle. */
|
|
7
|
+
export declare const cellSelectionFromIds: (anchorCellId: string, headCellId: string, positions: PositionLookup) => SmartSelection | null;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { occupancyGridFor } from "./grid.js";
|
|
2
|
+
/** Expands a logical rectangle until every intersected merged cell is fully contained. */
|
|
3
|
+
export const snapTableCellRect = (table, input) => {
|
|
4
|
+
const grid = occupancyGridFor(table);
|
|
5
|
+
const rect = {
|
|
6
|
+
top: Math.max(0, Math.min(input.top, grid.rows - 1)),
|
|
7
|
+
left: Math.max(0, Math.min(input.left, grid.columns - 1)),
|
|
8
|
+
bottom: Math.max(1, Math.min(input.bottom, grid.rows)),
|
|
9
|
+
right: Math.max(1, Math.min(input.right, grid.columns)),
|
|
10
|
+
};
|
|
11
|
+
let changed = true;
|
|
12
|
+
while (changed) {
|
|
13
|
+
changed = false;
|
|
14
|
+
grid.anchors.forEach((cell) => {
|
|
15
|
+
if (cell.top < rect.bottom && cell.bottom > rect.top && cell.left < rect.right && cell.right > rect.left) {
|
|
16
|
+
const next = { top: Math.min(rect.top, cell.top), left: Math.min(rect.left, cell.left), bottom: Math.max(rect.bottom, cell.bottom), right: Math.max(rect.right, cell.right) };
|
|
17
|
+
if (next.top !== rect.top || next.left !== rect.left || next.bottom !== rect.bottom || next.right !== rect.right) {
|
|
18
|
+
Object.assign(rect, next);
|
|
19
|
+
changed = true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const anchor = grid.at(rect.top, rect.left);
|
|
25
|
+
const head = grid.at(rect.bottom - 1, rect.right - 1);
|
|
26
|
+
if (!anchor || !head)
|
|
27
|
+
throw new Error("Cell selection rectangle does not resolve to a complete table grid.");
|
|
28
|
+
return { tableId: table.id, anchorCellId: anchor.cellId, headCellId: head.cellId, rect, cellIds: grid.anchorsIn(rect).map((cell) => cell.cellId) };
|
|
29
|
+
};
|
|
30
|
+
/** Model cell selections store direction as anchor/head positions while the overlay consumes the snapped rectangle. */
|
|
31
|
+
export const cellSelectionFromIds = (anchorCellId, headCellId, positions) => {
|
|
32
|
+
const anchor = positions.contentRangeOf(anchorCellId)?.from;
|
|
33
|
+
const head = positions.contentRangeOf(headCellId)?.to;
|
|
34
|
+
return anchor && head ? { type: "cell", anchor, head } : null;
|
|
35
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { Attrs, SmartDocument, SmartElementNode, SmartOperation, SmartSchema } from "../types.js";
|
|
2
|
+
import type { PositionLookup, ResolvedScope } from "../scope/types.js";
|
|
3
|
+
export interface Rect {
|
|
4
|
+
readonly top: number;
|
|
5
|
+
readonly left: number;
|
|
6
|
+
/** Exclusive. */
|
|
7
|
+
readonly bottom: number;
|
|
8
|
+
/** Exclusive. */
|
|
9
|
+
readonly right: number;
|
|
10
|
+
}
|
|
11
|
+
export interface GridCell {
|
|
12
|
+
readonly cellId: string;
|
|
13
|
+
readonly top: number;
|
|
14
|
+
readonly left: number;
|
|
15
|
+
/** Exclusive. */
|
|
16
|
+
readonly bottom: number;
|
|
17
|
+
/** Exclusive. */
|
|
18
|
+
readonly right: number;
|
|
19
|
+
readonly isAnchor: boolean;
|
|
20
|
+
readonly rowIndex: number;
|
|
21
|
+
readonly childIndex: number;
|
|
22
|
+
readonly node: SmartElementNode;
|
|
23
|
+
}
|
|
24
|
+
export interface OccupancyGrid {
|
|
25
|
+
readonly tableId: string;
|
|
26
|
+
readonly rows: number;
|
|
27
|
+
readonly columns: number;
|
|
28
|
+
readonly anchors: readonly GridCell[];
|
|
29
|
+
at(row: number, col: number): GridCell | null;
|
|
30
|
+
anchorsIn(rect: Rect): GridCell[];
|
|
31
|
+
coveredIn(rect: Rect): GridCell[];
|
|
32
|
+
isRectangular(rect: Rect): boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface TableGeometryIssue {
|
|
35
|
+
readonly code: "overlap" | "overhang" | "hole" | "unequal-width" | "noncontiguous-header" | "invalid-child";
|
|
36
|
+
readonly row: number;
|
|
37
|
+
readonly column?: number;
|
|
38
|
+
readonly cellId?: string;
|
|
39
|
+
readonly message: string;
|
|
40
|
+
}
|
|
41
|
+
export interface TableCommandContext {
|
|
42
|
+
readonly schema: SmartSchema;
|
|
43
|
+
readonly positions: PositionLookup;
|
|
44
|
+
}
|
|
45
|
+
export type TableCommand<P> = (document: SmartDocument, scope: ResolvedScope, params: P, ctx: TableCommandContext) => SmartOperation[];
|
|
46
|
+
export interface TableIds {
|
|
47
|
+
readonly tableId: string;
|
|
48
|
+
readonly rowIds: readonly string[];
|
|
49
|
+
readonly cellIds: readonly string[];
|
|
50
|
+
readonly paragraphIds: readonly string[];
|
|
51
|
+
}
|
|
52
|
+
export interface InsertTableParams {
|
|
53
|
+
readonly rows: number;
|
|
54
|
+
readonly columns: number;
|
|
55
|
+
readonly withHeader?: boolean;
|
|
56
|
+
readonly placement?: "before" | "after";
|
|
57
|
+
readonly ids: TableIds;
|
|
58
|
+
}
|
|
59
|
+
export interface RowParams {
|
|
60
|
+
readonly position?: "before" | "after";
|
|
61
|
+
readonly rowIndex?: number;
|
|
62
|
+
readonly rowId?: string;
|
|
63
|
+
readonly cellIds?: readonly string[];
|
|
64
|
+
readonly paragraphIds?: readonly string[];
|
|
65
|
+
}
|
|
66
|
+
export interface ColumnParams {
|
|
67
|
+
readonly position?: "before" | "after";
|
|
68
|
+
readonly columnIndex?: number;
|
|
69
|
+
readonly cellIds?: readonly string[];
|
|
70
|
+
readonly paragraphIds?: readonly string[];
|
|
71
|
+
}
|
|
72
|
+
export interface HeaderParams {
|
|
73
|
+
readonly target: "row" | "column" | "both" | "none";
|
|
74
|
+
}
|
|
75
|
+
export interface CellAttributesParams {
|
|
76
|
+
readonly attrs: Attrs;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* `widths`, when supplied, seeds the full columnWidths array instead of
|
|
80
|
+
* the command's own `Array(columns).fill(120)` fallback - the command
|
|
81
|
+
* layer has no way to know a table's real *rendered* per-column widths
|
|
82
|
+
* (that's DOM state, not model state), so a caller that already measured
|
|
83
|
+
* them (a resize-handle UI, mid-drag) should pass them through rather
|
|
84
|
+
* than let every other column silently reset to a fabricated default.
|
|
85
|
+
*/
|
|
86
|
+
export interface ColumnWidthParams {
|
|
87
|
+
readonly index: number;
|
|
88
|
+
readonly width: number;
|
|
89
|
+
readonly widths?: readonly number[];
|
|
90
|
+
}
|
|
91
|
+
export interface RowHeightParams {
|
|
92
|
+
readonly index: number;
|
|
93
|
+
readonly height: number;
|
|
94
|
+
}
|
|
95
|
+
export interface MoveTableAxisParams {
|
|
96
|
+
readonly direction: "up" | "down" | "left" | "right";
|
|
97
|
+
readonly index?: number;
|
|
98
|
+
}
|
|
99
|
+
export interface SplitCellParams {
|
|
100
|
+
readonly cellIds: readonly string[];
|
|
101
|
+
readonly paragraphIds: readonly string[];
|
|
102
|
+
}
|
|
103
|
+
export interface CellSelectionRect {
|
|
104
|
+
readonly tableId: string;
|
|
105
|
+
readonly anchorCellId: string;
|
|
106
|
+
readonly headCellId: string;
|
|
107
|
+
readonly rect: Rect;
|
|
108
|
+
readonly cellIds: readonly string[];
|
|
109
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { PersistedEditorDocument, SmartDocument, SmartSchema, SmartTransaction } from "./types.js";
|
|
2
|
+
export declare class StaleTransactionError extends Error {
|
|
3
|
+
constructor(expected: number, actual: number);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* A transaction was behind the current revision, but not too far behind to
|
|
7
|
+
* rebase - and at least one of its operations could not be safely
|
|
8
|
+
* transformed against what was committed in the meantime (see
|
|
9
|
+
* collab/transform.ts's doc comment for exactly which shapes this covers:
|
|
10
|
+
* concurrent moves sharing an array, or a concurrent edit to content a
|
|
11
|
+
* merge absorbed). The transaction is rejected outright rather than
|
|
12
|
+
* partially or incorrectly applied - the caller (a real transport
|
|
13
|
+
* implementation, in 12b-server/transport) decides what happens to the
|
|
14
|
+
* user's edit from here (e.g. surface it for manual resolution, retry as a
|
|
15
|
+
* fresh transaction against current state, or convert it to a suggestion).
|
|
16
|
+
*/
|
|
17
|
+
export declare class RebaseConflictError extends Error {
|
|
18
|
+
readonly reasons: readonly {
|
|
19
|
+
readonly operationIndex: number;
|
|
20
|
+
readonly reason: string;
|
|
21
|
+
}[];
|
|
22
|
+
constructor(reasons: readonly {
|
|
23
|
+
readonly operationIndex: number;
|
|
24
|
+
readonly reason: string;
|
|
25
|
+
}[]);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A transaction's baseRevision is older than anything this editor instance
|
|
29
|
+
* has retained (see FoundationEditor's revisionLogLimit) - there is not
|
|
30
|
+
* enough history left to rebase through. The caller must resync (reload
|
|
31
|
+
* the full document at the current revision, e.g. via replaceState) rather
|
|
32
|
+
* than attempt a partial rebase against an incomplete operation log.
|
|
33
|
+
*/
|
|
34
|
+
export declare class ResyncRequiredError extends Error {
|
|
35
|
+
readonly atRevision: number;
|
|
36
|
+
constructor(atRevision: number);
|
|
37
|
+
}
|
|
38
|
+
export declare const invertTransaction: (transaction: SmartTransaction) => SmartTransaction;
|
|
39
|
+
export declare const assertTransactionSerializable: (transaction: SmartTransaction) => void;
|
|
40
|
+
export declare const applyTransactionAtomic: (state: PersistedEditorDocument, transaction: SmartTransaction, schema: SmartSchema) => PersistedEditorDocument;
|
|
41
|
+
export declare const documentsEqual: (left: SmartDocument, right: SmartDocument) => boolean;
|