@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,150 +1,150 @@
1
- import { DOC_DEFAULT_STYLE_REF, locateParagraph } from "../model/node.js";
2
- import { paragraphIdOf, selectionRange } from "../model/position.js";
3
- import { setAlignmentStep } from "../model/step.js";
4
- import { createTransaction } from "../model/transaction.js";
5
-
6
- // ADR-0029: paragraph alignment is editable through a DIRECT `Paragraph.jc`
7
- // override — never by mutating `Doc.styles`, which 70.5% of real corpus
8
- // paragraphs share with at least one other paragraph (worst case 471 on one
9
- // ref). This module is `commands/size.js`'s paragraph-level counterpart.
10
-
11
- /**
12
- * The OOXML `w:jc` tokens this editor writes. Deliberately NOT the full
13
- * `ST_Jc` enum — `distribute`, `thaiDistribute`, `mediumKashida` and the rest
14
- * exist in the schema and are READ correctly on import (they reach
15
- * `applyParagraphStyle` and render), but nothing in this UI produces them, so
16
- * nothing here claims to. `start`/`end` are likewise read-only: they are the
17
- * bidi-aware spellings of left/right, and writing them would silently change
18
- * meaning in an RTL document.
19
- * @type {readonly string[]}
20
- */
21
- export const ALIGNMENT_VALUES = /** @type {const} */ (["left", "center", "right", "both"]);
22
-
23
- /**
24
- * OOXML's own default when nothing in the chain declares one. ECMA-376's
25
- * `ST_Jc` has no "default" member — the default is the ABSENCE of `w:jc`, and
26
- * Word renders that as left-aligned for an LTR paragraph. Measured 2026-08-31:
27
- * all 46 real corpus documents declare an explicit `docDefaults` jc anyway
28
- * (every one of them "left"), so this last resort is only reachable for a
29
- * document that never went through `import.js` at all.
30
- */
31
- const FALLBACK_ALIGNMENT = "left";
32
-
33
- /**
34
- * The effective alignment at a paragraph: its own direct override, else the
35
- * import-resolved value for its `pPrRef`, else the document's own
36
- * `docDefaults` (ADR-0029 EKLEME A), else OOXML's absent-means-left.
37
- * @param {import("../model/node.js").Doc} doc
38
- * @param {import("../model/node.js").Paragraph} para
39
- * @returns {string}
40
- */
41
- export function paragraphAlignment(doc, para) {
42
- if (typeof para.jc === "string") {
43
- return para.jc;
44
- }
45
- if (para.pPrRef !== undefined) {
46
- const resolved = doc.styles[para.pPrRef]?.jc;
47
- if (typeof resolved === "string") {
48
- return resolved;
49
- }
50
- }
51
- const fromDefaults = doc.styles[DOC_DEFAULT_STYLE_REF]?.jc;
52
- return typeof fromDefaults === "string" ? fromDefaults : FALLBACK_ALIGNMENT;
53
- }
54
-
55
- /**
56
- * The effective alignment at a position — what a toolbar button should light
57
- * up for, `marksAtSelection`'s paragraph-level analogue.
58
- * @param {import("../model/node.js").Doc} doc
59
- * @param {import("../model/position.js").Position} pos
60
- * @returns {string | null} null when the position names no real paragraph
61
- */
62
- export function effectiveAlignment(doc, pos) {
63
- const at = locateParagraph(doc, paragraphIdOf(pos));
64
- return at === null ? null : paragraphAlignment(doc, at.para);
65
- }
66
-
67
- /**
68
- * The alignment shared by EVERY paragraph the selection touches, or null when
69
- * they disagree — the same all-or-nothing shape `marksAtSelection` uses, so a
70
- * lit button always means "every selected paragraph is already this".
71
- * @param {import("../state/editor-state.js").EditorState} state
72
- * @returns {string | null}
73
- */
74
- export function alignmentAtSelection(state) {
75
- const paragraphs = selectedParagraphs(state);
76
- if (paragraphs === null || paragraphs.length === 0) {
77
- return null;
78
- }
79
- const first = paragraphAlignment(state.doc, paragraphs[0].para);
80
- for (const entry of paragraphs) {
81
- if (paragraphAlignment(state.doc, entry.para) !== first) {
82
- return null; // mixed — no button lights up
83
- }
84
- }
85
- return first;
86
- }
87
-
88
- /**
89
- * Every paragraph the selection touches, in document order. A collapsed
90
- * caret selects the ONE paragraph it sits in — unlike a run-level mark, an
91
- * alignment change with no selection still has an unambiguous target, so
92
- * there is no `storedMarks`-style pending state to model here.
93
- * @param {import("../state/editor-state.js").EditorState} state
94
- * @returns {{container: import("../model/node.js").Container, para: import("../model/node.js").Paragraph, paraIndex: number}[] | null}
95
- */
96
- function selectedParagraphs(state) {
97
- const { doc, selection } = state;
98
- const range = selectionRange(doc, selection) ?? { from: selection.head, to: selection.head };
99
-
100
- const fromAt = locateParagraph(doc, paragraphIdOf(range.from));
101
- const toAt = locateParagraph(doc, paragraphIdOf(range.to));
102
- // A selection never spans containers (ADR-0007), same rule marks follow.
103
- if (fromAt === null || toAt === null || fromAt.containerIndex !== toAt.containerIndex) {
104
- return null;
105
- }
106
-
107
- const out = [];
108
- for (let i = fromAt.paraIndex; i <= toAt.paraIndex; i += 1) {
109
- out.push({ container: fromAt.container, para: fromAt.container.content[i], paraIndex: i });
110
- }
111
- return out;
112
- }
113
-
114
- /**
115
- * Sets the alignment of every paragraph the selection touches.
116
- *
117
- * Unlike `toggleMark`, this is NOT a toggle: the four alignments are mutually
118
- * exclusive, so pressing "center" on already-centered text is a no-op rather
119
- * than a revert-to-nothing. Returns null when nothing would change, so a
120
- * redundant press never reaches the history stack.
121
- *
122
- * @param {import("../state/editor-state.js").EditorState} state
123
- * @param {string} value one of `ALIGNMENT_VALUES`
124
- * @returns {import("../model/transaction.js").Transaction | null}
125
- */
126
- export function setAlignment(state, value) {
127
- const { doc, selection } = state;
128
- const paragraphs = selectedParagraphs(state);
129
- if (paragraphs === null || paragraphs.length === 0) {
130
- return null;
131
- }
132
-
133
- const entries = paragraphs
134
- .filter((entry) => entry.para.jc !== value)
135
- .map((entry) => ({ containerId: entry.container.id, paragraphId: entry.para.id, value }));
136
- if (entries.length === 0) {
137
- return null;
138
- }
139
-
140
- const tr = createTransaction(doc, selection, "format");
141
- tr.step(setAlignmentStep(entries));
142
- if (!tr.changesDoc) {
143
- return null;
144
- }
145
-
146
- // Alignment never moves characters, so the selection is preserved as-is.
147
- tr.setSelection(selection);
148
- tr.setStoredMarks(null);
149
- return tr.build();
150
- }
1
+ import { DOC_DEFAULT_STYLE_REF, locateParagraph } from "../model/node.js";
2
+ import { paragraphIdOf, selectionRange } from "../model/position.js";
3
+ import { setAlignmentStep } from "../model/step.js";
4
+ import { createTransaction } from "../model/transaction.js";
5
+
6
+ // ADR-0029: paragraph alignment is editable through a DIRECT `Paragraph.jc`
7
+ // override — never by mutating `Doc.styles`, which 70.5% of real corpus
8
+ // paragraphs share with at least one other paragraph (worst case 471 on one
9
+ // ref). This module is `commands/size.js`'s paragraph-level counterpart.
10
+
11
+ /**
12
+ * The OOXML `w:jc` tokens this editor writes. Deliberately NOT the full
13
+ * `ST_Jc` enum — `distribute`, `thaiDistribute`, `mediumKashida` and the rest
14
+ * exist in the schema and are READ correctly on import (they reach
15
+ * `applyParagraphStyle` and render), but nothing in this UI produces them, so
16
+ * nothing here claims to. `start`/`end` are likewise read-only: they are the
17
+ * bidi-aware spellings of left/right, and writing them would silently change
18
+ * meaning in an RTL document.
19
+ * @type {readonly string[]}
20
+ */
21
+ export const ALIGNMENT_VALUES = /** @type {const} */ (["left", "center", "right", "both"]);
22
+
23
+ /**
24
+ * OOXML's own default when nothing in the chain declares one. ECMA-376's
25
+ * `ST_Jc` has no "default" member — the default is the ABSENCE of `w:jc`, and
26
+ * Word renders that as left-aligned for an LTR paragraph. Measured 2026-08-31:
27
+ * all 46 real corpus documents declare an explicit `docDefaults` jc anyway
28
+ * (every one of them "left"), so this last resort is only reachable for a
29
+ * document that never went through `import.js` at all.
30
+ */
31
+ const FALLBACK_ALIGNMENT = "left";
32
+
33
+ /**
34
+ * The effective alignment at a paragraph: its own direct override, else the
35
+ * import-resolved value for its `pPrRef`, else the document's own
36
+ * `docDefaults` (ADR-0029 EKLEME A), else OOXML's absent-means-left.
37
+ * @param {import("../model/node.js").Doc} doc
38
+ * @param {import("../model/node.js").Paragraph} para
39
+ * @returns {string}
40
+ */
41
+ export function paragraphAlignment(doc, para) {
42
+ if (typeof para.jc === "string") {
43
+ return para.jc;
44
+ }
45
+ if (para.pPrRef !== undefined) {
46
+ const resolved = doc.styles[para.pPrRef]?.jc;
47
+ if (typeof resolved === "string") {
48
+ return resolved;
49
+ }
50
+ }
51
+ const fromDefaults = doc.styles[DOC_DEFAULT_STYLE_REF]?.jc;
52
+ return typeof fromDefaults === "string" ? fromDefaults : FALLBACK_ALIGNMENT;
53
+ }
54
+
55
+ /**
56
+ * The effective alignment at a position — what a toolbar button should light
57
+ * up for, `marksAtSelection`'s paragraph-level analogue.
58
+ * @param {import("../model/node.js").Doc} doc
59
+ * @param {import("../model/position.js").Position} pos
60
+ * @returns {string | null} null when the position names no real paragraph
61
+ */
62
+ export function effectiveAlignment(doc, pos) {
63
+ const at = locateParagraph(doc, paragraphIdOf(pos));
64
+ return at === null ? null : paragraphAlignment(doc, at.para);
65
+ }
66
+
67
+ /**
68
+ * The alignment shared by EVERY paragraph the selection touches, or null when
69
+ * they disagree — the same all-or-nothing shape `marksAtSelection` uses, so a
70
+ * lit button always means "every selected paragraph is already this".
71
+ * @param {import("../state/editor-state.js").EditorState} state
72
+ * @returns {string | null}
73
+ */
74
+ export function alignmentAtSelection(state) {
75
+ const paragraphs = selectedParagraphs(state);
76
+ if (paragraphs === null || paragraphs.length === 0) {
77
+ return null;
78
+ }
79
+ const first = paragraphAlignment(state.doc, paragraphs[0].para);
80
+ for (const entry of paragraphs) {
81
+ if (paragraphAlignment(state.doc, entry.para) !== first) {
82
+ return null; // mixed — no button lights up
83
+ }
84
+ }
85
+ return first;
86
+ }
87
+
88
+ /**
89
+ * Every paragraph the selection touches, in document order. A collapsed
90
+ * caret selects the ONE paragraph it sits in — unlike a run-level mark, an
91
+ * alignment change with no selection still has an unambiguous target, so
92
+ * there is no `storedMarks`-style pending state to model here.
93
+ * @param {import("../state/editor-state.js").EditorState} state
94
+ * @returns {{container: import("../model/node.js").Container, para: import("../model/node.js").Paragraph, paraIndex: number}[] | null}
95
+ */
96
+ function selectedParagraphs(state) {
97
+ const { doc, selection } = state;
98
+ const range = selectionRange(doc, selection) ?? { from: selection.head, to: selection.head };
99
+
100
+ const fromAt = locateParagraph(doc, paragraphIdOf(range.from));
101
+ const toAt = locateParagraph(doc, paragraphIdOf(range.to));
102
+ // A selection never spans containers (ADR-0007), same rule marks follow.
103
+ if (fromAt === null || toAt === null || fromAt.containerIndex !== toAt.containerIndex) {
104
+ return null;
105
+ }
106
+
107
+ const out = [];
108
+ for (let i = fromAt.paraIndex; i <= toAt.paraIndex; i += 1) {
109
+ out.push({ container: fromAt.container, para: fromAt.container.content[i], paraIndex: i });
110
+ }
111
+ return out;
112
+ }
113
+
114
+ /**
115
+ * Sets the alignment of every paragraph the selection touches.
116
+ *
117
+ * Unlike `toggleMark`, this is NOT a toggle: the four alignments are mutually
118
+ * exclusive, so pressing "center" on already-centered text is a no-op rather
119
+ * than a revert-to-nothing. Returns null when nothing would change, so a
120
+ * redundant press never reaches the history stack.
121
+ *
122
+ * @param {import("../state/editor-state.js").EditorState} state
123
+ * @param {string} value one of `ALIGNMENT_VALUES`
124
+ * @returns {import("../model/transaction.js").Transaction | null}
125
+ */
126
+ export function setAlignment(state, value) {
127
+ const { doc, selection } = state;
128
+ const paragraphs = selectedParagraphs(state);
129
+ if (paragraphs === null || paragraphs.length === 0) {
130
+ return null;
131
+ }
132
+
133
+ const entries = paragraphs
134
+ .filter((entry) => entry.para.jc !== value)
135
+ .map((entry) => ({ containerId: entry.container.id, paragraphId: entry.para.id, value }));
136
+ if (entries.length === 0) {
137
+ return null;
138
+ }
139
+
140
+ const tr = createTransaction(doc, selection, "format");
141
+ tr.step(setAlignmentStep(entries));
142
+ if (!tr.changesDoc) {
143
+ return null;
144
+ }
145
+
146
+ // Alignment never moves characters, so the selection is preserved as-is.
147
+ tr.setSelection(selection);
148
+ tr.setStoredMarks(null);
149
+ return tr.build();
150
+ }