@sametalmdr/word-editor 0.2.0-alpha → 0.3.0-alpha

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 (82) hide show
  1. package/CHANGELOG.md +108 -70
  2. package/LICENSE +21 -21
  3. package/README.md +286 -247
  4. package/dist/canvas/box-clipboard.js +238 -238
  5. package/dist/canvas/box-selection.js +80 -80
  6. package/dist/canvas/canvas-editor.js +502 -502
  7. package/dist/canvas/controller.js +717 -717
  8. package/dist/canvas/geometry.js +131 -131
  9. package/dist/canvas/gesture.d.ts +0 -1
  10. package/dist/canvas/gesture.js +289 -289
  11. package/dist/canvas/handles.js +212 -212
  12. package/dist/canvas/menu.js +82 -82
  13. package/dist/canvas/mode-controller.js +81 -81
  14. package/dist/canvas/nudge.js +108 -108
  15. package/dist/canvas/overlay.js +149 -149
  16. package/dist/canvas/snap.js +132 -132
  17. package/dist/canvas/viewport.js +112 -112
  18. package/dist/canvas/z-order.js +171 -171
  19. package/dist/commands/alignment.js +150 -150
  20. package/dist/commands/box.js +272 -272
  21. package/dist/commands/clipboard.js +93 -93
  22. package/dist/commands/graphemes.js +112 -112
  23. package/dist/commands/marks.js +153 -153
  24. package/dist/commands/paragraph-props.js +176 -176
  25. package/dist/commands/size.js +368 -368
  26. package/dist/commands/text.js +291 -291
  27. package/dist/docx.d.ts +1 -0
  28. package/dist/docx.js +15 -13
  29. package/dist/editor/editing-host.js +434 -434
  30. package/dist/element.js +41 -41
  31. package/dist/fonts/OFL-Carlito.txt +94 -0
  32. package/dist/fonts/calibri-metrics.d.ts +27 -0
  33. package/dist/fonts/calibri-metrics.js +88 -0
  34. package/dist/history/history.js +433 -433
  35. package/dist/index.js +63 -63
  36. package/dist/input/beforeinput.js +224 -224
  37. package/dist/input/clipboard.js +107 -107
  38. package/dist/input/composition.js +341 -341
  39. package/dist/input/keymap.js +54 -54
  40. package/dist/io/ooxml/custgeom.js +72 -72
  41. package/dist/io/ooxml/drawingml.js +232 -232
  42. package/dist/io/ooxml/edits.js +67 -67
  43. package/dist/io/ooxml/export-splice.js +880 -880
  44. package/dist/io/ooxml/export.js +365 -365
  45. package/dist/io/ooxml/fill.d.ts +93 -8
  46. package/dist/io/ooxml/fill.js +618 -562
  47. package/dist/io/ooxml/fit.d.ts +252 -0
  48. package/dist/io/ooxml/fit.js +598 -0
  49. package/dist/io/ooxml/import-gate.js +200 -200
  50. package/dist/io/ooxml/import.js +691 -691
  51. package/dist/io/ooxml/inflate.js +126 -126
  52. package/dist/io/ooxml/media.js +87 -87
  53. package/dist/io/ooxml/ns.d.ts +2 -0
  54. package/dist/io/ooxml/ns.js +26 -21
  55. package/dist/io/ooxml/numbering.js +290 -290
  56. package/dist/io/ooxml/opc-write.js +171 -171
  57. package/dist/io/ooxml/provenance.js +93 -93
  58. package/dist/io/ooxml/recipe.js +708 -708
  59. package/dist/io/ooxml/relationships.js +59 -59
  60. package/dist/io/ooxml/run-fonts.d.ts +89 -0
  61. package/dist/io/ooxml/run-fonts.js +378 -0
  62. package/dist/io/ooxml/slots.d.ts +4 -0
  63. package/dist/io/ooxml/slots.js +582 -581
  64. package/dist/io/ooxml/styles-resolve.js +257 -257
  65. package/dist/io/ooxml/units.js +43 -43
  66. package/dist/io/ooxml/value.js +120 -120
  67. package/dist/io/ooxml/xml-scan.js +616 -616
  68. package/dist/io/ooxml/zip-container.js +228 -228
  69. package/dist/io/ooxml/zip-reject.js +106 -106
  70. package/dist/measure/placement.js +83 -83
  71. package/dist/measure/report.js +75 -75
  72. package/dist/model/ids.js +70 -70
  73. package/dist/model/normalize.js +146 -146
  74. package/dist/model/position.js +283 -283
  75. package/dist/model/transaction.js +209 -209
  76. package/dist/platform/capabilities.js +25 -25
  77. package/dist/react/index.js +21 -21
  78. package/dist/selection/controller.js +315 -315
  79. package/dist/selection/dom-map.js +334 -334
  80. package/dist/state/editor-state.js +109 -109
  81. package/dist/styles.css +66 -66
  82. package/package.json +94 -93
@@ -1,291 +1,291 @@
1
- import { devAssert } from "../dev.js";
2
- import { createRun, locateParagraph, paragraphLength, paragraphText } from "../model/node.js";
3
- import {
4
- collapsedSelection,
5
- containerIdOf,
6
- createPosition,
7
- isCollapsed,
8
- isSingleContainer,
9
- paragraphIdOf,
10
- selectionRange,
11
- withOffset,
12
- } from "../model/position.js";
13
- import { deleteStep, insertStep, mergeParagraphsStep, splitParagraphStep } from "../model/step.js";
14
- import { createTransaction } from "../model/transaction.js";
15
- import { marksAt } from "./marks.js";
16
- import { previousGraphemeStart, nextGraphemeEnd } from "./graphemes.js";
17
-
18
- /**
19
- * Deletes the selected range, appending steps to `tr`, and returns the position
20
- * the selection collapses to — already expressed in post-deletion coordinates.
21
- *
22
- * A multi-paragraph range decomposes into per-paragraph deletes plus merges,
23
- * because `DeleteStep` never spans paragraphs (ADR-0003):
24
- *
25
- * A|B|C selected from A@2 to C@3
26
- * delete A@2..A.end · delete all of B · delete C@0..C@3
27
- * merge A (absorbs the emptied B) · merge A again (absorbs C's remainder)
28
- *
29
- * The range may span paragraphs but never containers (ADR-0007), which is what
30
- * keeps that decomposition unchanged from P1 — every step it emits stays inside
31
- * one container.
32
- *
33
- * @param {import("../model/transaction.js").TransactionBuilder} tr
34
- * @param {import("../model/position.js").Selection} selection
35
- * @returns {import("../model/position.js").Position}
36
- */
37
- export function deleteRange(tr, selection) {
38
- const doc = tr.doc;
39
- devAssert(
40
- isSingleContainer(selection),
41
- "deleteRange: a text selection may not span containers",
42
- );
43
- const range = selectionRange(doc, selection);
44
- if (range === null || !isSingleContainer(selection)) {
45
- return selection.head;
46
- }
47
-
48
- const fromAt = locateParagraph(doc, paragraphIdOf(range.from));
49
- const toAt = locateParagraph(doc, paragraphIdOf(range.to));
50
- if (fromAt === null || toAt === null || fromAt.containerIndex !== toAt.containerIndex) {
51
- return range.from;
52
- }
53
-
54
- if (fromAt.paraIndex === toAt.paraIndex) {
55
- if (range.to.offset > range.from.offset) {
56
- tr.step(deleteStep(range.from, range.to));
57
- }
58
- return range.from;
59
- }
60
-
61
- const container = fromAt.container;
62
- const containerId = container.id;
63
- const firstId = fromAt.para.id;
64
-
65
- // Trim each paragraph first, while their ids and offsets are still valid.
66
- const firstEnd = paragraphLength(fromAt.para);
67
- if (firstEnd > range.from.offset) {
68
- tr.step(deleteStep(range.from, withOffset(range.from, firstEnd)));
69
- }
70
-
71
- for (let i = fromAt.paraIndex + 1; i < toAt.paraIndex; i += 1) {
72
- const para = container.content[i];
73
- const length = paragraphLength(para);
74
- if (length > 0) {
75
- tr.step(
76
- deleteStep(
77
- createPosition(containerId, para.id, 0),
78
- createPosition(containerId, para.id, length),
79
- ),
80
- );
81
- }
82
- }
83
-
84
- const lastId = toAt.para.id;
85
- if (range.to.offset > 0) {
86
- tr.step(
87
- deleteStep(
88
- createPosition(containerId, lastId, 0),
89
- createPosition(containerId, lastId, range.to.offset),
90
- ),
91
- );
92
- }
93
-
94
- // Then pull the emptied paragraphs into the first one.
95
- for (let i = fromAt.paraIndex; i < toAt.paraIndex; i += 1) {
96
- tr.step(mergeParagraphsStep(firstId));
97
- }
98
-
99
- return createPosition(containerId, firstId, range.from.offset);
100
- }
101
-
102
- /**
103
- * Inserts text at the cursor, replacing the selection if there is one.
104
- *
105
- * The replacement happens **in the same transaction** as the insert. Two
106
- * transactions would mean two Ctrl+Z presses to undo one user action.
107
- *
108
- * @param {import("../state/editor-state.js").EditorState} state
109
- * @param {string} text
110
- * @param {import("../model/transaction.js").TransactionOrigin} [origin]
111
- * @returns {import("../model/transaction.js").Transaction | null}
112
- */
113
- export function insertText(state, text, origin = "input") {
114
- if (text === "") {
115
- return null;
116
- }
117
-
118
- const tr = createTransaction(state.doc, state.selection, origin);
119
- const at = isCollapsed(state.selection)
120
- ? state.selection.head
121
- : deleteRange(tr, state.selection);
122
-
123
- const marks = state.storedMarks ?? marksAt(tr.doc, at);
124
- tr.step(insertStep(at, [createRun(text, marks)]));
125
- tr.setSelection(collapsedSelection(withOffset(at, at.offset + text.length)));
126
-
127
- return tr.build();
128
- }
129
-
130
- /**
131
- * Enter. Replaces the selection first, same single-transaction rule as
132
- * `insertText`. The new paragraph is a sibling inside the same container — Enter
133
- * never creates a box.
134
- * @param {import("../state/editor-state.js").EditorState} state
135
- * @param {import("../model/ids.js").IdFactory} ids
136
- * @returns {import("../model/transaction.js").Transaction}
137
- */
138
- export function splitParagraph(state, ids) {
139
- const tr = createTransaction(state.doc, state.selection, "input");
140
- const at = isCollapsed(state.selection)
141
- ? state.selection.head
142
- : deleteRange(tr, state.selection);
143
-
144
- const newId = ids.next();
145
- tr.step(splitParagraphStep(at, newId));
146
- tr.setSelection(collapsedSelection(createPosition(containerIdOf(at), newId, 0)));
147
-
148
- return tr.build();
149
- }
150
-
151
- /**
152
- * Backspace.
153
- *
154
- * | selection | position | action |
155
- * |---|---|---|
156
- * | range | — | delete the range |
157
- * | collapsed | `offset > 0` | delete the previous grapheme |
158
- * | collapsed | `offset === 0`, a paragraph precedes **in this container** | merge into it |
159
- * | collapsed | `offset === 0`, first paragraph of the container | no-op |
160
- *
161
- * The last row is the container rule: backspace at the top of a box does
162
- * nothing. It does **not** reach back into the previous box — boxes have no
163
- * meaningful document order between them, so merging across one would join two
164
- * unrelated pieces of the page.
165
- *
166
- * The merge case has to compute the join point *before* the step runs, because
167
- * the current paragraph is about to stop existing.
168
- *
169
- * @param {import("../state/editor-state.js").EditorState} state
170
- * @returns {import("../model/transaction.js").Transaction | null}
171
- */
172
- export function deleteBackward(state) {
173
- const { doc, selection } = state;
174
-
175
- if (!isCollapsed(selection)) {
176
- return deleteSelection(state);
177
- }
178
-
179
- const pos = selection.head;
180
- const at = locateParagraph(doc, paragraphIdOf(pos));
181
- if (at === null) {
182
- return null;
183
- }
184
-
185
- if (pos.offset > 0) {
186
- const text = paragraphText(at.para);
187
- const start = previousGraphemeStart(text, pos.offset);
188
- const tr = createTransaction(doc, selection, "input");
189
- tr.step(deleteStep(withOffset(pos, start), pos));
190
- tr.setSelection(collapsedSelection(withOffset(pos, start)));
191
- return tr.build();
192
- }
193
-
194
- if (at.paraIndex === 0) {
195
- return null;
196
- }
197
-
198
- const previous = at.container.content[at.paraIndex - 1];
199
- const joinAt = createPosition(at.container.id, previous.id, paragraphLength(previous));
200
-
201
- const tr = createTransaction(doc, selection, "input");
202
- tr.step(mergeParagraphsStep(previous.id));
203
- tr.setSelection(collapsedSelection(joinAt));
204
- return tr.build();
205
- }
206
-
207
- /**
208
- * Delete (forward). Mirrors `deleteBackward`:
209
- *
210
- * | selection | position | action |
211
- * |---|---|---|
212
- * | range | — | delete the range |
213
- * | collapsed | `offset < len` | delete the next grapheme |
214
- * | collapsed | `offset === len`, a paragraph follows **in this container** | merge it into this one |
215
- * | collapsed | `offset === len`, last paragraph of the container | no-op |
216
- *
217
- * Both directions land on the same join point; forward deletion simply already
218
- * sits there, so the selection does not move.
219
- *
220
- * @param {import("../state/editor-state.js").EditorState} state
221
- * @returns {import("../model/transaction.js").Transaction | null}
222
- */
223
- export function deleteForward(state) {
224
- const { doc, selection } = state;
225
-
226
- if (!isCollapsed(selection)) {
227
- return deleteSelection(state);
228
- }
229
-
230
- const pos = selection.head;
231
- const at = locateParagraph(doc, paragraphIdOf(pos));
232
- if (at === null) {
233
- return null;
234
- }
235
-
236
- const { para } = at;
237
- const length = paragraphLength(para);
238
-
239
- if (pos.offset < length) {
240
- const end = nextGraphemeEnd(paragraphText(para), pos.offset);
241
- const tr = createTransaction(doc, selection, "input");
242
- tr.step(deleteStep(pos, withOffset(pos, end)));
243
- tr.setSelection(collapsedSelection(pos));
244
- return tr.build();
245
- }
246
-
247
- if (at.paraIndex === at.container.content.length - 1) {
248
- return null;
249
- }
250
-
251
- const tr = createTransaction(doc, selection, "input");
252
- tr.step(mergeParagraphsStep(para.id));
253
- tr.setSelection(collapsedSelection(pos));
254
- return tr.build();
255
- }
256
-
257
- /**
258
- * Deletes the current selection and nothing else.
259
- * @param {import("../state/editor-state.js").EditorState} state
260
- * @param {import("../model/transaction.js").TransactionOrigin} [origin]
261
- * @returns {import("../model/transaction.js").Transaction | null}
262
- */
263
- export function deleteSelection(state, origin = "input") {
264
- if (isCollapsed(state.selection)) {
265
- return null;
266
- }
267
- const tr = createTransaction(state.doc, state.selection, origin);
268
- const at = deleteRange(tr, state.selection);
269
- if (!tr.changesDoc) {
270
- return null;
271
- }
272
- tr.setSelection(collapsedSelection(at));
273
- return tr.build();
274
- }
275
-
276
- /**
277
- * Deletes an explicit range — used for the ranges the browser computes for word
278
- * and line deletion via `getTargetRanges()`.
279
- * @param {import("../state/editor-state.js").EditorState} state
280
- * @param {import("../model/position.js").Selection} range
281
- * @returns {import("../model/transaction.js").Transaction | null}
282
- */
283
- export function deleteExplicitRange(state, range) {
284
- const tr = createTransaction(state.doc, state.selection, "input");
285
- const at = deleteRange(tr, range);
286
- if (!tr.changesDoc) {
287
- return null;
288
- }
289
- tr.setSelection(collapsedSelection(at));
290
- return tr.build();
291
- }
1
+ import { devAssert } from "../dev.js";
2
+ import { createRun, locateParagraph, paragraphLength, paragraphText } from "../model/node.js";
3
+ import {
4
+ collapsedSelection,
5
+ containerIdOf,
6
+ createPosition,
7
+ isCollapsed,
8
+ isSingleContainer,
9
+ paragraphIdOf,
10
+ selectionRange,
11
+ withOffset,
12
+ } from "../model/position.js";
13
+ import { deleteStep, insertStep, mergeParagraphsStep, splitParagraphStep } from "../model/step.js";
14
+ import { createTransaction } from "../model/transaction.js";
15
+ import { marksAt } from "./marks.js";
16
+ import { previousGraphemeStart, nextGraphemeEnd } from "./graphemes.js";
17
+
18
+ /**
19
+ * Deletes the selected range, appending steps to `tr`, and returns the position
20
+ * the selection collapses to — already expressed in post-deletion coordinates.
21
+ *
22
+ * A multi-paragraph range decomposes into per-paragraph deletes plus merges,
23
+ * because `DeleteStep` never spans paragraphs (ADR-0003):
24
+ *
25
+ * A|B|C selected from A@2 to C@3
26
+ * delete A@2..A.end · delete all of B · delete C@0..C@3
27
+ * merge A (absorbs the emptied B) · merge A again (absorbs C's remainder)
28
+ *
29
+ * The range may span paragraphs but never containers (ADR-0007), which is what
30
+ * keeps that decomposition unchanged from P1 — every step it emits stays inside
31
+ * one container.
32
+ *
33
+ * @param {import("../model/transaction.js").TransactionBuilder} tr
34
+ * @param {import("../model/position.js").Selection} selection
35
+ * @returns {import("../model/position.js").Position}
36
+ */
37
+ export function deleteRange(tr, selection) {
38
+ const doc = tr.doc;
39
+ devAssert(
40
+ isSingleContainer(selection),
41
+ "deleteRange: a text selection may not span containers",
42
+ );
43
+ const range = selectionRange(doc, selection);
44
+ if (range === null || !isSingleContainer(selection)) {
45
+ return selection.head;
46
+ }
47
+
48
+ const fromAt = locateParagraph(doc, paragraphIdOf(range.from));
49
+ const toAt = locateParagraph(doc, paragraphIdOf(range.to));
50
+ if (fromAt === null || toAt === null || fromAt.containerIndex !== toAt.containerIndex) {
51
+ return range.from;
52
+ }
53
+
54
+ if (fromAt.paraIndex === toAt.paraIndex) {
55
+ if (range.to.offset > range.from.offset) {
56
+ tr.step(deleteStep(range.from, range.to));
57
+ }
58
+ return range.from;
59
+ }
60
+
61
+ const container = fromAt.container;
62
+ const containerId = container.id;
63
+ const firstId = fromAt.para.id;
64
+
65
+ // Trim each paragraph first, while their ids and offsets are still valid.
66
+ const firstEnd = paragraphLength(fromAt.para);
67
+ if (firstEnd > range.from.offset) {
68
+ tr.step(deleteStep(range.from, withOffset(range.from, firstEnd)));
69
+ }
70
+
71
+ for (let i = fromAt.paraIndex + 1; i < toAt.paraIndex; i += 1) {
72
+ const para = container.content[i];
73
+ const length = paragraphLength(para);
74
+ if (length > 0) {
75
+ tr.step(
76
+ deleteStep(
77
+ createPosition(containerId, para.id, 0),
78
+ createPosition(containerId, para.id, length),
79
+ ),
80
+ );
81
+ }
82
+ }
83
+
84
+ const lastId = toAt.para.id;
85
+ if (range.to.offset > 0) {
86
+ tr.step(
87
+ deleteStep(
88
+ createPosition(containerId, lastId, 0),
89
+ createPosition(containerId, lastId, range.to.offset),
90
+ ),
91
+ );
92
+ }
93
+
94
+ // Then pull the emptied paragraphs into the first one.
95
+ for (let i = fromAt.paraIndex; i < toAt.paraIndex; i += 1) {
96
+ tr.step(mergeParagraphsStep(firstId));
97
+ }
98
+
99
+ return createPosition(containerId, firstId, range.from.offset);
100
+ }
101
+
102
+ /**
103
+ * Inserts text at the cursor, replacing the selection if there is one.
104
+ *
105
+ * The replacement happens **in the same transaction** as the insert. Two
106
+ * transactions would mean two Ctrl+Z presses to undo one user action.
107
+ *
108
+ * @param {import("../state/editor-state.js").EditorState} state
109
+ * @param {string} text
110
+ * @param {import("../model/transaction.js").TransactionOrigin} [origin]
111
+ * @returns {import("../model/transaction.js").Transaction | null}
112
+ */
113
+ export function insertText(state, text, origin = "input") {
114
+ if (text === "") {
115
+ return null;
116
+ }
117
+
118
+ const tr = createTransaction(state.doc, state.selection, origin);
119
+ const at = isCollapsed(state.selection)
120
+ ? state.selection.head
121
+ : deleteRange(tr, state.selection);
122
+
123
+ const marks = state.storedMarks ?? marksAt(tr.doc, at);
124
+ tr.step(insertStep(at, [createRun(text, marks)]));
125
+ tr.setSelection(collapsedSelection(withOffset(at, at.offset + text.length)));
126
+
127
+ return tr.build();
128
+ }
129
+
130
+ /**
131
+ * Enter. Replaces the selection first, same single-transaction rule as
132
+ * `insertText`. The new paragraph is a sibling inside the same container — Enter
133
+ * never creates a box.
134
+ * @param {import("../state/editor-state.js").EditorState} state
135
+ * @param {import("../model/ids.js").IdFactory} ids
136
+ * @returns {import("../model/transaction.js").Transaction}
137
+ */
138
+ export function splitParagraph(state, ids) {
139
+ const tr = createTransaction(state.doc, state.selection, "input");
140
+ const at = isCollapsed(state.selection)
141
+ ? state.selection.head
142
+ : deleteRange(tr, state.selection);
143
+
144
+ const newId = ids.next();
145
+ tr.step(splitParagraphStep(at, newId));
146
+ tr.setSelection(collapsedSelection(createPosition(containerIdOf(at), newId, 0)));
147
+
148
+ return tr.build();
149
+ }
150
+
151
+ /**
152
+ * Backspace.
153
+ *
154
+ * | selection | position | action |
155
+ * |---|---|---|
156
+ * | range | — | delete the range |
157
+ * | collapsed | `offset > 0` | delete the previous grapheme |
158
+ * | collapsed | `offset === 0`, a paragraph precedes **in this container** | merge into it |
159
+ * | collapsed | `offset === 0`, first paragraph of the container | no-op |
160
+ *
161
+ * The last row is the container rule: backspace at the top of a box does
162
+ * nothing. It does **not** reach back into the previous box — boxes have no
163
+ * meaningful document order between them, so merging across one would join two
164
+ * unrelated pieces of the page.
165
+ *
166
+ * The merge case has to compute the join point *before* the step runs, because
167
+ * the current paragraph is about to stop existing.
168
+ *
169
+ * @param {import("../state/editor-state.js").EditorState} state
170
+ * @returns {import("../model/transaction.js").Transaction | null}
171
+ */
172
+ export function deleteBackward(state) {
173
+ const { doc, selection } = state;
174
+
175
+ if (!isCollapsed(selection)) {
176
+ return deleteSelection(state);
177
+ }
178
+
179
+ const pos = selection.head;
180
+ const at = locateParagraph(doc, paragraphIdOf(pos));
181
+ if (at === null) {
182
+ return null;
183
+ }
184
+
185
+ if (pos.offset > 0) {
186
+ const text = paragraphText(at.para);
187
+ const start = previousGraphemeStart(text, pos.offset);
188
+ const tr = createTransaction(doc, selection, "input");
189
+ tr.step(deleteStep(withOffset(pos, start), pos));
190
+ tr.setSelection(collapsedSelection(withOffset(pos, start)));
191
+ return tr.build();
192
+ }
193
+
194
+ if (at.paraIndex === 0) {
195
+ return null;
196
+ }
197
+
198
+ const previous = at.container.content[at.paraIndex - 1];
199
+ const joinAt = createPosition(at.container.id, previous.id, paragraphLength(previous));
200
+
201
+ const tr = createTransaction(doc, selection, "input");
202
+ tr.step(mergeParagraphsStep(previous.id));
203
+ tr.setSelection(collapsedSelection(joinAt));
204
+ return tr.build();
205
+ }
206
+
207
+ /**
208
+ * Delete (forward). Mirrors `deleteBackward`:
209
+ *
210
+ * | selection | position | action |
211
+ * |---|---|---|
212
+ * | range | — | delete the range |
213
+ * | collapsed | `offset < len` | delete the next grapheme |
214
+ * | collapsed | `offset === len`, a paragraph follows **in this container** | merge it into this one |
215
+ * | collapsed | `offset === len`, last paragraph of the container | no-op |
216
+ *
217
+ * Both directions land on the same join point; forward deletion simply already
218
+ * sits there, so the selection does not move.
219
+ *
220
+ * @param {import("../state/editor-state.js").EditorState} state
221
+ * @returns {import("../model/transaction.js").Transaction | null}
222
+ */
223
+ export function deleteForward(state) {
224
+ const { doc, selection } = state;
225
+
226
+ if (!isCollapsed(selection)) {
227
+ return deleteSelection(state);
228
+ }
229
+
230
+ const pos = selection.head;
231
+ const at = locateParagraph(doc, paragraphIdOf(pos));
232
+ if (at === null) {
233
+ return null;
234
+ }
235
+
236
+ const { para } = at;
237
+ const length = paragraphLength(para);
238
+
239
+ if (pos.offset < length) {
240
+ const end = nextGraphemeEnd(paragraphText(para), pos.offset);
241
+ const tr = createTransaction(doc, selection, "input");
242
+ tr.step(deleteStep(pos, withOffset(pos, end)));
243
+ tr.setSelection(collapsedSelection(pos));
244
+ return tr.build();
245
+ }
246
+
247
+ if (at.paraIndex === at.container.content.length - 1) {
248
+ return null;
249
+ }
250
+
251
+ const tr = createTransaction(doc, selection, "input");
252
+ tr.step(mergeParagraphsStep(para.id));
253
+ tr.setSelection(collapsedSelection(pos));
254
+ return tr.build();
255
+ }
256
+
257
+ /**
258
+ * Deletes the current selection and nothing else.
259
+ * @param {import("../state/editor-state.js").EditorState} state
260
+ * @param {import("../model/transaction.js").TransactionOrigin} [origin]
261
+ * @returns {import("../model/transaction.js").Transaction | null}
262
+ */
263
+ export function deleteSelection(state, origin = "input") {
264
+ if (isCollapsed(state.selection)) {
265
+ return null;
266
+ }
267
+ const tr = createTransaction(state.doc, state.selection, origin);
268
+ const at = deleteRange(tr, state.selection);
269
+ if (!tr.changesDoc) {
270
+ return null;
271
+ }
272
+ tr.setSelection(collapsedSelection(at));
273
+ return tr.build();
274
+ }
275
+
276
+ /**
277
+ * Deletes an explicit range — used for the ranges the browser computes for word
278
+ * and line deletion via `getTargetRanges()`.
279
+ * @param {import("../state/editor-state.js").EditorState} state
280
+ * @param {import("../model/position.js").Selection} range
281
+ * @returns {import("../model/transaction.js").Transaction | null}
282
+ */
283
+ export function deleteExplicitRange(state, range) {
284
+ const tr = createTransaction(state.doc, state.selection, "input");
285
+ const at = deleteRange(tr, range);
286
+ if (!tr.changesDoc) {
287
+ return null;
288
+ }
289
+ tr.setSelection(collapsedSelection(at));
290
+ return tr.build();
291
+ }
package/dist/docx.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { importDocx } from "./io/ooxml/import.js";
2
2
  export { exportDocx } from "./io/ooxml/export.js";
3
+ export { checkFit } from "./io/ooxml/fit.js";
3
4
  export { inspectTemplate, validateTemplate, fillTemplate, TemplateError, ENGINE_VERSION } from "./io/ooxml/fill.js";
package/dist/docx.js CHANGED
@@ -1,13 +1,15 @@
1
- // The /docx subpath (FR2, ADR-0037): DOCX import/export for the editor, and
2
- // the template fill engine. DOM-free — it runs in browsers, Node and Deno
3
- // (invariant #4) and needs only TextEncoder/TextDecoder and
4
- // CompressionStream/DecompressionStream("deflate-raw"). It makes no network
5
- // request (invariant #3).
6
- //
7
- // importDocx/exportDocx are the editor's own IO; their shapes are not yet
8
- // stable at 0.x alpha. The fill engine's surface is inspectTemplate,
9
- // validateTemplate, fillTemplate and TemplateError.
10
-
11
- export { importDocx } from "./io/ooxml/import.js";
12
- export { exportDocx } from "./io/ooxml/export.js";
13
- export { inspectTemplate, validateTemplate, fillTemplate, TemplateError, ENGINE_VERSION } from "./io/ooxml/fill.js";
1
+ // The /docx subpath (FR2, ADR-0037): DOCX import/export for the editor, and
2
+ // the template fill engine. DOM-free — it runs in browsers, Node and Deno
3
+ // (invariant #4) and needs only TextEncoder/TextDecoder and
4
+ // CompressionStream/DecompressionStream("deflate-raw"). It makes no network
5
+ // request (invariant #3).
6
+ //
7
+ // importDocx/exportDocx are the editor's own IO; their shapes are not yet
8
+ // stable at 0.x alpha. The fill engine's surface is inspectTemplate,
9
+ // validateTemplate, fillTemplate and TemplateError, and checkFit (FR4a,
10
+ // ADR-0042): whether each value stays on the lines its box gives it.
11
+
12
+ export { importDocx } from "./io/ooxml/import.js";
13
+ export { exportDocx } from "./io/ooxml/export.js";
14
+ export { inspectTemplate, validateTemplate, fillTemplate, TemplateError, ENGINE_VERSION } from "./io/ooxml/fill.js";
15
+ export { checkFit } from "./io/ooxml/fit.js";