@stll/folio-core 0.1.3 → 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.
Files changed (75) hide show
  1. package/dist/docx/index.d.ts +2 -0
  2. package/dist/docx/index.js +2 -0
  3. package/dist/docx/wrapTypes.d.ts +8 -1
  4. package/dist/docx/wrapTypes.js +0 -8
  5. package/dist/i18n/messages/catalogs.gen.d.ts +2776 -0
  6. package/dist/i18n/messages/catalogs.gen.js +2742 -0
  7. package/dist/i18n/messages/messages.gen.d.ts +166 -0
  8. package/dist/i18n/messages/messages.gen.js +0 -0
  9. package/dist/i18n/messages.d.ts +13 -0
  10. package/dist/i18n/messages.js +39 -0
  11. package/dist/layout-bridge/cellSelectionHighlight.d.ts +18 -0
  12. package/dist/layout-bridge/cellSelectionHighlight.js +36 -0
  13. package/dist/layout-bridge/headerFooterLayout.d.ts +50 -0
  14. package/dist/layout-bridge/headerFooterLayout.js +216 -0
  15. package/dist/layout-bridge/measuring/measureBlocksPipeline.d.ts +33 -0
  16. package/dist/layout-bridge/measuring/measureBlocksPipeline.js +0 -0
  17. package/dist/layout-bridge/sectionColumns.d.ts +11 -0
  18. package/dist/layout-bridge/sectionColumns.js +21 -0
  19. package/dist/layout-bridge/tableInsertHover.d.ts +42 -0
  20. package/dist/layout-bridge/tableInsertHover.js +102 -0
  21. package/dist/layout-engine/index.d.ts +2 -1
  22. package/dist/layout-engine/index.js +2 -1
  23. package/dist/layout-engine/measure/measureParagraph.js +40 -5
  24. package/dist/layout-engine/pmPageIndex.d.ts +24 -0
  25. package/dist/layout-engine/pmPageIndex.js +37 -0
  26. package/dist/layout-painter/imageLayout.d.ts +101 -0
  27. package/dist/layout-painter/imageLayout.js +161 -0
  28. package/dist/layout-painter/renderPage.d.ts +16 -1
  29. package/dist/layout-painter/renderPage.js +28 -1
  30. package/dist/layout-painter/renderParagraph.js +1 -1
  31. package/dist/layout-painter/sdtBoundary.d.ts +24 -1
  32. package/dist/layout-painter/sdtBoundary.js +40 -1
  33. package/dist/managers/AutoSaveManager.d.ts +89 -0
  34. package/dist/managers/AutoSaveManager.js +279 -0
  35. package/dist/managers/ClipboardManager.d.ts +31 -0
  36. package/dist/managers/ClipboardManager.js +147 -0
  37. package/dist/paged-layout/rangeProjection.d.ts +23 -1
  38. package/dist/paged-layout/rangeProjection.js +34 -1
  39. package/dist/prosemirror/cellDragSelection.d.ts +34 -0
  40. package/dist/prosemirror/cellDragSelection.js +69 -0
  41. package/dist/prosemirror/commands/sectionBreak.d.ts +15 -0
  42. package/dist/prosemirror/commands/sectionBreak.js +54 -0
  43. package/dist/prosemirror/commentIdAllocator.d.ts +32 -0
  44. package/dist/prosemirror/commentIdAllocator.js +35 -0
  45. package/dist/prosemirror/commentOps.d.ts +35 -0
  46. package/dist/prosemirror/commentOps.js +104 -0
  47. package/dist/prosemirror/conversion/toProseDoc.js +29 -6
  48. package/dist/prosemirror/extensions/index.d.ts +3 -0
  49. package/dist/prosemirror/extensions/index.js +3 -0
  50. package/dist/prosemirror/imageCommit.d.ts +42 -0
  51. package/dist/prosemirror/imageCommit.js +118 -0
  52. package/dist/prosemirror/paraText.d.ts +26 -0
  53. package/dist/prosemirror/paraText.js +73 -0
  54. package/dist/prosemirror/plugins/templateDirectives.d.ts +21 -1
  55. package/dist/prosemirror/plugins/templateDirectives.js +40 -1
  56. package/dist/prosemirror/queries.d.ts +46 -0
  57. package/dist/prosemirror/queries.js +74 -0
  58. package/dist/prosemirror/styles/styleResolver.d.ts +43 -1
  59. package/dist/prosemirror/styles/styleResolver.js +34 -0
  60. package/dist/prosemirror/tableResize.d.ts +49 -0
  61. package/dist/prosemirror/tableResize.js +162 -0
  62. package/dist/prosemirror/utils/extractTrackedChanges.d.ts +106 -0
  63. package/dist/prosemirror/utils/extractTrackedChanges.js +379 -0
  64. package/dist/prosemirror/utils/visualLineNavigation.d.ts +27 -0
  65. package/dist/prosemirror/utils/visualLineNavigation.js +217 -0
  66. package/dist/style-engine/index.d.ts +2 -2
  67. package/dist/style-engine/styleEngine.d.ts +14 -1
  68. package/dist/style-engine/styleEngine.js +15 -0
  69. package/dist/utils/colorResolver.d.ts +8 -1
  70. package/dist/utils/colorResolver.js +12 -1
  71. package/dist/utils/findVerticalScrollParent.d.ts +20 -0
  72. package/dist/utils/findVerticalScrollParent.js +30 -0
  73. package/dist/utils/fontResolver.d.ts +22 -3
  74. package/dist/utils/fontResolver.js +216 -31
  75. package/package.json +9 -1
@@ -77,6 +77,39 @@ const measureContentLeft = (pagesContainer, zoom) => {
77
77
  }
78
78
  return Number.isFinite(min) ? (min - overlayLeft) / zoom : null;
79
79
  };
80
+ const measureDirectiveGutter = (pagesContainer, zoom) => {
81
+ if (zoom <= 0) return null;
82
+ const overlay = pagesContainer.parentElement?.querySelector("[data-testid=\"selection-overlay\"]");
83
+ const pages = pagesContainer.querySelectorAll(".layout-page");
84
+ if (!overlay || pages.length === 0) return null;
85
+ const overlayRect = overlay.getBoundingClientRect();
86
+ const pageBands = [];
87
+ let contentLeft = null;
88
+ let marginWidth = null;
89
+ for (const [domIndex, page] of pages.entries()) {
90
+ const contentEl = page.querySelector(".layout-page-content");
91
+ if (!contentEl) continue;
92
+ const pageNumberRaw = page.dataset["pageNumber"];
93
+ const pageIndex = pageNumberRaw ? Number(pageNumberRaw) - 1 : domIndex;
94
+ const contentRect = contentEl.getBoundingClientRect();
95
+ pageBands.push({
96
+ pageIndex,
97
+ top: (contentRect.top - overlayRect.top) / zoom,
98
+ bottom: (contentRect.bottom - overlayRect.top) / zoom
99
+ });
100
+ if (contentLeft === null) {
101
+ const pageRect = page.getBoundingClientRect();
102
+ contentLeft = (contentRect.left - overlayRect.left) / zoom;
103
+ marginWidth = Math.max(0, (contentRect.left - pageRect.left) / zoom);
104
+ }
105
+ }
106
+ if (contentLeft === null || marginWidth === null) return null;
107
+ return {
108
+ contentLeft,
109
+ marginWidth,
110
+ pageBands
111
+ };
112
+ };
80
113
  /**
81
114
  * Sample the painted-run text style at each position in a single walk
82
115
  * over the rendered run spans. Positions outside the rendered buffer
@@ -150,4 +183,4 @@ const projectRangesToRects = async (ranges, ctx) => {
150
183
  return result;
151
184
  };
152
185
  //#endregion
153
- export { collectRunFontsAtPmPositions, measureContentLeft, projectRangesToRects };
186
+ export { collectRunFontsAtPmPositions, measureContentLeft, measureDirectiveGutter, projectRangesToRects };
@@ -0,0 +1,34 @@
1
+ import { EditorView } from "prosemirror-view";
2
+
3
+ //#region src/prosemirror/cellDragSelection.d.ts
4
+ /**
5
+ * @deprecated No longer used. The tracker ignores pointer overflow so same-cell
6
+ * drags stay text selections. Kept only for backward-compatible imports; will be
7
+ * removed in a future major.
8
+ */
9
+ declare const CELL_SELECT_OVERFLOW_PX = 5;
10
+ /**
11
+ * Walk up from a PM position to the enclosing `tableCell`/`tableHeader` and
12
+ * return its `before(d)` position (what `CellSelection` resolves against), or
13
+ * null when the position isn't inside a table cell.
14
+ */
15
+ declare function findCellPosFromPmPos(view: EditorView, pmPos: number): number | null;
16
+ /** Dispatch a `CellSelection` spanning the two cell positions. */
17
+ declare function applyCellSelection(view: EditorView, anchorCellPos: number, headCellPos: number): boolean;
18
+ /**
19
+ * Per-gesture state machine for cell-drag selection. Create one per pointer
20
+ * handler; drive it from mousedown/mousemove/mouseup.
21
+ */
22
+ type CellDragTracker = {
23
+ /** mousedown: record the cell under the press (or null when outside a table). */begin(cellPos: number | null): void;
24
+ /**
25
+ * mousemove: returns true when it applied a `CellSelection` and the caller
26
+ * should NOT also run its text-drag selection for this move.
27
+ */
28
+ update(view: EditorView, pmPos: number, clientX: number): boolean; /** mouseup / gesture end: clear the transient drag flags. */
29
+ end(): void; /** True while a cell drag is actively producing CellSelections. */
30
+ readonly isCellDragging: boolean;
31
+ };
32
+ declare function createCellDragTracker(): CellDragTracker;
33
+ //#endregion
34
+ export { CELL_SELECT_OVERFLOW_PX, CellDragTracker, applyCellSelection, createCellDragTracker, findCellPosFromPmPos };
@@ -0,0 +1,69 @@
1
+ import { CellSelection } from "prosemirror-tables";
2
+ //#region src/prosemirror/cellDragSelection.ts
3
+ /**
4
+ * @deprecated No longer used. The tracker ignores pointer overflow so same-cell
5
+ * drags stay text selections. Kept only for backward-compatible imports; will be
6
+ * removed in a future major.
7
+ */
8
+ const CELL_SELECT_OVERFLOW_PX = 5;
9
+ /**
10
+ * Walk up from a PM position to the enclosing `tableCell`/`tableHeader` and
11
+ * return its `before(d)` position (what `CellSelection` resolves against), or
12
+ * null when the position isn't inside a table cell.
13
+ */
14
+ function findCellPosFromPmPos(view, pmPos) {
15
+ try {
16
+ const $pos = view.state.doc.resolve(pmPos);
17
+ for (let d = $pos.depth; d > 0; d--) {
18
+ const node = $pos.node(d);
19
+ if (node.type.name === "tableCell" || node.type.name === "tableHeader") return $pos.before(d);
20
+ }
21
+ } catch {}
22
+ return null;
23
+ }
24
+ /** Dispatch a `CellSelection` spanning the two cell positions. */
25
+ function applyCellSelection(view, anchorCellPos, headCellPos) {
26
+ try {
27
+ const $a = view.state.doc.resolve(anchorCellPos);
28
+ const $h = view.state.doc.resolve(headCellPos);
29
+ view.dispatch(view.state.tr.setSelection(new CellSelection($a, $h)));
30
+ return true;
31
+ } catch {
32
+ return false;
33
+ }
34
+ }
35
+ function createCellDragTracker() {
36
+ let anchorCellPos = null;
37
+ let cellDragging = false;
38
+ return {
39
+ begin(cellPos) {
40
+ anchorCellPos = cellPos;
41
+ cellDragging = false;
42
+ },
43
+ update(view, pmPos, _clientX) {
44
+ if (anchorCellPos === null) return false;
45
+ if (cellDragging) {
46
+ const cur = findCellPosFromPmPos(view, pmPos);
47
+ if (cur !== null) {
48
+ applyCellSelection(view, anchorCellPos, cur);
49
+ return true;
50
+ }
51
+ }
52
+ const cur = findCellPosFromPmPos(view, pmPos);
53
+ if (cur !== null && cur !== anchorCellPos) {
54
+ cellDragging = true;
55
+ applyCellSelection(view, anchorCellPos, cur);
56
+ return true;
57
+ }
58
+ return false;
59
+ },
60
+ end() {
61
+ cellDragging = false;
62
+ },
63
+ get isCellDragging() {
64
+ return cellDragging;
65
+ }
66
+ };
67
+ }
68
+ //#endregion
69
+ export { CELL_SELECT_OVERFLOW_PX, applyCellSelection, createCellDragTracker, findCellPosFromPmPos };
@@ -0,0 +1,15 @@
1
+ import { Command } from "prosemirror-state";
2
+
3
+ //#region src/prosemirror/commands/sectionBreak.d.ts
4
+ /**
5
+ * Insert a "next page" section break at the cursor — starts a new section on a
6
+ * new page.
7
+ */
8
+ declare const insertSectionBreakNextPage: Command;
9
+ /**
10
+ * Insert a "continuous" section break at the cursor — starts a new section on
11
+ * the same page.
12
+ */
13
+ declare const insertSectionBreakContinuous: Command;
14
+ //#endregion
15
+ export { insertSectionBreakContinuous, insertSectionBreakNextPage };
@@ -0,0 +1,54 @@
1
+ import { TextSelection } from "prosemirror-state";
2
+ //#region src/prosemirror/commands/sectionBreak.ts
3
+ /**
4
+ * Insert a section break at the current cursor position.
5
+ *
6
+ * In OOXML a section break is the `sectPr` carried by the *last* paragraph of a
7
+ * section. So we split the current paragraph at the cursor, mark the first half
8
+ * with `sectionBreakType` (it becomes the section end), and leave the cursor in
9
+ * the second half (the first paragraph of the new section). Content after the
10
+ * cursor therefore flows into the new section — onto a new page for `nextPage`,
11
+ * or in place for `continuous`.
12
+ */
13
+ function insertSectionBreakAtCursor(breakType) {
14
+ return (state, dispatch) => {
15
+ const { schema } = state;
16
+ const paragraphType = schema.nodes["paragraph"];
17
+ if (!paragraphType) return false;
18
+ const { $from } = state.selection;
19
+ if (!($from.parent.isTextblock ? state.doc.resolve($from.before()).depth === 0 : $from.depth === 0)) return false;
20
+ if (dispatch) {
21
+ const tr = state.tr;
22
+ let cursorPos;
23
+ if ($from.parent.isTextblock) {
24
+ const paraPos = $from.before();
25
+ tr.split($from.pos);
26
+ const firstPara = tr.doc.nodeAt(paraPos);
27
+ if (firstPara) tr.setNodeMarkup(paraPos, void 0, {
28
+ ...firstPara.attrs,
29
+ sectionBreakType: breakType
30
+ });
31
+ cursorPos = tr.mapping.map($from.pos);
32
+ } else {
33
+ const pos = $from.pos;
34
+ tr.insert(pos, paragraphType.create({ sectionBreakType: breakType }));
35
+ cursorPos = pos + 1;
36
+ }
37
+ tr.setSelection(TextSelection.create(tr.doc, cursorPos));
38
+ dispatch(tr.scrollIntoView());
39
+ }
40
+ return true;
41
+ };
42
+ }
43
+ /**
44
+ * Insert a "next page" section break at the cursor — starts a new section on a
45
+ * new page.
46
+ */
47
+ const insertSectionBreakNextPage = insertSectionBreakAtCursor("nextPage");
48
+ /**
49
+ * Insert a "continuous" section break at the cursor — starts a new section on
50
+ * the same page.
51
+ */
52
+ const insertSectionBreakContinuous = insertSectionBreakAtCursor("continuous");
53
+ //#endregion
54
+ export { insertSectionBreakContinuous, insertSectionBreakNextPage };
@@ -0,0 +1,32 @@
1
+ import { Comment } from "../types/content.js";
2
+ import { EditorView } from "prosemirror-view";
3
+
4
+ //#region src/prosemirror/commentIdAllocator.d.ts
5
+ /** Sentinel ID for a comment that hasn't been persisted yet (anchored to selection). */
6
+ declare const PENDING_COMMENT_ID = -1;
7
+ type CommentIdAllocator = {
8
+ /** Allocate the next ID and advance the counter. */next(): number;
9
+ /**
10
+ * On document load, bump the counter above the highest ID found in the
11
+ * loaded comments and tracked-change marks so subsequent allocations don't
12
+ * collide with already-present IDs.
13
+ */
14
+ seedAbove(maxId: number): void;
15
+ };
16
+ /**
17
+ * Create an instance-scoped monotonic comment/revision ID allocator. IDs are
18
+ * never reused (deleting a comment does not free its ID), and the counter is
19
+ * private to this allocator — multiple editors get independent ID spaces.
20
+ */
21
+ declare function createCommentIdAllocator(): CommentIdAllocator;
22
+ /**
23
+ * Seed an allocator above every comment/revision ID currently in the document
24
+ * — comment objects (including replies, which carry no mark) plus
25
+ * tracked-change `revisionId` marks. Because `seedAbove` only ever raises the
26
+ * counter, this is safe to call on load (React) or before each allocation
27
+ * (Vue): new IDs never collide with or reuse an existing one, and the comment
28
+ * and revision ID spaces stay unified.
29
+ */
30
+ declare function seedCommentAllocator(allocator: CommentIdAllocator, comments: Comment[] | undefined, view: EditorView | null): void;
31
+ //#endregion
32
+ export { CommentIdAllocator, PENDING_COMMENT_ID, createCommentIdAllocator, seedCommentAllocator };
@@ -0,0 +1,35 @@
1
+ //#region src/prosemirror/commentIdAllocator.ts
2
+ /** Sentinel ID for a comment that hasn't been persisted yet (anchored to selection). */
3
+ const PENDING_COMMENT_ID = -1;
4
+ /**
5
+ * Create an instance-scoped monotonic comment/revision ID allocator. IDs are
6
+ * never reused (deleting a comment does not free its ID), and the counter is
7
+ * private to this allocator — multiple editors get independent ID spaces.
8
+ */
9
+ function createCommentIdAllocator() {
10
+ let nextId = 1;
11
+ return {
12
+ next: () => nextId++,
13
+ seedAbove(maxId) {
14
+ if (maxId >= nextId) nextId = maxId + 1;
15
+ }
16
+ };
17
+ }
18
+ /**
19
+ * Seed an allocator above every comment/revision ID currently in the document
20
+ * — comment objects (including replies, which carry no mark) plus
21
+ * tracked-change `revisionId` marks. Because `seedAbove` only ever raises the
22
+ * counter, this is safe to call on load (React) or before each allocation
23
+ * (Vue): new IDs never collide with or reuse an existing one, and the comment
24
+ * and revision ID spaces stay unified.
25
+ */
26
+ function seedCommentAllocator(allocator, comments, view) {
27
+ let max = 0;
28
+ for (const comment of comments ?? []) max = Math.max(max, comment.id);
29
+ if (view) view.state.doc.descendants((node) => {
30
+ for (const mark of node.marks) if (mark.attrs["revisionId"] != null) max = Math.max(max, mark.attrs["revisionId"]);
31
+ });
32
+ allocator.seedAbove(max);
33
+ }
34
+ //#endregion
35
+ export { PENDING_COMMENT_ID, createCommentIdAllocator, seedCommentAllocator };
@@ -0,0 +1,35 @@
1
+ import { Comment } from "../types/content.js";
2
+ import { CommentIdAllocator } from "./commentIdAllocator.js";
3
+ import { EditorView } from "prosemirror-view";
4
+
5
+ //#region src/prosemirror/commentOps.d.ts
6
+ /** Build a Comment object with a freshly-allocated ID. */
7
+ declare function createComment(allocator: CommentIdAllocator, text: string, authorName: string, parentId?: number): Comment;
8
+ type AddCommentOptions = {
9
+ paraId: string;
10
+ text: string;
11
+ author: string;
12
+ search?: string;
13
+ };
14
+ /**
15
+ * Locate the comment range (paragraph, narrowed by `search`), add the comment
16
+ * mark, dispatch, and return the created Comment. Returns null when the schema
17
+ * lacks a comment mark or the range can't be resolved. The caller owns adding
18
+ * the comment to its own state and showing the sidebar.
19
+ */
20
+ declare function addCommentToRange(view: EditorView, options: AddCommentOptions, allocator: CommentIdAllocator): Comment | null;
21
+ type ProposeChangeOptions = {
22
+ paraId: string;
23
+ search: string;
24
+ replaceWith: string;
25
+ author: string;
26
+ };
27
+ /**
28
+ * Apply a tracked change (insertion / deletion / replace) to the located range.
29
+ * `search === ''` means a pure insertion at the paragraph end; `replaceWith ===
30
+ * ''` means a pure deletion. Refuses to layer onto an existing tracked change.
31
+ * Returns true when a change was dispatched, false otherwise.
32
+ */
33
+ declare function applyProposedChange(view: EditorView, options: ProposeChangeOptions, allocator: CommentIdAllocator): boolean;
34
+ //#endregion
35
+ export { AddCommentOptions, ProposeChangeOptions, addCommentToRange, applyProposedChange, createComment };
@@ -0,0 +1,104 @@
1
+ import { findParagraphByParaId } from "./utils/findParagraphByParaId.js";
2
+ import { findTextInPmParagraph } from "./paraText.js";
3
+ //#region src/prosemirror/commentOps.ts
4
+ /** Build a Comment object with a freshly-allocated ID. */
5
+ function createComment(allocator, text, authorName, parentId) {
6
+ return {
7
+ id: allocator.next(),
8
+ author: authorName,
9
+ date: (/* @__PURE__ */ new Date()).toISOString(),
10
+ content: [{
11
+ type: "paragraph",
12
+ formatting: {},
13
+ content: [{
14
+ type: "run",
15
+ formatting: {},
16
+ content: [{
17
+ type: "text",
18
+ text
19
+ }]
20
+ }]
21
+ }],
22
+ ...parentId !== void 0 && { parentId }
23
+ };
24
+ }
25
+ /**
26
+ * Locate the comment range (paragraph, narrowed by `search`), add the comment
27
+ * mark, dispatch, and return the created Comment. Returns null when the schema
28
+ * lacks a comment mark or the range can't be resolved. The caller owns adding
29
+ * the comment to its own state and showing the sidebar.
30
+ */
31
+ function addCommentToRange(view, options, allocator) {
32
+ const { schema } = view.state;
33
+ if (!schema.marks["comment"]) return null;
34
+ const range = findParagraphByParaId(view.state.doc, options.paraId);
35
+ if (!range) return null;
36
+ let from = range.from + 1;
37
+ let to = range.to - 1;
38
+ if (options.search) {
39
+ const textRange = findTextInPmParagraph(view.state.doc, range.from, range.to, options.search);
40
+ if (!textRange) return null;
41
+ from = textRange.from;
42
+ to = textRange.to;
43
+ }
44
+ if (from >= to) return null;
45
+ const comment = createComment(allocator, options.text, options.author);
46
+ view.dispatch(view.state.tr.addMark(from, to, schema.marks["comment"].create({ commentId: comment.id })));
47
+ return comment;
48
+ }
49
+ /**
50
+ * Apply a tracked change (insertion / deletion / replace) to the located range.
51
+ * `search === ''` means a pure insertion at the paragraph end; `replaceWith ===
52
+ * ''` means a pure deletion. Refuses to layer onto an existing tracked change.
53
+ * Returns true when a change was dispatched, false otherwise.
54
+ */
55
+ function applyProposedChange(view, options, allocator) {
56
+ const { schema } = view.state;
57
+ if (!schema.marks["deletion"] || !schema.marks["insertion"]) return false;
58
+ const range = findParagraphByParaId(view.state.doc, options.paraId);
59
+ if (!range) return false;
60
+ const isInsertion = options.search === "";
61
+ const isDeletion = options.replaceWith === "";
62
+ if (isInsertion && isDeletion) return false;
63
+ let textFrom;
64
+ let textTo;
65
+ if (isInsertion) {
66
+ textFrom = range.to - 1;
67
+ textTo = range.to - 1;
68
+ } else {
69
+ const textRange = findTextInPmParagraph(view.state.doc, range.from, range.to, options.search);
70
+ if (!textRange) return false;
71
+ textFrom = textRange.from;
72
+ textTo = textRange.to;
73
+ }
74
+ if (textFrom < textTo) {
75
+ let overlaps = false;
76
+ view.state.doc.nodesBetween(textFrom, textTo, (node) => {
77
+ for (const m of node.marks) if (m.type === schema.marks["insertion"] || m.type === schema.marks["deletion"]) {
78
+ overlaps = true;
79
+ return false;
80
+ }
81
+ return true;
82
+ });
83
+ if (overlaps) return false;
84
+ }
85
+ const revisionId = allocator.next();
86
+ const date = (/* @__PURE__ */ new Date()).toISOString();
87
+ const deletionMark = schema.marks["deletion"].create({
88
+ revisionId,
89
+ author: options.author,
90
+ date
91
+ });
92
+ const insertionMark = schema.marks["insertion"].create({
93
+ revisionId,
94
+ author: options.author,
95
+ date
96
+ });
97
+ let tr = view.state.tr;
98
+ if (!isInsertion) tr = tr.addMark(textFrom, textTo, deletionMark);
99
+ if (!isDeletion) tr = tr.insert(textTo, schema.text(options.replaceWith, [insertionMark]));
100
+ view.dispatch(tr);
101
+ return true;
102
+ }
103
+ //#endregion
104
+ export { addCommentToRange, applyProposedChange, createComment };
@@ -5,6 +5,7 @@ import { setAutospacingBaseValue } from "../autospacingBase.js";
5
5
  import { shadingToRunShadingAttrs } from "./runShadingMark.js";
6
6
  import { assertValidProseMirrorDocument } from "../validation.js";
7
7
  import { mergeTextFormatting } from "../../utils/textFormattingMerge.js";
8
+ import { mergeParagraphFormatting } from "../../utils/paragraphFormattingMerge.js";
8
9
  import { marksToTextFormatting } from "./fromProseDoc.js";
9
10
  import { createStyleEngine } from "../../style-engine/styleEngine.js";
10
11
  import { buildRunFormattingOverrideAttrs } from "../extensions/marks/RunFormattingOverrideExtension.js";
@@ -77,8 +78,8 @@ function convertBlockSdt(blockSdt, convertBlocks) {
77
78
  * Resolves style-based text formatting and passes it to runs so that
78
79
  * paragraph styles (like Heading1) apply their font size, color, etc.
79
80
  */
80
- function convertParagraph(paragraph, styleResolver, activeCommentIds, extraRunFormatting) {
81
- const attrs = paragraphFormattingToAttrs(paragraph, styleResolver);
81
+ function convertParagraph(paragraph, styleResolver, activeCommentIds, extraRunFormatting, tableParagraphOverlay) {
82
+ const attrs = paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOverlay);
82
83
  const inlineNodes = [];
83
84
  let inlineOffset = 0;
84
85
  let bookmarksArr;
@@ -202,7 +203,7 @@ function canCarryTrackedRunMark(node, markType) {
202
203
  * If a styleResolver is provided, resolves style-based formatting and merges
203
204
  * with inline formatting. Inline formatting takes precedence.
204
205
  */
205
- function paragraphFormattingToAttrs(paragraph, styleResolver) {
206
+ function paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOverlay) {
206
207
  const formatting = paragraph.formatting;
207
208
  const styleId = formatting?.styleId;
208
209
  const attrs = {};
@@ -233,7 +234,7 @@ function paragraphFormattingToAttrs(paragraph, styleResolver) {
233
234
  };
234
235
  let stylePpr;
235
236
  if (styleResolver) {
236
- stylePpr = styleResolver.resolveParagraphStyle(styleId).paragraphFormatting;
237
+ stylePpr = styleResolver.resolveParagraphStyleInTable(styleId, tableParagraphOverlay).paragraphFormatting;
237
238
  set("alignment", formatting?.alignment ?? stylePpr?.alignment);
238
239
  set("spaceBefore", formatting?.spaceBefore ?? stylePpr?.spaceBefore);
239
240
  set("spaceAfter", formatting?.spaceAfter ?? stylePpr?.spaceAfter);
@@ -299,6 +300,22 @@ function paragraphFormattingToAttrs(paragraph, styleResolver) {
299
300
  return attrs;
300
301
  }
301
302
  /**
303
+ * Pick the paragraph-spacing fields out of a table style's (or conditional
304
+ * region's) `w:pPr` for use as the cell-paragraph cascade overlay. Narrower
305
+ * than the full `ParagraphFormatting` bag — see
306
+ * {@link TableCellParagraphSpacingOverlay}.
307
+ */
308
+ function extractTableParagraphSpacingOverlay(pPr) {
309
+ if (!pPr) return;
310
+ const overlay = {};
311
+ if (pPr.spaceBefore !== void 0) overlay.spaceBefore = pPr.spaceBefore;
312
+ if (pPr.spaceAfter !== void 0) overlay.spaceAfter = pPr.spaceAfter;
313
+ if (pPr.lineSpacing !== void 0) overlay.lineSpacing = pPr.lineSpacing;
314
+ if (pPr.lineSpacingRule !== void 0) overlay.lineSpacingRule = pPr.lineSpacingRule;
315
+ if (pPr.contextualSpacing !== void 0) overlay.contextualSpacing = pPr.contextualSpacing;
316
+ return Object.keys(overlay).length > 0 ? overlay : void 0;
317
+ }
318
+ /**
302
319
  * Resolve table style conditional formatting
303
320
  */
304
321
  function resolveTableStyleConditional(styleResolver, tableStyleId, conditionType) {
@@ -308,9 +325,11 @@ function resolveTableStyleConditional(styleResolver, tableStyleId, conditionType
308
325
  const conditional = style.tblStylePr.find((p) => p.type === conditionType);
309
326
  if (!conditional) return;
310
327
  const mergedRunProps = mergeTextFormatting(conditional.pPr?.runProperties ? resolveTextFormatting(conditional.pPr.runProperties, styleResolver) : void 0, conditional.rPr ? resolveTextFormatting(conditional.rPr, styleResolver) : void 0);
328
+ const paragraphSpacingOverlay = extractTableParagraphSpacingOverlay(conditional.pPr);
311
329
  const result = {};
312
330
  if (conditional.tcPr) result.tcPr = conditional.tcPr;
313
331
  if (mergedRunProps) result.rPr = mergedRunProps;
332
+ if (paragraphSpacingOverlay) result.pPr = paragraphSpacingOverlay;
314
333
  return result;
315
334
  }
316
335
  function resolveTableBaseStyle(styleResolver, tableStyleId) {
@@ -318,10 +337,12 @@ function resolveTableBaseStyle(styleResolver, tableStyleId) {
318
337
  const style = styleResolver.getStyle(tableStyleId);
319
338
  if (!style) return;
320
339
  const mergedRunProps = mergeTextFormatting(style.pPr?.runProperties ? resolveTextFormatting(style.pPr.runProperties, styleResolver) : void 0, style.rPr ? resolveTextFormatting(style.rPr, styleResolver) : void 0);
340
+ const paragraphSpacingOverlay = extractTableParagraphSpacingOverlay(style.pPr);
321
341
  const result = {};
322
342
  if (style.tcPr) result.tcPr = style.tcPr;
323
343
  if (mergedRunProps) result.rPr = mergedRunProps;
324
- return result.tcPr || result.rPr ? result : void 0;
344
+ if (paragraphSpacingOverlay) result.pPr = paragraphSpacingOverlay;
345
+ return result.tcPr || result.rPr || result.pPr ? result : void 0;
325
346
  }
326
347
  function mergeConditionalStyles(base, override) {
327
348
  if (!base && !override) return;
@@ -351,6 +372,8 @@ function mergeConditionalStyles(base, override) {
351
372
  }
352
373
  const mergedRPr = mergeTextFormatting(base.rPr, override.rPr);
353
374
  if (mergedRPr) merged.rPr = mergedRPr;
375
+ const mergedPPr = mergeParagraphFormatting(base.pPr, override.pPr);
376
+ if (mergedPPr) merged.pPr = mergedPPr;
354
377
  return merged;
355
378
  }
356
379
  function hasDirectRunFormatting(formatting) {
@@ -725,7 +748,7 @@ function convertTableCell(cell, styleResolver, isHeader, gridWidthPercent, condi
725
748
  if (preserveVMergeRestart) attrs._preserveVMergeRestart = true;
726
749
  if (vMergeContinuationCells && vMergeContinuationCells.length > 0) attrs._docxVMergeContinuationCells = vMergeContinuationCells;
727
750
  const contentNodes = [];
728
- for (const content of cell.content) if (content.type === "paragraph") contentNodes.push(convertParagraph(content, styleResolver, void 0, conditionalStyle?.rPr));
751
+ for (const content of cell.content) if (content.type === "paragraph") contentNodes.push(convertParagraph(content, styleResolver, void 0, conditionalStyle?.rPr, conditionalStyle?.pPr));
729
752
  else contentNodes.push(convertTable(content, styleResolver, theme));
730
753
  if (contentNodes.length === 0) contentNodes.push(schema.node("paragraph", {}, []));
731
754
  const nodeType = isHeader ? "tableHeader" : "tableCell";
@@ -0,0 +1,3 @@
1
+ import { ExtensionManager } from "./ExtensionManager.js";
2
+ import { StarterKitOptions, createStarterKit } from "./StarterKit.js";
3
+ export { ExtensionManager, type StarterKitOptions, createStarterKit };
@@ -0,0 +1,3 @@
1
+ import { ExtensionManager } from "./ExtensionManager.js";
2
+ import { createStarterKit } from "./StarterKit.js";
3
+ export { ExtensionManager, createStarterKit };
@@ -0,0 +1,42 @@
1
+ import { Node } from "prosemirror-model";
2
+ import { EditorView } from "prosemirror-view";
3
+
4
+ //#region src/prosemirror/imageCommit.d.ts
5
+ /**
6
+ * An image resize handle: the 4 corners ('nw'/'ne'/'se'/'sw') resize both axes
7
+ * and keep the aspect ratio; the 4 edge midpoints ('n'/'s'/'e'/'w') resize a
8
+ * single axis so the user can deliberately stretch the image (break aspect).
9
+ */
10
+ type ImageResizeHandle = "nw" | "ne" | "se" | "sw" | "n" | "s" | "e" | "w";
11
+ /**
12
+ * New image dimensions for a resize drag, shared by the React and Vue overlays
13
+ * (issue #266). Corner handles drive both axes (aspect-locked unless
14
+ * `lockAspect` is false, e.g. Shift held); edge handles drive one axis and
15
+ * never lock. The non-driven axis is returned unchanged. Driven axes are
16
+ * clamped to a sane pixel range.
17
+ */
18
+ declare function calculateResizedImageDimensions(handle: ImageResizeHandle, deltaX: number, deltaY: number, startWidth: number, startHeight: number, lockAspect: boolean): {
19
+ width: number;
20
+ height: number;
21
+ };
22
+ /** True when the image is floating (anchored) rather than inline. */
23
+ declare function isFloatingImage(node: Node): boolean;
24
+ /**
25
+ * Resize commit: set the image node's `width`/`height`. Returns `pmPos` to
26
+ * re-select, or null if the position no longer holds an image.
27
+ */
28
+ declare function commitImageResize(view: EditorView, pmPos: number, newWidth: number, newHeight: number): number | null;
29
+ /**
30
+ * Floating drag commit: rewrite the anchor's margin-relative `position`
31
+ * offsets (in EMU) so the image lands at the drop point while staying
32
+ * floating. Returns `pmPos` to re-select, or null on no-op / failure.
33
+ */
34
+ declare function commitImageFloatMove(view: EditorView, pmPos: number, hOffsetEmu: number, vOffsetEmu: number): number | null;
35
+ /**
36
+ * Inline drag commit: move the image node to `dropPos` via a delete + insert
37
+ * pair. Returns the PM position to re-select, or null when the drop is a
38
+ * no-op (same slot) or fails.
39
+ */
40
+ declare function commitImageInlineMove(view: EditorView, pmPos: number, dropPos: number): number | null;
41
+ //#endregion
42
+ export { ImageResizeHandle, calculateResizedImageDimensions, commitImageFloatMove, commitImageInlineMove, commitImageResize, isFloatingImage };