@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.
- package/CHANGELOG.md +108 -70
- package/LICENSE +21 -21
- package/README.md +286 -247
- package/dist/canvas/box-clipboard.js +238 -238
- package/dist/canvas/box-selection.js +80 -80
- package/dist/canvas/canvas-editor.js +502 -502
- package/dist/canvas/controller.js +717 -717
- package/dist/canvas/geometry.js +131 -131
- package/dist/canvas/gesture.d.ts +0 -1
- package/dist/canvas/gesture.js +289 -289
- package/dist/canvas/handles.js +212 -212
- package/dist/canvas/menu.js +82 -82
- package/dist/canvas/mode-controller.js +81 -81
- package/dist/canvas/nudge.js +108 -108
- package/dist/canvas/overlay.js +149 -149
- package/dist/canvas/snap.js +132 -132
- package/dist/canvas/viewport.js +112 -112
- package/dist/canvas/z-order.js +171 -171
- package/dist/commands/alignment.js +150 -150
- package/dist/commands/box.js +272 -272
- package/dist/commands/clipboard.js +93 -93
- package/dist/commands/graphemes.js +112 -112
- package/dist/commands/marks.js +153 -153
- package/dist/commands/paragraph-props.js +176 -176
- package/dist/commands/size.js +368 -368
- package/dist/commands/text.js +291 -291
- package/dist/docx.d.ts +1 -0
- package/dist/docx.js +15 -13
- package/dist/editor/editing-host.js +434 -434
- package/dist/element.js +41 -41
- package/dist/fonts/OFL-Carlito.txt +94 -0
- package/dist/fonts/calibri-metrics.d.ts +27 -0
- package/dist/fonts/calibri-metrics.js +88 -0
- package/dist/history/history.js +433 -433
- package/dist/index.js +63 -63
- package/dist/input/beforeinput.js +224 -224
- package/dist/input/clipboard.js +107 -107
- package/dist/input/composition.js +341 -341
- package/dist/input/keymap.js +54 -54
- package/dist/io/ooxml/custgeom.js +72 -72
- package/dist/io/ooxml/drawingml.js +232 -232
- package/dist/io/ooxml/edits.js +67 -67
- package/dist/io/ooxml/export-splice.js +880 -880
- package/dist/io/ooxml/export.js +365 -365
- package/dist/io/ooxml/fill.d.ts +93 -8
- package/dist/io/ooxml/fill.js +618 -562
- package/dist/io/ooxml/fit.d.ts +252 -0
- package/dist/io/ooxml/fit.js +598 -0
- package/dist/io/ooxml/import-gate.js +200 -200
- package/dist/io/ooxml/import.js +691 -691
- package/dist/io/ooxml/inflate.js +126 -126
- package/dist/io/ooxml/media.js +87 -87
- package/dist/io/ooxml/ns.d.ts +2 -0
- package/dist/io/ooxml/ns.js +26 -21
- package/dist/io/ooxml/numbering.js +290 -290
- package/dist/io/ooxml/opc-write.js +171 -171
- package/dist/io/ooxml/provenance.js +93 -93
- package/dist/io/ooxml/recipe.js +708 -708
- package/dist/io/ooxml/relationships.js +59 -59
- package/dist/io/ooxml/run-fonts.d.ts +89 -0
- package/dist/io/ooxml/run-fonts.js +378 -0
- package/dist/io/ooxml/slots.d.ts +4 -0
- package/dist/io/ooxml/slots.js +582 -581
- package/dist/io/ooxml/styles-resolve.js +257 -257
- package/dist/io/ooxml/units.js +43 -43
- package/dist/io/ooxml/value.js +120 -120
- package/dist/io/ooxml/xml-scan.js +616 -616
- package/dist/io/ooxml/zip-container.js +228 -228
- package/dist/io/ooxml/zip-reject.js +106 -106
- package/dist/measure/placement.js +83 -83
- package/dist/measure/report.js +75 -75
- package/dist/model/ids.js +70 -70
- package/dist/model/normalize.js +146 -146
- package/dist/model/position.js +283 -283
- package/dist/model/transaction.js +209 -209
- package/dist/platform/capabilities.js +25 -25
- package/dist/react/index.js +21 -21
- package/dist/selection/controller.js +315 -315
- package/dist/selection/dom-map.js +334 -334
- package/dist/state/editor-state.js +109 -109
- package/dist/styles.css +66 -66
- package/package.json +94 -93
package/dist/commands/box.js
CHANGED
|
@@ -1,272 +1,272 @@
|
|
|
1
|
-
import { containerIndexById, layoutOf } from "../model/node.js";
|
|
2
|
-
import { insertContainerStep, removeContainerStep, setLayoutStep } from "../model/step.js";
|
|
3
|
-
import { createTransaction } from "../model/transaction.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* The five user-visible box operations, as commands over the three container
|
|
7
|
-
* steps (ADR-0007) — move/resize/setZ all become `setLayoutStep`, since intent
|
|
8
|
-
* lives in `Transaction.origin`, not in a wider step vocabulary.
|
|
9
|
-
*
|
|
10
|
-
* None of these touch text selection: a box operation and a caret position are
|
|
11
|
-
* orthogonal in this model, so every command here carries `state.selection`
|
|
12
|
-
* through unchanged rather than requiring a `BoxSelection` type to exist yet.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Adds a new container to the document.
|
|
17
|
-
* @param {import("../state/editor-state.js").EditorState} state
|
|
18
|
-
* @param {import("../model/node.js").Container} container
|
|
19
|
-
* @param {import("../model/node.js").BoxLayout | null} [layout]
|
|
20
|
-
* @param {number} [index] defaults to the end of the document
|
|
21
|
-
* @returns {import("../model/transaction.js").Transaction | null}
|
|
22
|
-
*/
|
|
23
|
-
export function createBox(state, container, layout = null, index) {
|
|
24
|
-
const { doc, selection } = state;
|
|
25
|
-
const at = index ?? doc.content.length;
|
|
26
|
-
|
|
27
|
-
const tr = createTransaction(doc, selection, "format");
|
|
28
|
-
tr.step(insertContainerStep(at, container, layout));
|
|
29
|
-
if (!tr.changesDoc) {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
return tr.build();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Removes a container and its geometry together.
|
|
37
|
-
* @param {import("../state/editor-state.js").EditorState} state
|
|
38
|
-
* @param {import("../model/node.js").NodeId} containerId
|
|
39
|
-
* @returns {import("../model/transaction.js").Transaction | null}
|
|
40
|
-
*/
|
|
41
|
-
export function deleteBox(state, containerId) {
|
|
42
|
-
const { doc, selection } = state;
|
|
43
|
-
if (containerIndexById(doc, containerId) === -1) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
// `normalizeDoc` asserts a document holds at least one container and states
|
|
47
|
-
// explicitly that keeping it that way is the command layer's job — this is
|
|
48
|
-
// that job. Without the guard the assertion fires in dev and, worse, in
|
|
49
|
-
// production (where `devAssert` is a no-op) a zero-container document would
|
|
50
|
-
// reach the renderer.
|
|
51
|
-
if (doc.content.length === 1) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const tr = createTransaction(doc, selection, "format");
|
|
56
|
-
tr.step(removeContainerStep(containerId));
|
|
57
|
-
if (!tr.changesDoc) {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
return tr.build();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Whether any key in `patch` differs from the container's current layout.
|
|
65
|
-
*
|
|
66
|
-
* Needed because `patchLayout` (model/node.js) always builds a fresh layout
|
|
67
|
-
* object whether or not values changed, so `TransactionBuilder.changesDoc`
|
|
68
|
-
* cannot tell a real move from a stationary one (mouse down, mouse up, no
|
|
69
|
-
* movement) — that would write a spurious history entry.
|
|
70
|
-
*
|
|
71
|
-
* @param {import("../model/node.js").BoxLayout} current
|
|
72
|
-
* @param {Partial<import("../model/node.js").BoxLayout>} patch
|
|
73
|
-
* @returns {boolean}
|
|
74
|
-
*/
|
|
75
|
-
function changesAnything(current, patch) {
|
|
76
|
-
return /** @type {import("../model/node.js").LayoutKey[]} */ (Object.keys(patch)).some(
|
|
77
|
-
(key) => current[key] !== patch[key],
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* @param {import("../state/editor-state.js").EditorState} state
|
|
83
|
-
* @param {import("../model/node.js").NodeId} containerId
|
|
84
|
-
* @param {Partial<import("../model/node.js").BoxLayout>} patch
|
|
85
|
-
* @param {import("../model/transaction.js").TransactionOrigin} origin
|
|
86
|
-
* @returns {import("../model/transaction.js").Transaction | null}
|
|
87
|
-
*/
|
|
88
|
-
function setLayout(state, containerId, patch, origin) {
|
|
89
|
-
const { doc, selection } = state;
|
|
90
|
-
const current = layoutOf(doc, containerId);
|
|
91
|
-
if (current === null) {
|
|
92
|
-
return null; // nothing to patch — this container is content, not a box
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (!changesAnything(current, patch)) {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// The patch keeps the operation's FULL key set — `{x,y}` for a move even when
|
|
100
|
-
// only x moved this frame. Narrowing it to just the differing keys looks
|
|
101
|
-
// tidier and is a real bug: `HistoryEngine` collapses a drag only while the
|
|
102
|
-
// key set stays identical, so one perfectly horizontal pointer move (y
|
|
103
|
-
// unchanged) would emit `{x}`, break the collapse, and split the drag into two
|
|
104
|
-
// undo entries mid-gesture. Dragging in a straight line is not an edge case.
|
|
105
|
-
//
|
|
106
|
-
// Emitting an unchanged key is harmless in both directions: applying it is
|
|
107
|
-
// idempotent, and `invertSetLayout` restores that key from `docBefore`, where
|
|
108
|
-
// it already held this value.
|
|
109
|
-
const tr = createTransaction(doc, selection, origin);
|
|
110
|
-
tr.step(setLayoutStep(containerId, patch));
|
|
111
|
-
return tr.build();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Moves a box. `origin: "pointer"` — a drag calls this once per pointer move,
|
|
116
|
-
* and `HistoryEngine` collapses a run of same-container, same-key-set
|
|
117
|
-
* `SetLayoutStep`s from this origin into a single undo entry (ADR-0007).
|
|
118
|
-
* @param {import("../state/editor-state.js").EditorState} state
|
|
119
|
-
* @param {import("../model/node.js").NodeId} containerId
|
|
120
|
-
* @param {number} x
|
|
121
|
-
* @param {number} y
|
|
122
|
-
* @returns {import("../model/transaction.js").Transaction | null}
|
|
123
|
-
*/
|
|
124
|
-
export function moveBox(state, containerId, x, y) {
|
|
125
|
-
return setLayout(state, containerId, { x, y }, "pointer");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Resizes a box. Same collapsing behaviour as `moveBox`, keyed on `{cx, cy}`
|
|
130
|
-
* instead of `{x, y}` — a resize drag never collapses into a move drag or
|
|
131
|
-
* vice versa, because the key sets differ.
|
|
132
|
-
* @param {import("../state/editor-state.js").EditorState} state
|
|
133
|
-
* @param {import("../model/node.js").NodeId} containerId
|
|
134
|
-
* @param {number} cx
|
|
135
|
-
* @param {number} cy
|
|
136
|
-
* @returns {import("../model/transaction.js").Transaction | null}
|
|
137
|
-
*/
|
|
138
|
-
export function resizeBox(state, containerId, cx, cy) {
|
|
139
|
-
return setLayout(state, containerId, { cx, cy }, "pointer");
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Moves a box to another page. Shares `moveBox`'s coalescing key set only
|
|
144
|
-
* incidentally if a drag ever patches `page` alongside `x`/`y` in the same
|
|
145
|
-
* step — ordinary page reassignment is a discrete action, so `origin:
|
|
146
|
-
* "format"` (never coalesced), matching `setBoxZ` below.
|
|
147
|
-
* @param {import("../state/editor-state.js").EditorState} state
|
|
148
|
-
* @param {import("../model/node.js").NodeId} containerId
|
|
149
|
-
* @param {number} page
|
|
150
|
-
* @returns {import("../model/transaction.js").Transaction | null}
|
|
151
|
-
*/
|
|
152
|
-
export function setBoxPage(state, containerId, page) {
|
|
153
|
-
return setLayout(state, containerId, { page }, "format");
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Reorders a box's stacking. A discrete action (a menu command or keyboard
|
|
158
|
-
* shortcut, never a drag), so `origin: "format"` — never coalesced.
|
|
159
|
-
* @param {import("../state/editor-state.js").EditorState} state
|
|
160
|
-
* @param {import("../model/node.js").NodeId} containerId
|
|
161
|
-
* @param {number} z
|
|
162
|
-
* @returns {import("../model/transaction.js").Transaction | null}
|
|
163
|
-
*/
|
|
164
|
-
export function setBoxZ(state, containerId, z) {
|
|
165
|
-
return setLayout(state, containerId, { z }, "format");
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* @typedef {object} BoxTransformEntry
|
|
170
|
-
* @property {import("../model/node.js").NodeId} containerId
|
|
171
|
-
* @property {Partial<import("../model/node.js").BoxLayout>} patch
|
|
172
|
-
*/
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* The shared engine behind `transformBoxes`/`setBoxZOrder`: every entry in
|
|
176
|
-
* one call becomes its OWN `setLayoutStep`, all in ONE transaction —
|
|
177
|
-
* deliberately a NEW function rather than a change to `setLayout` above,
|
|
178
|
-
* which `moveBox`/`resizeBox` must stay byte-identical to (HIST1/HIST5/
|
|
179
|
-
* HIST6's `find` strings, and their monotonic `expectedMinBreaks` 9/8/7,
|
|
180
|
-
* are measured against those two functions' current bodies).
|
|
181
|
-
*
|
|
182
|
-
* Unlike `setLayout` (one box, one step, skipped entirely when its patch
|
|
183
|
-
* is a no-op), an entry here is never dropped individually just because
|
|
184
|
-
* ITS OWN patch didn't change anything this frame — ADR-0018 Karar 2's
|
|
185
|
-
* "transformBoxes bir karedeki HER girdiyi yazar" rule. A multi-box drag's
|
|
186
|
-
* frame shape (step count, and each step's key set) has to stay identical
|
|
187
|
-
* from frame to frame for `HistoryEngine.canCoalesce`'s `sameKeySet` check
|
|
188
|
-
* to keep collapsing the gesture into one undo entry; dropping one entry
|
|
189
|
-
* because it happened not to move this frame would shrink the step count
|
|
190
|
-
* and split the drag mid-gesture — the exact bug `setLayout`'s own comment
|
|
191
|
-
* already documents for a single box's unchanged key, generalized to N.
|
|
192
|
-
*
|
|
193
|
-
* The WHOLE call is a no-op (returns null) when nothing in the batch
|
|
194
|
-
* changed, or when any entry names something that is not a box — writing
|
|
195
|
-
* N-1 of N valid entries would silently narrow the gesture's own roster.
|
|
196
|
-
*
|
|
197
|
-
* @param {import("../state/editor-state.js").EditorState} state
|
|
198
|
-
* @param {BoxTransformEntry[]} entries
|
|
199
|
-
* @param {import("../model/transaction.js").TransactionOrigin} origin
|
|
200
|
-
* @returns {import("../model/transaction.js").Transaction | null}
|
|
201
|
-
*/
|
|
202
|
-
function transformLayouts(state, entries, origin) {
|
|
203
|
-
const { doc, selection } = state;
|
|
204
|
-
|
|
205
|
-
const current = entries.map((entry) => layoutOf(doc, entry.containerId));
|
|
206
|
-
if (current.some((layout) => layout === null)) {
|
|
207
|
-
return null;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const anyChanged = entries.some((entry, i) =>
|
|
211
|
-
changesAnything(/** @type {import("../model/node.js").BoxLayout} */ (current[i]), entry.patch),
|
|
212
|
-
);
|
|
213
|
-
if (!anyChanged) {
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const tr = createTransaction(doc, selection, origin);
|
|
218
|
-
for (const entry of entries) {
|
|
219
|
-
tr.step(setLayoutStep(entry.containerId, entry.patch));
|
|
220
|
-
}
|
|
221
|
-
return tr.build();
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Moves/resizes N boxes in ONE transaction — the command a corner-handle
|
|
226
|
-
* drag needs and `moveBox`/`resizeBox` cannot give: those write exactly
|
|
227
|
-
* `{x,y}`/`{cx,cy}`, never a corner's full `{x,y,cx,cy}` in one step
|
|
228
|
-
* (ADR-0018 Karar 2). `origin: "pointer"` — coalesces the same way a
|
|
229
|
-
* single-box drag does, keyed on each step's own patch (see
|
|
230
|
-
* `src/canvas/handles.js`'s `resizeKeys` for the fixed key set a given
|
|
231
|
-
* handle's drag must keep writing for the whole gesture).
|
|
232
|
-
* @param {import("../state/editor-state.js").EditorState} state
|
|
233
|
-
* @param {BoxTransformEntry[]} entries
|
|
234
|
-
* @returns {import("../model/transaction.js").Transaction | null}
|
|
235
|
-
*/
|
|
236
|
-
export function transformBoxes(state, entries) {
|
|
237
|
-
return transformLayouts(state, entries, "pointer");
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* `transformBoxes` for exactly one box — the corner-handle case itself.
|
|
242
|
-
* @param {import("../state/editor-state.js").EditorState} state
|
|
243
|
-
* @param {import("../model/node.js").NodeId} containerId
|
|
244
|
-
* @param {Partial<import("../model/node.js").BoxLayout>} patch
|
|
245
|
-
* @returns {import("../model/transaction.js").Transaction | null}
|
|
246
|
-
*/
|
|
247
|
-
export function transformBox(state, containerId, patch) {
|
|
248
|
-
return transformBoxes(state, [{ containerId, patch }]);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* @typedef {object} BoxZOrderEntry
|
|
253
|
-
* @property {import("../model/node.js").NodeId} containerId
|
|
254
|
-
* @property {number} z
|
|
255
|
-
*/
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Reorders N boxes' stacking in ONE transaction (a "bring to front" over a
|
|
259
|
-
* multi-selection touches more than one box's z at once). `origin:
|
|
260
|
-
* "format"` — a menu/keyboard action, never coalesced, matching the
|
|
261
|
-
* existing single-box `setBoxZ`.
|
|
262
|
-
* @param {import("../state/editor-state.js").EditorState} state
|
|
263
|
-
* @param {BoxZOrderEntry[]} entries
|
|
264
|
-
* @returns {import("../model/transaction.js").Transaction | null}
|
|
265
|
-
*/
|
|
266
|
-
export function setBoxZOrder(state, entries) {
|
|
267
|
-
return transformLayouts(
|
|
268
|
-
state,
|
|
269
|
-
entries.map((entry) => ({ containerId: entry.containerId, patch: { z: entry.z } })),
|
|
270
|
-
"format",
|
|
271
|
-
);
|
|
272
|
-
}
|
|
1
|
+
import { containerIndexById, layoutOf } from "../model/node.js";
|
|
2
|
+
import { insertContainerStep, removeContainerStep, setLayoutStep } from "../model/step.js";
|
|
3
|
+
import { createTransaction } from "../model/transaction.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The five user-visible box operations, as commands over the three container
|
|
7
|
+
* steps (ADR-0007) — move/resize/setZ all become `setLayoutStep`, since intent
|
|
8
|
+
* lives in `Transaction.origin`, not in a wider step vocabulary.
|
|
9
|
+
*
|
|
10
|
+
* None of these touch text selection: a box operation and a caret position are
|
|
11
|
+
* orthogonal in this model, so every command here carries `state.selection`
|
|
12
|
+
* through unchanged rather than requiring a `BoxSelection` type to exist yet.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Adds a new container to the document.
|
|
17
|
+
* @param {import("../state/editor-state.js").EditorState} state
|
|
18
|
+
* @param {import("../model/node.js").Container} container
|
|
19
|
+
* @param {import("../model/node.js").BoxLayout | null} [layout]
|
|
20
|
+
* @param {number} [index] defaults to the end of the document
|
|
21
|
+
* @returns {import("../model/transaction.js").Transaction | null}
|
|
22
|
+
*/
|
|
23
|
+
export function createBox(state, container, layout = null, index) {
|
|
24
|
+
const { doc, selection } = state;
|
|
25
|
+
const at = index ?? doc.content.length;
|
|
26
|
+
|
|
27
|
+
const tr = createTransaction(doc, selection, "format");
|
|
28
|
+
tr.step(insertContainerStep(at, container, layout));
|
|
29
|
+
if (!tr.changesDoc) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return tr.build();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Removes a container and its geometry together.
|
|
37
|
+
* @param {import("../state/editor-state.js").EditorState} state
|
|
38
|
+
* @param {import("../model/node.js").NodeId} containerId
|
|
39
|
+
* @returns {import("../model/transaction.js").Transaction | null}
|
|
40
|
+
*/
|
|
41
|
+
export function deleteBox(state, containerId) {
|
|
42
|
+
const { doc, selection } = state;
|
|
43
|
+
if (containerIndexById(doc, containerId) === -1) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
// `normalizeDoc` asserts a document holds at least one container and states
|
|
47
|
+
// explicitly that keeping it that way is the command layer's job — this is
|
|
48
|
+
// that job. Without the guard the assertion fires in dev and, worse, in
|
|
49
|
+
// production (where `devAssert` is a no-op) a zero-container document would
|
|
50
|
+
// reach the renderer.
|
|
51
|
+
if (doc.content.length === 1) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const tr = createTransaction(doc, selection, "format");
|
|
56
|
+
tr.step(removeContainerStep(containerId));
|
|
57
|
+
if (!tr.changesDoc) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return tr.build();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Whether any key in `patch` differs from the container's current layout.
|
|
65
|
+
*
|
|
66
|
+
* Needed because `patchLayout` (model/node.js) always builds a fresh layout
|
|
67
|
+
* object whether or not values changed, so `TransactionBuilder.changesDoc`
|
|
68
|
+
* cannot tell a real move from a stationary one (mouse down, mouse up, no
|
|
69
|
+
* movement) — that would write a spurious history entry.
|
|
70
|
+
*
|
|
71
|
+
* @param {import("../model/node.js").BoxLayout} current
|
|
72
|
+
* @param {Partial<import("../model/node.js").BoxLayout>} patch
|
|
73
|
+
* @returns {boolean}
|
|
74
|
+
*/
|
|
75
|
+
function changesAnything(current, patch) {
|
|
76
|
+
return /** @type {import("../model/node.js").LayoutKey[]} */ (Object.keys(patch)).some(
|
|
77
|
+
(key) => current[key] !== patch[key],
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {import("../state/editor-state.js").EditorState} state
|
|
83
|
+
* @param {import("../model/node.js").NodeId} containerId
|
|
84
|
+
* @param {Partial<import("../model/node.js").BoxLayout>} patch
|
|
85
|
+
* @param {import("../model/transaction.js").TransactionOrigin} origin
|
|
86
|
+
* @returns {import("../model/transaction.js").Transaction | null}
|
|
87
|
+
*/
|
|
88
|
+
function setLayout(state, containerId, patch, origin) {
|
|
89
|
+
const { doc, selection } = state;
|
|
90
|
+
const current = layoutOf(doc, containerId);
|
|
91
|
+
if (current === null) {
|
|
92
|
+
return null; // nothing to patch — this container is content, not a box
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!changesAnything(current, patch)) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// The patch keeps the operation's FULL key set — `{x,y}` for a move even when
|
|
100
|
+
// only x moved this frame. Narrowing it to just the differing keys looks
|
|
101
|
+
// tidier and is a real bug: `HistoryEngine` collapses a drag only while the
|
|
102
|
+
// key set stays identical, so one perfectly horizontal pointer move (y
|
|
103
|
+
// unchanged) would emit `{x}`, break the collapse, and split the drag into two
|
|
104
|
+
// undo entries mid-gesture. Dragging in a straight line is not an edge case.
|
|
105
|
+
//
|
|
106
|
+
// Emitting an unchanged key is harmless in both directions: applying it is
|
|
107
|
+
// idempotent, and `invertSetLayout` restores that key from `docBefore`, where
|
|
108
|
+
// it already held this value.
|
|
109
|
+
const tr = createTransaction(doc, selection, origin);
|
|
110
|
+
tr.step(setLayoutStep(containerId, patch));
|
|
111
|
+
return tr.build();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Moves a box. `origin: "pointer"` — a drag calls this once per pointer move,
|
|
116
|
+
* and `HistoryEngine` collapses a run of same-container, same-key-set
|
|
117
|
+
* `SetLayoutStep`s from this origin into a single undo entry (ADR-0007).
|
|
118
|
+
* @param {import("../state/editor-state.js").EditorState} state
|
|
119
|
+
* @param {import("../model/node.js").NodeId} containerId
|
|
120
|
+
* @param {number} x
|
|
121
|
+
* @param {number} y
|
|
122
|
+
* @returns {import("../model/transaction.js").Transaction | null}
|
|
123
|
+
*/
|
|
124
|
+
export function moveBox(state, containerId, x, y) {
|
|
125
|
+
return setLayout(state, containerId, { x, y }, "pointer");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Resizes a box. Same collapsing behaviour as `moveBox`, keyed on `{cx, cy}`
|
|
130
|
+
* instead of `{x, y}` — a resize drag never collapses into a move drag or
|
|
131
|
+
* vice versa, because the key sets differ.
|
|
132
|
+
* @param {import("../state/editor-state.js").EditorState} state
|
|
133
|
+
* @param {import("../model/node.js").NodeId} containerId
|
|
134
|
+
* @param {number} cx
|
|
135
|
+
* @param {number} cy
|
|
136
|
+
* @returns {import("../model/transaction.js").Transaction | null}
|
|
137
|
+
*/
|
|
138
|
+
export function resizeBox(state, containerId, cx, cy) {
|
|
139
|
+
return setLayout(state, containerId, { cx, cy }, "pointer");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Moves a box to another page. Shares `moveBox`'s coalescing key set only
|
|
144
|
+
* incidentally if a drag ever patches `page` alongside `x`/`y` in the same
|
|
145
|
+
* step — ordinary page reassignment is a discrete action, so `origin:
|
|
146
|
+
* "format"` (never coalesced), matching `setBoxZ` below.
|
|
147
|
+
* @param {import("../state/editor-state.js").EditorState} state
|
|
148
|
+
* @param {import("../model/node.js").NodeId} containerId
|
|
149
|
+
* @param {number} page
|
|
150
|
+
* @returns {import("../model/transaction.js").Transaction | null}
|
|
151
|
+
*/
|
|
152
|
+
export function setBoxPage(state, containerId, page) {
|
|
153
|
+
return setLayout(state, containerId, { page }, "format");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Reorders a box's stacking. A discrete action (a menu command or keyboard
|
|
158
|
+
* shortcut, never a drag), so `origin: "format"` — never coalesced.
|
|
159
|
+
* @param {import("../state/editor-state.js").EditorState} state
|
|
160
|
+
* @param {import("../model/node.js").NodeId} containerId
|
|
161
|
+
* @param {number} z
|
|
162
|
+
* @returns {import("../model/transaction.js").Transaction | null}
|
|
163
|
+
*/
|
|
164
|
+
export function setBoxZ(state, containerId, z) {
|
|
165
|
+
return setLayout(state, containerId, { z }, "format");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @typedef {object} BoxTransformEntry
|
|
170
|
+
* @property {import("../model/node.js").NodeId} containerId
|
|
171
|
+
* @property {Partial<import("../model/node.js").BoxLayout>} patch
|
|
172
|
+
*/
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The shared engine behind `transformBoxes`/`setBoxZOrder`: every entry in
|
|
176
|
+
* one call becomes its OWN `setLayoutStep`, all in ONE transaction —
|
|
177
|
+
* deliberately a NEW function rather than a change to `setLayout` above,
|
|
178
|
+
* which `moveBox`/`resizeBox` must stay byte-identical to (HIST1/HIST5/
|
|
179
|
+
* HIST6's `find` strings, and their monotonic `expectedMinBreaks` 9/8/7,
|
|
180
|
+
* are measured against those two functions' current bodies).
|
|
181
|
+
*
|
|
182
|
+
* Unlike `setLayout` (one box, one step, skipped entirely when its patch
|
|
183
|
+
* is a no-op), an entry here is never dropped individually just because
|
|
184
|
+
* ITS OWN patch didn't change anything this frame — ADR-0018 Karar 2's
|
|
185
|
+
* "transformBoxes bir karedeki HER girdiyi yazar" rule. A multi-box drag's
|
|
186
|
+
* frame shape (step count, and each step's key set) has to stay identical
|
|
187
|
+
* from frame to frame for `HistoryEngine.canCoalesce`'s `sameKeySet` check
|
|
188
|
+
* to keep collapsing the gesture into one undo entry; dropping one entry
|
|
189
|
+
* because it happened not to move this frame would shrink the step count
|
|
190
|
+
* and split the drag mid-gesture — the exact bug `setLayout`'s own comment
|
|
191
|
+
* already documents for a single box's unchanged key, generalized to N.
|
|
192
|
+
*
|
|
193
|
+
* The WHOLE call is a no-op (returns null) when nothing in the batch
|
|
194
|
+
* changed, or when any entry names something that is not a box — writing
|
|
195
|
+
* N-1 of N valid entries would silently narrow the gesture's own roster.
|
|
196
|
+
*
|
|
197
|
+
* @param {import("../state/editor-state.js").EditorState} state
|
|
198
|
+
* @param {BoxTransformEntry[]} entries
|
|
199
|
+
* @param {import("../model/transaction.js").TransactionOrigin} origin
|
|
200
|
+
* @returns {import("../model/transaction.js").Transaction | null}
|
|
201
|
+
*/
|
|
202
|
+
function transformLayouts(state, entries, origin) {
|
|
203
|
+
const { doc, selection } = state;
|
|
204
|
+
|
|
205
|
+
const current = entries.map((entry) => layoutOf(doc, entry.containerId));
|
|
206
|
+
if (current.some((layout) => layout === null)) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const anyChanged = entries.some((entry, i) =>
|
|
211
|
+
changesAnything(/** @type {import("../model/node.js").BoxLayout} */ (current[i]), entry.patch),
|
|
212
|
+
);
|
|
213
|
+
if (!anyChanged) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const tr = createTransaction(doc, selection, origin);
|
|
218
|
+
for (const entry of entries) {
|
|
219
|
+
tr.step(setLayoutStep(entry.containerId, entry.patch));
|
|
220
|
+
}
|
|
221
|
+
return tr.build();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Moves/resizes N boxes in ONE transaction — the command a corner-handle
|
|
226
|
+
* drag needs and `moveBox`/`resizeBox` cannot give: those write exactly
|
|
227
|
+
* `{x,y}`/`{cx,cy}`, never a corner's full `{x,y,cx,cy}` in one step
|
|
228
|
+
* (ADR-0018 Karar 2). `origin: "pointer"` — coalesces the same way a
|
|
229
|
+
* single-box drag does, keyed on each step's own patch (see
|
|
230
|
+
* `src/canvas/handles.js`'s `resizeKeys` for the fixed key set a given
|
|
231
|
+
* handle's drag must keep writing for the whole gesture).
|
|
232
|
+
* @param {import("../state/editor-state.js").EditorState} state
|
|
233
|
+
* @param {BoxTransformEntry[]} entries
|
|
234
|
+
* @returns {import("../model/transaction.js").Transaction | null}
|
|
235
|
+
*/
|
|
236
|
+
export function transformBoxes(state, entries) {
|
|
237
|
+
return transformLayouts(state, entries, "pointer");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* `transformBoxes` for exactly one box — the corner-handle case itself.
|
|
242
|
+
* @param {import("../state/editor-state.js").EditorState} state
|
|
243
|
+
* @param {import("../model/node.js").NodeId} containerId
|
|
244
|
+
* @param {Partial<import("../model/node.js").BoxLayout>} patch
|
|
245
|
+
* @returns {import("../model/transaction.js").Transaction | null}
|
|
246
|
+
*/
|
|
247
|
+
export function transformBox(state, containerId, patch) {
|
|
248
|
+
return transformBoxes(state, [{ containerId, patch }]);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @typedef {object} BoxZOrderEntry
|
|
253
|
+
* @property {import("../model/node.js").NodeId} containerId
|
|
254
|
+
* @property {number} z
|
|
255
|
+
*/
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Reorders N boxes' stacking in ONE transaction (a "bring to front" over a
|
|
259
|
+
* multi-selection touches more than one box's z at once). `origin:
|
|
260
|
+
* "format"` — a menu/keyboard action, never coalesced, matching the
|
|
261
|
+
* existing single-box `setBoxZ`.
|
|
262
|
+
* @param {import("../state/editor-state.js").EditorState} state
|
|
263
|
+
* @param {BoxZOrderEntry[]} entries
|
|
264
|
+
* @returns {import("../model/transaction.js").Transaction | null}
|
|
265
|
+
*/
|
|
266
|
+
export function setBoxZOrder(state, entries) {
|
|
267
|
+
return transformLayouts(
|
|
268
|
+
state,
|
|
269
|
+
entries.map((entry) => ({ containerId: entry.containerId, patch: { z: entry.z } })),
|
|
270
|
+
"format",
|
|
271
|
+
);
|
|
272
|
+
}
|