@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
package/dist/model/ids.js CHANGED
@@ -1,70 +1,70 @@
1
- /**
2
- * @typedef {object} IdFactory
3
- * @property {() => number} next
4
- * @property {() => number} peek
5
- */
6
-
7
- /**
8
- * Monotonic id source, scoped to one document. Per-document rather than global
9
- * so tests are deterministic; ARCHITECTURE.md §4 only requires uniqueness within
10
- * a document.
11
- *
12
- * When loading a document from storage, seed `start` past the largest existing
13
- * id so new nodes never collide with restored ones.
14
- *
15
- * @param {number} [start]
16
- * @returns {IdFactory}
17
- */
18
- export function createIdFactory(start = 1) {
19
- let next = start;
20
- return {
21
- next() {
22
- return next++;
23
- },
24
- peek() {
25
- return next;
26
- },
27
- };
28
- }
29
-
30
- /**
31
- * Scans an already-constructed Doc for its highest NodeId — `doc.id`, every
32
- * container/paragraph id under `doc.content`, every decoration id, every
33
- * image id. Runs carry no NodeId (node.js's Run typedef has no `id` field) so
34
- * they are not visited.
35
- *
36
- * Mirrors the maxId-tracking pattern `document-json.js`'s JSON parser and
37
- * `import.js`'s DOCX importer each use to seed a factory past every id
38
- * already in use — but against a FINISHED Doc rather than while building one,
39
- * since neither existing tracker (one interleaved with raw JSON field
40
- * parsing, one a live mint-time counter) is callable against a Doc that
41
- * already exists. Used by `loadDocument` to reseed the shared IdFactory when
42
- * swapping documents (ADR-0027 Ek 49) — without this, a factory minted for
43
- * one document keeps handing out ids that may already be in use in the next.
44
- * @param {import("./node.js").Doc} doc
45
- * @returns {number}
46
- */
47
- export function maxNodeId(doc) {
48
- let max = doc.id;
49
- for (const container of doc.content) {
50
- if (container.id > max) {
51
- max = container.id;
52
- }
53
- for (const paragraph of container.content) {
54
- if (paragraph.id > max) {
55
- max = paragraph.id;
56
- }
57
- }
58
- }
59
- for (const decoration of doc.decorations) {
60
- if (decoration.id > max) {
61
- max = decoration.id;
62
- }
63
- }
64
- for (const image of doc.images) {
65
- if (image.id > max) {
66
- max = image.id;
67
- }
68
- }
69
- return max;
70
- }
1
+ /**
2
+ * @typedef {object} IdFactory
3
+ * @property {() => number} next
4
+ * @property {() => number} peek
5
+ */
6
+
7
+ /**
8
+ * Monotonic id source, scoped to one document. Per-document rather than global
9
+ * so tests are deterministic; ARCHITECTURE.md §4 only requires uniqueness within
10
+ * a document.
11
+ *
12
+ * When loading a document from storage, seed `start` past the largest existing
13
+ * id so new nodes never collide with restored ones.
14
+ *
15
+ * @param {number} [start]
16
+ * @returns {IdFactory}
17
+ */
18
+ export function createIdFactory(start = 1) {
19
+ let next = start;
20
+ return {
21
+ next() {
22
+ return next++;
23
+ },
24
+ peek() {
25
+ return next;
26
+ },
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Scans an already-constructed Doc for its highest NodeId — `doc.id`, every
32
+ * container/paragraph id under `doc.content`, every decoration id, every
33
+ * image id. Runs carry no NodeId (node.js's Run typedef has no `id` field) so
34
+ * they are not visited.
35
+ *
36
+ * Mirrors the maxId-tracking pattern `document-json.js`'s JSON parser and
37
+ * `import.js`'s DOCX importer each use to seed a factory past every id
38
+ * already in use — but against a FINISHED Doc rather than while building one,
39
+ * since neither existing tracker (one interleaved with raw JSON field
40
+ * parsing, one a live mint-time counter) is callable against a Doc that
41
+ * already exists. Used by `loadDocument` to reseed the shared IdFactory when
42
+ * swapping documents (ADR-0027 Ek 49) — without this, a factory minted for
43
+ * one document keeps handing out ids that may already be in use in the next.
44
+ * @param {import("./node.js").Doc} doc
45
+ * @returns {number}
46
+ */
47
+ export function maxNodeId(doc) {
48
+ let max = doc.id;
49
+ for (const container of doc.content) {
50
+ if (container.id > max) {
51
+ max = container.id;
52
+ }
53
+ for (const paragraph of container.content) {
54
+ if (paragraph.id > max) {
55
+ max = paragraph.id;
56
+ }
57
+ }
58
+ }
59
+ for (const decoration of doc.decorations) {
60
+ if (decoration.id > max) {
61
+ max = decoration.id;
62
+ }
63
+ }
64
+ for (const image of doc.images) {
65
+ if (image.id > max) {
66
+ max = image.id;
67
+ }
68
+ }
69
+ return max;
70
+ }
@@ -1,146 +1,146 @@
1
- import { devAssert } from "../dev.js";
2
- import { canvasOf, createContainer, createDoc, createRun, marksEqual, rebuildParagraph } from "./node.js";
3
-
4
- /**
5
- * Canonicalises a run list:
6
- * 1. drop empty runs
7
- * 2. merge adjacent runs that share a mark set
8
- * 3. a paragraph with no text keeps exactly one empty run, so the renderer has
9
- * somewhere to put the caret
10
- *
11
- * None of these rules move a character, so **no offset changes** — that is what
12
- * lets positions survive normalisation without being mapped (ADR-0002).
13
- *
14
- * @param {import("./node.js").Run[]} runs
15
- * @returns {import("./node.js").Run[]}
16
- */
17
- function normalizeRuns(runs) {
18
- /** @type {import("./node.js").Run[]} */
19
- const out = [];
20
-
21
- for (const run of runs) {
22
- if (run.text === "") {
23
- continue;
24
- }
25
- const last = out[out.length - 1];
26
- if (last !== undefined && marksEqual(last.marks, run.marks)) {
27
- out[out.length - 1] = createRun(last.text + run.text, last.marks);
28
- } else {
29
- out.push(run);
30
- }
31
- }
32
-
33
- if (out.length === 0) {
34
- // Keep the first original run rather than a fresh one so an empty paragraph
35
- // retains its marks — a paragraph split after bold text stays bold.
36
- out.push(runs.length > 0 ? runs[0] : createRun(""));
37
- }
38
-
39
- return out;
40
- }
41
-
42
- /**
43
- * Returns the same paragraph object when it is already normalised. Preserving
44
- * identity matters: the renderer skips paragraphs that compare `===`.
45
- * @param {import("./node.js").Paragraph} para
46
- * @returns {import("./node.js").Paragraph}
47
- */
48
- export function normalizeParagraph(para) {
49
- const runs = normalizeRuns(para.content);
50
-
51
- if (runs.length === para.content.length) {
52
- let identical = true;
53
- for (let i = 0; i < runs.length; i += 1) {
54
- if (runs[i] !== para.content[i]) {
55
- identical = false;
56
- break;
57
- }
58
- }
59
- if (identical) {
60
- return para;
61
- }
62
- }
63
-
64
- return rebuildParagraph(para, runs);
65
- }
66
-
67
- /**
68
- * Returns the same container object when every paragraph in it is already
69
- * normalised — the identity preservation that lets the renderer skip whole
70
- * boxes with `===`.
71
- * @param {import("./node.js").Container} container
72
- * @param {Set<import("./node.js").NodeId>} [dirtyIds]
73
- * @returns {import("./node.js").Container}
74
- */
75
- export function normalizeContainer(container, dirtyIds) {
76
- devAssert(
77
- container.content.length > 0,
78
- "container must always hold at least one paragraph",
79
- );
80
-
81
- /** @type {import("./node.js").Paragraph[] | null} */
82
- let paragraphs = null;
83
-
84
- for (let i = 0; i < container.content.length; i += 1) {
85
- const para = container.content[i];
86
- if (dirtyIds !== undefined && !dirtyIds.has(para.id)) {
87
- continue;
88
- }
89
- const next = normalizeParagraph(para);
90
- if (next !== para) {
91
- if (paragraphs === null) {
92
- paragraphs = container.content.slice();
93
- }
94
- paragraphs[i] = next;
95
- }
96
- }
97
-
98
- return paragraphs === null ? container : createContainer(container.id, paragraphs);
99
- }
100
-
101
- /**
102
- * Normalises the paragraphs a transaction touched, walking containers then
103
- * paragraphs. Pass `dirtyIds` to skip the rest — untouched nodes keep their
104
- * identity either way, but skipping saves the scan on large documents.
105
- *
106
- * Two emptiness rules, both **bugs rather than states to repair**: a document
107
- * with zero containers, and a container with zero paragraphs. No step in the set
108
- * can produce either (`DeleteStep` cannot remove a paragraph,
109
- * `MergeParagraphsStep` requires a following sibling *inside the same
110
- * container*, and `RemoveContainerStep` is the only way to drop a container at
111
- * all). Repairing them here would mean minting a node id inside normalisation,
112
- * which would break `invert(apply(step)) === identity`. So they are asserted.
113
- *
114
- * Note this permits a document to reach zero containers via `RemoveContainer` —
115
- * the assertion catches it, and it is the command layer's job not to remove the
116
- * last one.
117
- *
118
- * @param {import("./node.js").Doc} doc
119
- * @param {Set<import("./node.js").NodeId>} [dirtyIds]
120
- * @returns {import("./node.js").Doc}
121
- */
122
- export function normalizeDoc(doc, dirtyIds) {
123
- devAssert(doc.content.length > 0, "document must always hold at least one container");
124
-
125
- /** @type {import("./node.js").Container[] | null} */
126
- let content = null;
127
-
128
- for (let i = 0; i < doc.content.length; i += 1) {
129
- const container = doc.content[i];
130
- const next = normalizeContainer(container, dirtyIds);
131
- if (next !== container) {
132
- if (content === null) {
133
- content = doc.content.slice();
134
- }
135
- content[i] = next;
136
- }
137
- }
138
-
139
- if (content === null) {
140
- return doc;
141
- }
142
- // canvasOf, never a hand-listed set: a branch missing here would be silently
143
- // reset to its empty default by createDoc (that is exactly how Doc.images was
144
- // lost on every edit — see canvasOf's own note).
145
- return createDoc(doc.id, content, doc.layout, canvasOf(doc));
146
- }
1
+ import { devAssert } from "../dev.js";
2
+ import { canvasOf, createContainer, createDoc, createRun, marksEqual, rebuildParagraph } from "./node.js";
3
+
4
+ /**
5
+ * Canonicalises a run list:
6
+ * 1. drop empty runs
7
+ * 2. merge adjacent runs that share a mark set
8
+ * 3. a paragraph with no text keeps exactly one empty run, so the renderer has
9
+ * somewhere to put the caret
10
+ *
11
+ * None of these rules move a character, so **no offset changes** — that is what
12
+ * lets positions survive normalisation without being mapped (ADR-0002).
13
+ *
14
+ * @param {import("./node.js").Run[]} runs
15
+ * @returns {import("./node.js").Run[]}
16
+ */
17
+ function normalizeRuns(runs) {
18
+ /** @type {import("./node.js").Run[]} */
19
+ const out = [];
20
+
21
+ for (const run of runs) {
22
+ if (run.text === "") {
23
+ continue;
24
+ }
25
+ const last = out[out.length - 1];
26
+ if (last !== undefined && marksEqual(last.marks, run.marks)) {
27
+ out[out.length - 1] = createRun(last.text + run.text, last.marks);
28
+ } else {
29
+ out.push(run);
30
+ }
31
+ }
32
+
33
+ if (out.length === 0) {
34
+ // Keep the first original run rather than a fresh one so an empty paragraph
35
+ // retains its marks — a paragraph split after bold text stays bold.
36
+ out.push(runs.length > 0 ? runs[0] : createRun(""));
37
+ }
38
+
39
+ return out;
40
+ }
41
+
42
+ /**
43
+ * Returns the same paragraph object when it is already normalised. Preserving
44
+ * identity matters: the renderer skips paragraphs that compare `===`.
45
+ * @param {import("./node.js").Paragraph} para
46
+ * @returns {import("./node.js").Paragraph}
47
+ */
48
+ export function normalizeParagraph(para) {
49
+ const runs = normalizeRuns(para.content);
50
+
51
+ if (runs.length === para.content.length) {
52
+ let identical = true;
53
+ for (let i = 0; i < runs.length; i += 1) {
54
+ if (runs[i] !== para.content[i]) {
55
+ identical = false;
56
+ break;
57
+ }
58
+ }
59
+ if (identical) {
60
+ return para;
61
+ }
62
+ }
63
+
64
+ return rebuildParagraph(para, runs);
65
+ }
66
+
67
+ /**
68
+ * Returns the same container object when every paragraph in it is already
69
+ * normalised — the identity preservation that lets the renderer skip whole
70
+ * boxes with `===`.
71
+ * @param {import("./node.js").Container} container
72
+ * @param {Set<import("./node.js").NodeId>} [dirtyIds]
73
+ * @returns {import("./node.js").Container}
74
+ */
75
+ export function normalizeContainer(container, dirtyIds) {
76
+ devAssert(
77
+ container.content.length > 0,
78
+ "container must always hold at least one paragraph",
79
+ );
80
+
81
+ /** @type {import("./node.js").Paragraph[] | null} */
82
+ let paragraphs = null;
83
+
84
+ for (let i = 0; i < container.content.length; i += 1) {
85
+ const para = container.content[i];
86
+ if (dirtyIds !== undefined && !dirtyIds.has(para.id)) {
87
+ continue;
88
+ }
89
+ const next = normalizeParagraph(para);
90
+ if (next !== para) {
91
+ if (paragraphs === null) {
92
+ paragraphs = container.content.slice();
93
+ }
94
+ paragraphs[i] = next;
95
+ }
96
+ }
97
+
98
+ return paragraphs === null ? container : createContainer(container.id, paragraphs);
99
+ }
100
+
101
+ /**
102
+ * Normalises the paragraphs a transaction touched, walking containers then
103
+ * paragraphs. Pass `dirtyIds` to skip the rest — untouched nodes keep their
104
+ * identity either way, but skipping saves the scan on large documents.
105
+ *
106
+ * Two emptiness rules, both **bugs rather than states to repair**: a document
107
+ * with zero containers, and a container with zero paragraphs. No step in the set
108
+ * can produce either (`DeleteStep` cannot remove a paragraph,
109
+ * `MergeParagraphsStep` requires a following sibling *inside the same
110
+ * container*, and `RemoveContainerStep` is the only way to drop a container at
111
+ * all). Repairing them here would mean minting a node id inside normalisation,
112
+ * which would break `invert(apply(step)) === identity`. So they are asserted.
113
+ *
114
+ * Note this permits a document to reach zero containers via `RemoveContainer` —
115
+ * the assertion catches it, and it is the command layer's job not to remove the
116
+ * last one.
117
+ *
118
+ * @param {import("./node.js").Doc} doc
119
+ * @param {Set<import("./node.js").NodeId>} [dirtyIds]
120
+ * @returns {import("./node.js").Doc}
121
+ */
122
+ export function normalizeDoc(doc, dirtyIds) {
123
+ devAssert(doc.content.length > 0, "document must always hold at least one container");
124
+
125
+ /** @type {import("./node.js").Container[] | null} */
126
+ let content = null;
127
+
128
+ for (let i = 0; i < doc.content.length; i += 1) {
129
+ const container = doc.content[i];
130
+ const next = normalizeContainer(container, dirtyIds);
131
+ if (next !== container) {
132
+ if (content === null) {
133
+ content = doc.content.slice();
134
+ }
135
+ content[i] = next;
136
+ }
137
+ }
138
+
139
+ if (content === null) {
140
+ return doc;
141
+ }
142
+ // canvasOf, never a hand-listed set: a branch missing here would be silently
143
+ // reset to its empty default by createDoc (that is exactly how Doc.images was
144
+ // lost on every edit — see canvasOf's own note).
145
+ return createDoc(doc.id, content, doc.layout, canvasOf(doc));
146
+ }