@sobree/core 0.1.24 → 0.1.26
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/dist/editor/index.d.ts +51 -120
- package/dist/editor/internal/changePipeline.d.ts +123 -0
- package/dist/editor/internal/frameCaret.d.ts +37 -0
- package/dist/editor/internal/frames.d.ts +105 -0
- package/dist/editor/view/docSerialize/block.d.ts +15 -0
- package/dist/editor/view/docSerialize/index.d.ts +10 -1
- package/dist/editor/view/docSerialize/inline.d.ts +2 -1
- package/dist/editor/wiring.d.ts +3 -0
- package/dist/history/history.d.ts +27 -6
- package/dist/history/types.d.ts +32 -3
- package/dist/index.js +5475 -5166
- package/dist/index.js.map +1 -1
- package/dist/paperStack/paperStack.d.ts +28 -5
- package/package.json +1 -1
package/dist/editor/index.d.ts
CHANGED
|
@@ -103,8 +103,6 @@ export declare class Editor {
|
|
|
103
103
|
private readonly registry;
|
|
104
104
|
private readonly debounceMs;
|
|
105
105
|
private readonly getContentHosts;
|
|
106
|
-
private debounceHandle;
|
|
107
|
-
private revision;
|
|
108
106
|
/** The editor's observable event surface (change / selection / keydown
|
|
109
107
|
* / track-changes-change). See `./events.ts`. */
|
|
110
108
|
private readonly events;
|
|
@@ -123,8 +121,6 @@ export declare class Editor {
|
|
|
123
121
|
* `ops/trackedInput`.
|
|
124
122
|
*/
|
|
125
123
|
private trackedInput;
|
|
126
|
-
/** Cached last-seen per-block JSON strings, for diff-based version bumps. */
|
|
127
|
-
private lastSerialisedBlocks;
|
|
128
124
|
/** Tracks `@font-face` registrations for the document's embedded fonts. */
|
|
129
125
|
private readonly fontFaces;
|
|
130
126
|
/**
|
|
@@ -136,29 +132,25 @@ export declare class Editor {
|
|
|
136
132
|
*/
|
|
137
133
|
readonly history: History;
|
|
138
134
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* tell us anything the AST doesn't already know, while losing any
|
|
143
|
-
* fidelity the serializer drops (column widths, vAlign, …). `getDocument`
|
|
144
|
-
* and `emitChangeNow` sync only when this flag is set.
|
|
135
|
+
* The editing context the caret was last in — a frame id, or `"body"`.
|
|
136
|
+
* When it changes, the undo-capture group is closed so each box's edit
|
|
137
|
+
* is a distinct undo step. `null` until the first selection.
|
|
145
138
|
*/
|
|
146
|
-
private
|
|
139
|
+
private lastEditContext;
|
|
147
140
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* `anchoredFrames[id].content.body`.
|
|
141
|
+
* Editable-textbox-frame controller — owns the dirty-frame set, the
|
|
142
|
+
* frame DOM read-back, and the pre-/post-edit selection capture/restore
|
|
143
|
+
* that drives gold-standard frame undo. Built in the constructor. See
|
|
144
|
+
* `./internal/frames.ts`.
|
|
153
145
|
*/
|
|
154
|
-
private
|
|
146
|
+
private frames;
|
|
155
147
|
/**
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
148
|
+
* The change / sync pipeline — owns `revision`, the debounce handle, the
|
|
149
|
+
* per-block JSON cache + `domDirty`, and the render → mirror → emit
|
|
150
|
+
* cycle (`commit`, `syncFromDom`, `adoptYDocState`, …). Built in the
|
|
151
|
+
* constructor. See `./internal/changePipeline.ts`.
|
|
160
152
|
*/
|
|
161
|
-
private
|
|
153
|
+
private pipeline;
|
|
162
154
|
/**
|
|
163
155
|
* Kernel seam handed to the behaviour modules (`ops/*`, `query`). Built
|
|
164
156
|
* once in the constructor; closes over this instance's privates so the
|
|
@@ -167,6 +159,32 @@ export declare class Editor {
|
|
|
167
159
|
*/
|
|
168
160
|
private readonly ctx;
|
|
169
161
|
constructor(host: HTMLElement, options?: EditorOptions);
|
|
162
|
+
/**
|
|
163
|
+
* Optional content-hashed blob layer. The cache's `onResolved` callback
|
|
164
|
+
* patches `doc.rawParts` and fires `change` so the renderer picks up a
|
|
165
|
+
* freshly-arrived blob without an explicit refresh from the embedder.
|
|
166
|
+
*/
|
|
167
|
+
private createBlobCache;
|
|
168
|
+
/**
|
|
169
|
+
* History orchestrator — Phase 1b.6: backed by Y.UndoManager, which
|
|
170
|
+
* observes the body / meta / parts top-level Y types and tracks
|
|
171
|
+
* operations whose origin matches `localOrigin`. Local edits (mirrored
|
|
172
|
+
* with origin "local") create stack items; remote-provider edits don't —
|
|
173
|
+
* so `Cmd+Z` reverses only this peer's own edits. Selection capture /
|
|
174
|
+
* restore is delegated to the {@link FrameController} (referenced lazily;
|
|
175
|
+
* it's built right after this).
|
|
176
|
+
*/
|
|
177
|
+
private createHistory;
|
|
178
|
+
/** Editor-attribute setup + the initial render of the seeded document. */
|
|
179
|
+
private mountHost;
|
|
180
|
+
/** The hooks `wireEditorDom` calls — each forwards to the owning module. */
|
|
181
|
+
private buildDomHooks;
|
|
182
|
+
/**
|
|
183
|
+
* Route an `input` event. A caret inside an editable textbox frame reads
|
|
184
|
+
* back into that frame's body (not the document body); everything else is
|
|
185
|
+
* an ordinary body edit. Either way, schedule a debounced change.
|
|
186
|
+
*/
|
|
187
|
+
private handleInput;
|
|
170
188
|
/**
|
|
171
189
|
* Initialise `this.doc` + registry from the Y.Doc. Two paths, run
|
|
172
190
|
* after the context exists so the adopt path can resolve cached part
|
|
@@ -183,12 +201,6 @@ export declare class Editor {
|
|
|
183
201
|
* stay private to the class while modules get a curated surface.
|
|
184
202
|
*/
|
|
185
203
|
private buildContext;
|
|
186
|
-
/**
|
|
187
|
-
* Re-project the Y.Doc into `this.doc`, sync the BlockRegistry to
|
|
188
|
-
* the projected ids, re-render the DOM, and fire `change`. Called
|
|
189
|
-
* when a remote provider applies an update we didn't initiate.
|
|
190
|
-
*/
|
|
191
|
-
private adoptYDocState;
|
|
192
204
|
/**
|
|
193
205
|
* Resolve any `partRefs` hashes that are currently cached into the
|
|
194
206
|
* provided document's `rawParts`. In-place mutation — the document
|
|
@@ -220,26 +232,6 @@ export declare class Editor {
|
|
|
220
232
|
setShowHiddenText(show: boolean): void;
|
|
221
233
|
/** Replace the document. Fires `change` synchronously. */
|
|
222
234
|
setDocument(doc: SobreeDocument): void;
|
|
223
|
-
/**
|
|
224
|
-
* Internal apply path shared by `setDocument` and any other
|
|
225
|
-
* full-replace caller. The Y.Doc mirror produces tracked Y
|
|
226
|
-
* operations that Y.UndoManager turns into a single stack item.
|
|
227
|
-
*/
|
|
228
|
-
private applyDocument;
|
|
229
|
-
/**
|
|
230
|
-
* Re-render the current `doc` into the content hosts. Syncs
|
|
231
|
-
* `@font-face` registrations BEFORE rendering so newly-embedded fonts
|
|
232
|
-
* are available to the render pass. No selection restore, no change
|
|
233
|
-
* emit — callers sequence those.
|
|
234
|
-
*/
|
|
235
|
-
private renderCurrent;
|
|
236
|
-
/**
|
|
237
|
-
* Soft-revert to a doc `snapshot` and re-render. Resets the
|
|
238
|
-
* serialised-block cache + dom-dirty flag; no registry reset, mirror, or
|
|
239
|
-
* change emit. Used to undo native IME mutations before a tracked
|
|
240
|
-
* re-insert (see `ops/trackedInput`).
|
|
241
|
-
*/
|
|
242
|
-
private restoreSnapshot;
|
|
243
235
|
/**
|
|
244
236
|
* Drop entries from `rawParts` that nothing in the AST references.
|
|
245
237
|
* Useful after deleting images (or font embeds — Phase 3) to keep the
|
|
@@ -458,87 +450,18 @@ export declare class Editor {
|
|
|
458
450
|
_registry(): BlockRegistry;
|
|
459
451
|
/** @internal */
|
|
460
452
|
_blockElementAt(index: number): HTMLElement | null;
|
|
461
|
-
/**
|
|
462
|
-
* Parallel array of live block ids (same length as `doc.body`), used
|
|
463
|
-
* by the renderer to stamp `data-block-id` onto every block element.
|
|
464
|
-
* Lets external tools (block tools, embedders) locate a block's DOM
|
|
465
|
-
* element after the body is re-rendered from scratch.
|
|
466
|
-
*/
|
|
467
|
-
private blockIdsArray;
|
|
468
453
|
private checkRefs;
|
|
469
454
|
private checkRange;
|
|
470
|
-
/**
|
|
471
|
-
* Apply a mutation to `this.doc`, update the registry, re-render, fire
|
|
472
|
-
* change. Returns the affected refs (post-bump).
|
|
473
|
-
*/
|
|
474
|
-
private commit;
|
|
475
|
-
/**
|
|
476
|
-
* Ensure `this.doc` reflects the latest edits. If the DOM has been
|
|
477
|
-
* dirtied by user typing / paste / drop, pull the latest content out
|
|
478
|
-
* of it and bump affected block versions. If the last mutation came
|
|
479
|
-
* from the API, the AST is already current — skip the (lossy)
|
|
480
|
-
* DOM-to-AST round-trip.
|
|
481
|
-
*/
|
|
482
|
-
private ensureCurrent;
|
|
483
|
-
private syncFromDom;
|
|
484
|
-
/**
|
|
485
|
-
* Re-read the DOM of each dirty editable textbox frame into the AST.
|
|
486
|
-
* The frame element IS the serialization host (the block renderer paints
|
|
487
|
-
* its body directly into it), so `serializeHostsToDocument([el])` yields
|
|
488
|
-
* the same `Block[]` shape as a body host. Matched to the AST frame by
|
|
489
|
-
* its stable `data-anchor-id`. Pure body swap — geometry/anchor untouched.
|
|
490
|
-
*/
|
|
491
|
-
private syncFramesFromDom;
|
|
492
|
-
/**
|
|
493
|
-
* The id of the editable textbox frame the caret currently sits in, or
|
|
494
|
-
* null when the selection is in ordinary body flow. Used to route an
|
|
495
|
-
* `input` event to the frame read-back instead of the body read-back.
|
|
496
|
-
*/
|
|
497
|
-
private editedFrameId;
|
|
498
455
|
/**
|
|
499
456
|
* Toggle a mark on the caret inside an editable textbox frame, natively
|
|
500
|
-
* (`document.execCommand`)
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
* to run properties). Returns false when the caret isn't in a frame, so
|
|
504
|
-
* the mark command falls back to the body path.
|
|
457
|
+
* (`document.execCommand`). Returns false when the caret isn't in a
|
|
458
|
+
* frame, so the mark command falls back to the body path. See
|
|
459
|
+
* {@link FrameController.applyFrameMark}.
|
|
505
460
|
*/
|
|
506
461
|
applyFrameMark(tag: string): boolean;
|
|
507
462
|
/** Active state of `tag` at a frame caret (toolbar highlight), or null
|
|
508
463
|
* when the caret isn't in a frame. */
|
|
509
464
|
frameMarkActive(tag: string): boolean | null;
|
|
510
|
-
/**
|
|
511
|
-
* Schedule a DOM-driven change emit. Called from the `input` listener
|
|
512
|
-
* when the user types — the DOM is the source of truth and we sync the
|
|
513
|
-
* AST from it before notifying listeners.
|
|
514
|
-
*/
|
|
515
|
-
private scheduleChange;
|
|
516
|
-
/**
|
|
517
|
-
* Emit a `change` event using the current in-memory AST verbatim. Do
|
|
518
|
-
* NOT sync from DOM — callers that need a DOM sync should call it
|
|
519
|
-
* explicitly (user-typing path does). API mutations have already
|
|
520
|
-
* rendered their AST into the DOM and must not let the lossy DOM-read
|
|
521
|
-
* overwrite properties the renderer doesn't surface
|
|
522
|
-
* (column widths, verticalAlign, table properties, …).
|
|
523
|
-
*/
|
|
524
|
-
private emitChangeNow;
|
|
525
|
-
/**
|
|
526
|
-
* Snapshot of the live block ids in body order — used both as the
|
|
527
|
-
* input to `applyDocumentToYDoc` (so each Y.Map carries its stable
|
|
528
|
-
* id) and as the `blockIdsArray()` the renderer uses to set the
|
|
529
|
-
* `data-block-id` attribute.
|
|
530
|
-
*/
|
|
531
|
-
private allBlockIds;
|
|
532
|
-
/**
|
|
533
|
-
* Mirror the current `this.doc` into the Y.Doc as a single
|
|
534
|
-
* transaction. The diff is performed by `applyDocumentToYDoc`,
|
|
535
|
-
* which matches blocks by id so concurrent edits to *different*
|
|
536
|
-
* blocks merge cleanly via the Y.Array CRDT.
|
|
537
|
-
*
|
|
538
|
-
* Origin is `"local"` so a future Y observer can distinguish locally-
|
|
539
|
-
* driven mutations (already rendered) from remote ones (need re-render).
|
|
540
|
-
*/
|
|
541
|
-
private mirrorToYDoc;
|
|
542
465
|
/**
|
|
543
466
|
* Compose the current selection into a {@link SelectionPayload} and
|
|
544
467
|
* dispatch to subscribers. Called from the document-level
|
|
@@ -546,6 +469,14 @@ export declare class Editor {
|
|
|
546
469
|
* even when no subscribers exist (the early-return keeps it cheap).
|
|
547
470
|
*/
|
|
548
471
|
private fireSelection;
|
|
472
|
+
/**
|
|
473
|
+
* When the caret moves to a different editing context — another textbox
|
|
474
|
+
* frame, or between a frame and the body — close the undo-capture group
|
|
475
|
+
* so the next edit there is its own undo step. Without this, two edits
|
|
476
|
+
* to different boxes within `captureTimeout` coalesce and a single undo
|
|
477
|
+
* reverts both, unlike Word (where each box is a distinct action).
|
|
478
|
+
*/
|
|
479
|
+
private breakUndoOnContextChange;
|
|
549
480
|
private fireKeyDown;
|
|
550
481
|
}
|
|
551
482
|
export { countBlocks };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { EditResult } from '../../doc/api';
|
|
2
|
+
import { SobreeDocument } from '../../doc/types';
|
|
3
|
+
import { EditorContext } from '../context';
|
|
4
|
+
import { EditorEvents } from '../events';
|
|
5
|
+
import { FrameController } from './frames';
|
|
6
|
+
import { Mutation } from './mutations';
|
|
7
|
+
export declare class ChangePipeline {
|
|
8
|
+
private readonly ctx;
|
|
9
|
+
private readonly events;
|
|
10
|
+
private readonly frames;
|
|
11
|
+
private readonly debounceMs;
|
|
12
|
+
private revision;
|
|
13
|
+
private debounceHandle;
|
|
14
|
+
/** Cached last-seen per-block JSON strings, for diff-based version bumps. */
|
|
15
|
+
private lastSerialisedBlocks;
|
|
16
|
+
/**
|
|
17
|
+
* True when DOM mutations since the last sync were user-driven (typing,
|
|
18
|
+
* paste, drag-drop image). False right after we render from AST — the
|
|
19
|
+
* DOM is then a projection of the doc, and reading it back can't tell us
|
|
20
|
+
* anything the AST doesn't already know, while losing any fidelity the
|
|
21
|
+
* serializer drops (column widths, vAlign, …). `getDocument` and
|
|
22
|
+
* `emitChangeNow` sync only when this flag is set.
|
|
23
|
+
*/
|
|
24
|
+
private domDirty;
|
|
25
|
+
/**
|
|
26
|
+
* Set by `syncFromDom` when the pending change was a pure live frame
|
|
27
|
+
* keystroke; read (and reset) by `emitChangeNow` into the change
|
|
28
|
+
* payload's `liveFrameEdit`. Lets the host skip the overlay repaint
|
|
29
|
+
* that would clobber the caret, while still repainting on undo/remote.
|
|
30
|
+
*/
|
|
31
|
+
private pendingLiveFrameEdit;
|
|
32
|
+
constructor(ctx: EditorContext, events: EditorEvents, frames: FrameController, debounceMs: number);
|
|
33
|
+
/** Monotonic counter bumped on each `change` event. */
|
|
34
|
+
getRevision(): number;
|
|
35
|
+
/** Mark the body DOM dirty (user typed in body flow). */
|
|
36
|
+
markBodyDirty(): void;
|
|
37
|
+
setDomDirty(value: boolean): void;
|
|
38
|
+
/**
|
|
39
|
+
* Seed `lastSerialisedBlocks` from `doc` (no dirty flip). Called from the
|
|
40
|
+
* Editor's document-init path, which owns `doc`/registry setup directly.
|
|
41
|
+
*/
|
|
42
|
+
captureBaseline(doc: SobreeDocument): void;
|
|
43
|
+
/** Cancel any pending debounced change (teardown). */
|
|
44
|
+
cancelPending(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Apply a mutation to the doc, update the registry, re-render, fire
|
|
47
|
+
* change. Returns the affected refs (post-bump).
|
|
48
|
+
*/
|
|
49
|
+
commit<T = void>(update: Partial<SobreeDocument>, mutations: readonly Mutation[], value?: T, _reason?: string): EditResult<T>;
|
|
50
|
+
/**
|
|
51
|
+
* Ensure the doc reflects the latest edits. If the DOM has been dirtied
|
|
52
|
+
* by user typing / paste / drop, pull the latest content out of it and
|
|
53
|
+
* bump affected block versions. If the last mutation came from the API,
|
|
54
|
+
* the AST is already current — skip the (lossy) DOM-to-AST round-trip.
|
|
55
|
+
*/
|
|
56
|
+
ensureCurrent(): SobreeDocument;
|
|
57
|
+
syncFromDom(): SobreeDocument;
|
|
58
|
+
/**
|
|
59
|
+
* Schedule a DOM-driven change emit. Called from the `input` listener
|
|
60
|
+
* when the user types — the DOM is the source of truth and we sync the
|
|
61
|
+
* AST from it before notifying listeners.
|
|
62
|
+
*/
|
|
63
|
+
scheduleChange(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Emit a `change` event using the current in-memory AST verbatim. Do
|
|
66
|
+
* NOT sync from DOM — callers that need a DOM sync should call it
|
|
67
|
+
* explicitly (user-typing path does). API mutations have already
|
|
68
|
+
* rendered their AST into the DOM and must not let the lossy DOM-read
|
|
69
|
+
* overwrite properties the renderer doesn't surface
|
|
70
|
+
* (column widths, verticalAlign, table properties, …).
|
|
71
|
+
*/
|
|
72
|
+
emitChangeNow(): void;
|
|
73
|
+
/**
|
|
74
|
+
* Re-render the current `doc` into the content hosts. Syncs
|
|
75
|
+
* `@font-face` registrations BEFORE rendering so newly-embedded fonts
|
|
76
|
+
* are available to the render pass. No selection restore, no change
|
|
77
|
+
* emit — callers sequence those.
|
|
78
|
+
*/
|
|
79
|
+
renderCurrent(): void;
|
|
80
|
+
/**
|
|
81
|
+
* Soft-revert to a doc `snapshot` and re-render. Resets the
|
|
82
|
+
* serialised-block cache + dom-dirty flag; no registry reset, mirror, or
|
|
83
|
+
* change emit. Used to undo native IME mutations before a tracked
|
|
84
|
+
* re-insert (see `ops/trackedInput`).
|
|
85
|
+
*/
|
|
86
|
+
restoreSnapshot(snapshot: SobreeDocument): void;
|
|
87
|
+
/**
|
|
88
|
+
* Internal apply path shared by `setDocument` and any other
|
|
89
|
+
* full-replace caller. The Y.Doc mirror produces tracked Y
|
|
90
|
+
* operations that Y.UndoManager turns into a single stack item.
|
|
91
|
+
*/
|
|
92
|
+
applyDocument(doc: SobreeDocument): void;
|
|
93
|
+
/**
|
|
94
|
+
* Parallel array of live block ids (same length as `doc.body`), used by
|
|
95
|
+
* the renderer to stamp `data-block-id` onto every block element. Lets
|
|
96
|
+
* external tools (block tools, embedders) locate a block's DOM element
|
|
97
|
+
* after the body is re-rendered from scratch.
|
|
98
|
+
*/
|
|
99
|
+
blockIdsArray(): string[];
|
|
100
|
+
/**
|
|
101
|
+
* Snapshot of the live block ids in body order — used both as the input
|
|
102
|
+
* to `applyDocumentToYDoc` (so each Y.Map carries its stable id) and as
|
|
103
|
+
* the `blockIdsArray()` the renderer uses to set the `data-block-id`
|
|
104
|
+
* attribute.
|
|
105
|
+
*/
|
|
106
|
+
allBlockIds(): string[];
|
|
107
|
+
/**
|
|
108
|
+
* Mirror the current `doc` into the Y.Doc as a single transaction. The
|
|
109
|
+
* diff is performed by `applyDocumentToYDoc`, which matches blocks by id
|
|
110
|
+
* so concurrent edits to *different* blocks merge cleanly via the
|
|
111
|
+
* Y.Array CRDT.
|
|
112
|
+
*
|
|
113
|
+
* Origin is `"local"` so a future Y observer can distinguish locally-
|
|
114
|
+
* driven mutations (already rendered) from remote ones (need re-render).
|
|
115
|
+
*/
|
|
116
|
+
mirrorToYDoc(): void;
|
|
117
|
+
/**
|
|
118
|
+
* Re-project the Y.Doc into `doc`, sync the BlockRegistry to the
|
|
119
|
+
* projected ids, re-render the DOM, and fire `change`. Called when a
|
|
120
|
+
* remote provider applies an update we didn't initiate.
|
|
121
|
+
*/
|
|
122
|
+
adoptYDocState(): void;
|
|
123
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selection <-> character-offset helpers for editable textbox frames.
|
|
3
|
+
*
|
|
4
|
+
* A frame's body is a contentEditable island whose text isn't addressable
|
|
5
|
+
* by the body's block-registry `Selection` model. Undo/redo still needs to
|
|
6
|
+
* capture the caret (or selection) on a frame edit and put it back, so we
|
|
7
|
+
* model a frame selection as a `{ start, end }` character span across the
|
|
8
|
+
* frame's text nodes — stable enough to survive the AST round-trip and
|
|
9
|
+
* clamp cleanly to the (possibly shorter) post-undo text. A collapsed
|
|
10
|
+
* caret is `start === end`.
|
|
11
|
+
*/
|
|
12
|
+
export interface FrameOffsets {
|
|
13
|
+
/** Character offset of the selection start within the frame. */
|
|
14
|
+
start: number;
|
|
15
|
+
/** Character offset of the selection end (=== start for a caret). */
|
|
16
|
+
end: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The current selection's `{ start, end }` character offsets within `root`,
|
|
20
|
+
* or `null` when the selection isn't anchored inside `root`. Normalised so
|
|
21
|
+
* `start <= end` regardless of selection direction.
|
|
22
|
+
*/
|
|
23
|
+
export declare function frameSelectionOffsets(root: HTMLElement, doc: Document): FrameOffsets | null;
|
|
24
|
+
/**
|
|
25
|
+
* Character offset of the collapsed caret within `root` (selection start),
|
|
26
|
+
* or `null` when the selection isn't inside `root`. Thin wrapper over
|
|
27
|
+
* {@link frameSelectionOffsets} for caret-only callers.
|
|
28
|
+
*/
|
|
29
|
+
export declare function caretCharOffset(root: HTMLElement, doc: Document): number | null;
|
|
30
|
+
/**
|
|
31
|
+
* Select `start..end` characters within `root` (a collapsed caret when
|
|
32
|
+
* equal), clamping each end to the available text — an undo can revert to
|
|
33
|
+
* shorter text. No-op-safe when `root` has no text nodes.
|
|
34
|
+
*/
|
|
35
|
+
export declare function applyFrameSelection(root: HTMLElement, offsets: FrameOffsets, doc: Document): void;
|
|
36
|
+
/** Place a collapsed caret `offset` chars into `root` (clamped). */
|
|
37
|
+
export declare function placeCaretAtOffset(root: HTMLElement, offset: number, doc: Document): void;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { CapturedSelection } from '../../history/types';
|
|
2
|
+
import { EditorContext } from '../context';
|
|
3
|
+
export declare class FrameController {
|
|
4
|
+
private readonly ctx;
|
|
5
|
+
/**
|
|
6
|
+
* Ids of editable textbox frames whose DOM the user has edited since
|
|
7
|
+
* the last sync. Frames live in the floating overlay (outside the
|
|
8
|
+
* body content hosts), so they need their own read-back path —
|
|
9
|
+
* `syncFramesFromDom` re-serialises each dirty frame into
|
|
10
|
+
* `anchoredFrames[id].content.body`.
|
|
11
|
+
*/
|
|
12
|
+
private readonly dirtyFrameIds;
|
|
13
|
+
/**
|
|
14
|
+
* Selection captured at `beforeinput` (pre-DOM-mutation) for the open
|
|
15
|
+
* undo group — restored on undo so the caret lands where the edit began.
|
|
16
|
+
* `hasPendingPreEdit` distinguishes "nothing stashed" from a stashed
|
|
17
|
+
* `null` (a body `Selection` can legitimately be `null`).
|
|
18
|
+
*/
|
|
19
|
+
private pendingPreEdit;
|
|
20
|
+
private hasPendingPreEdit;
|
|
21
|
+
constructor(ctx: EditorContext);
|
|
22
|
+
/** True when a frame edit is pending a read-back. */
|
|
23
|
+
hasDirtyFrames(): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Route an `input` event: if the caret sits in an editable textbox
|
|
26
|
+
* frame, mark that frame dirty and return `true` (the body must NOT be
|
|
27
|
+
* read back). Returns `false` for an ordinary body edit.
|
|
28
|
+
*/
|
|
29
|
+
routeInput(): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Re-read the DOM of each dirty editable textbox frame into the AST.
|
|
32
|
+
* The frame element IS the serialization host (the block renderer paints
|
|
33
|
+
* its body directly into it), so `serializeHostsToDocument([el])` yields
|
|
34
|
+
* the same `Block[]` shape as a body host. Matched to the AST frame by
|
|
35
|
+
* its stable `data-anchor-id`. Pure body swap — geometry/anchor untouched.
|
|
36
|
+
*
|
|
37
|
+
* `captureRunDefaults` promotes each paragraph's rendered base font to
|
|
38
|
+
* `runDefaults`, so a frame's text keeps its size/family even when a
|
|
39
|
+
* keystroke (or a select-all-retype) strips every run's inline styling —
|
|
40
|
+
* the heading no longer collapses to the default font on the next repaint.
|
|
41
|
+
*/
|
|
42
|
+
syncFramesFromDom(): void;
|
|
43
|
+
/**
|
|
44
|
+
* The id of the editable textbox frame the caret currently sits in, or
|
|
45
|
+
* null when the selection is in ordinary body flow. Used to route an
|
|
46
|
+
* `input` event to the frame read-back instead of the body read-back.
|
|
47
|
+
*/
|
|
48
|
+
editedFrameId(): string | null;
|
|
49
|
+
/** The editable textbox frame element the caret is inside, or null. */
|
|
50
|
+
private focusedFrameEl;
|
|
51
|
+
/** The (freshly-painted) frame element with this `data-anchor-id`, or null. */
|
|
52
|
+
private frameElById;
|
|
53
|
+
/**
|
|
54
|
+
* Capture the live selection for an undo step. A selection inside an
|
|
55
|
+
* editable textbox frame becomes a `FrameSelection` — the body
|
|
56
|
+
* `Selection` model is keyed on registry blocks and can't address frame
|
|
57
|
+
* content — so undo can restore it the same way it restores a body
|
|
58
|
+
* selection. Everything else is an ordinary body selection.
|
|
59
|
+
*/
|
|
60
|
+
captureSelectionForHistory(): CapturedSelection;
|
|
61
|
+
/**
|
|
62
|
+
* The selection BEFORE the edit that opened the current undo group,
|
|
63
|
+
* stashed by `onBeforeInput` (which fires before the DOM mutates). Falls
|
|
64
|
+
* back to the live selection for edits that bypass `beforeinput`
|
|
65
|
+
* (programmatic mutations, `setDocument`).
|
|
66
|
+
*/
|
|
67
|
+
capturePreEditSelection(): CapturedSelection;
|
|
68
|
+
/**
|
|
69
|
+
* Stash the pre-edit selection on the first input of a new undo group,
|
|
70
|
+
* so undo can land the caret where the edit began. `beforeinput` fires
|
|
71
|
+
* before the browser mutates the DOM, so the live selection here is the
|
|
72
|
+
* pre-edit position. Only the FIRST input of a group stashes; coalesced
|
|
73
|
+
* inputs leave the group's `before` intact (`History.onGroupSettled`
|
|
74
|
+
* clears the stash once a step has captured).
|
|
75
|
+
*
|
|
76
|
+
* Scoped to textbox frames. The body already restores its caret through
|
|
77
|
+
* the proven `applySelectionToDom` path on `stack-item-popped`; we leave
|
|
78
|
+
* its behaviour byte-for-byte unchanged (no pre-edit stash → `before`
|
|
79
|
+
* falls back to the post-edit selection, same as `after`). Frames had no
|
|
80
|
+
* working restore at all, so they get the full pre-/post-edit treatment.
|
|
81
|
+
*/
|
|
82
|
+
onBeforeInput(): void;
|
|
83
|
+
/** Drop the pending pre-edit stash — a step has captured (or extended) it. */
|
|
84
|
+
clearPendingPreEditSelection(): void;
|
|
85
|
+
/**
|
|
86
|
+
* Restore an undo step's selection. Fires on `stack-item-popped`, which
|
|
87
|
+
* runs AFTER the change handler has already repainted the frame overlay
|
|
88
|
+
* (`adoptYDocState` calls `emitChangeNow` synchronously), so a captured
|
|
89
|
+
* frame selection lands on the fresh frame element and sticks — the same
|
|
90
|
+
* lifecycle the body selection restore relies on.
|
|
91
|
+
*/
|
|
92
|
+
restoreCapturedSelection(sel: CapturedSelection): void;
|
|
93
|
+
/**
|
|
94
|
+
* Toggle a mark on the caret inside an editable textbox frame, natively
|
|
95
|
+
* (`document.execCommand`), so the body-selection mark path doesn't have
|
|
96
|
+
* to understand frame coordinates. The resulting `<b>`/`<i>`/`<u>` tags
|
|
97
|
+
* round-trip through the frame read-back (the inline serializer maps them
|
|
98
|
+
* to run properties). Returns false when the caret isn't in a frame, so
|
|
99
|
+
* the mark command falls back to the body path.
|
|
100
|
+
*/
|
|
101
|
+
applyFrameMark(tag: string): boolean;
|
|
102
|
+
/** Active state of `tag` at a frame caret (toolbar highlight), or null
|
|
103
|
+
* when the caret isn't in a frame. */
|
|
104
|
+
frameMarkActive(tag: string): boolean | null;
|
|
105
|
+
}
|
|
@@ -10,5 +10,20 @@ export interface BlockSerializeContext {
|
|
|
10
10
|
numId: number;
|
|
11
11
|
ordered: boolean;
|
|
12
12
|
} | null;
|
|
13
|
+
/**
|
|
14
|
+
* Capture each paragraph's effective base run style (from the rendered
|
|
15
|
+
* `<p>`'s inline font) into `ParagraphProperties.runDefaults`.
|
|
16
|
+
*
|
|
17
|
+
* Used for textbox-frame read-back only. A frame's text carries its font
|
|
18
|
+
* on the runs, with no named style to fall back on; so when a keystroke
|
|
19
|
+
* lands in a bare text node — or a select-all-retype replaces every
|
|
20
|
+
* styled span with one unstyled node — the runs lose their font and a
|
|
21
|
+
* repaint renders the whole line at the default tiny size. The `<p>`
|
|
22
|
+
* element keeps its inline font through these DOM edits, so promoting it
|
|
23
|
+
* to a paragraph-level default makes the font survive run-level loss.
|
|
24
|
+
* Body flow leaves this off: its runs legitimately inherit from named
|
|
25
|
+
* styles, which must stay style-linked across edits.
|
|
26
|
+
*/
|
|
27
|
+
captureRunDefaults?: boolean;
|
|
13
28
|
}
|
|
14
29
|
export declare function blocksFromNodes(nodes: readonly Node[], ctx: BlockSerializeContext): Block[];
|
|
@@ -5,4 +5,13 @@ import { SobreeDocument } from '../../../doc/types';
|
|
|
5
5
|
* produced here — the Sobree façade injects those from its current page
|
|
6
6
|
* setup state before handing the document off to the exporter.
|
|
7
7
|
*/
|
|
8
|
-
export
|
|
8
|
+
export interface SerializeHostsOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Capture each paragraph's effective base run style into
|
|
11
|
+
* `ParagraphProperties.runDefaults`. Set for textbox-frame read-back so a
|
|
12
|
+
* frame's font survives run-level styling loss; left off for body flow,
|
|
13
|
+
* which stays style-linked. See `BlockSerializeContext.captureRunDefaults`.
|
|
14
|
+
*/
|
|
15
|
+
captureRunDefaults?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function serializeHostsToDocument(hosts: readonly HTMLElement[], options?: SerializeHostsOptions): SobreeDocument;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { InlineRun } from '../../../doc/types';
|
|
1
|
+
import { InlineRun, RunProperties } from '../../../doc/types';
|
|
2
2
|
/**
|
|
3
3
|
* Serialise DOM children of `el` into a flat `InlineRun[]`. Nested
|
|
4
4
|
* formatting wrappers (`<strong><em>...`) are flattened — each leaf text
|
|
@@ -9,3 +9,4 @@ import { InlineRun } from '../../../doc/types';
|
|
|
9
9
|
* children (their own flat run list).
|
|
10
10
|
*/
|
|
11
11
|
export declare function serializeInlineChildren(el: HTMLElement): InlineRun[];
|
|
12
|
+
export declare function mergeStyleAttribute(base: RunProperties, styleAttr: string | null): RunProperties;
|
package/dist/editor/wiring.d.ts
CHANGED
|
@@ -24,6 +24,9 @@ export interface EditorDomHooks {
|
|
|
24
24
|
trackedInput: TrackedInput;
|
|
25
25
|
/** Tracked-changes authoring mode is on right now. */
|
|
26
26
|
isTrackedEnabled: () => boolean;
|
|
27
|
+
/** A real edit is about to mutate the DOM (`beforeinput`, before the
|
|
28
|
+
* mutation). Lets the editor stash the pre-edit selection for undo. */
|
|
29
|
+
onBeforeInput: () => void;
|
|
27
30
|
/** Mark the DOM dirty + schedule a debounced change. */
|
|
28
31
|
onInput: () => void;
|
|
29
32
|
/** Fire the editor's `selection` event. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { CapturedSelection, HistoryConfig, HistoryDepth } from './types';
|
|
2
|
+
import { Selection as PublicSelection } from '../doc/api';
|
|
3
3
|
/**
|
|
4
4
|
* Undo / redo for the Sobree editor — backed by `Y.UndoManager`.
|
|
5
5
|
*
|
|
@@ -51,22 +51,43 @@ export interface HistoryOptions extends Partial<HistoryConfig> {
|
|
|
51
51
|
* local writes. UndoManager only tracks operations whose origin
|
|
52
52
|
* is in this set. Defaults to `"local"`. */
|
|
53
53
|
localOrigin?: unknown;
|
|
54
|
-
/** Capture the *current* live selection — called as a
|
|
55
|
-
* is
|
|
56
|
-
|
|
54
|
+
/** Capture the *current* (post-edit) live selection — called as a
|
|
55
|
+
* stack item is added/updated, stashed as the step's `after`. Returns
|
|
56
|
+
* a body `Selection` or a frame selection; `History` keeps it opaque. */
|
|
57
|
+
captureSelection: () => CapturedSelection;
|
|
58
|
+
/** Capture the selection as it was BEFORE the edit that opened the
|
|
59
|
+
* current undo step — stashed as the step's `before` and restored on
|
|
60
|
+
* undo. Defaults to {@link captureSelection} when not provided (callers
|
|
61
|
+
* without a pre-edit hook, e.g. headless, get post-edit on both). */
|
|
62
|
+
capturePreEditSelection?: () => CapturedSelection;
|
|
57
63
|
/** Restore a previously-captured selection to the live DOM /
|
|
58
64
|
* EditorSelection. Called on undo / redo after the Y.Doc has been
|
|
59
65
|
* re-projected and re-rendered. */
|
|
60
|
-
restoreSelection: (sel:
|
|
66
|
+
restoreSelection: (sel: CapturedSelection) => void;
|
|
67
|
+
/** Notify the caller that the current undo group has captured (or
|
|
68
|
+
* extended) its selection, so it can drop any pending pre-edit stash.
|
|
69
|
+
* Fires on every `stack-item-added` / `stack-item-updated`. */
|
|
70
|
+
onGroupSettled?: () => void;
|
|
61
71
|
}
|
|
62
72
|
export declare class History {
|
|
63
73
|
private readonly mgr;
|
|
64
74
|
private readonly listeners;
|
|
65
75
|
private readonly captureSelection;
|
|
76
|
+
private readonly capturePreEditSelection;
|
|
66
77
|
private readonly restoreSelection;
|
|
78
|
+
private readonly onGroupSettled;
|
|
67
79
|
constructor(opts: HistoryOptions);
|
|
68
80
|
undo(): boolean;
|
|
69
81
|
redo(): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Close the current undo-capture group so the NEXT local edit starts a
|
|
84
|
+
* fresh undo step instead of coalescing into the previous one (within
|
|
85
|
+
* `captureTimeout`). The editor calls this when the editing context
|
|
86
|
+
* changes — e.g. the caret moves to a different textbox frame, or
|
|
87
|
+
* between a frame and the body — so two distinct edits don't collapse
|
|
88
|
+
* into a single undo. No-op when there's nothing pending to capture.
|
|
89
|
+
*/
|
|
90
|
+
stopCapturing(): void;
|
|
70
91
|
canUndo(): boolean;
|
|
71
92
|
canRedo(): boolean;
|
|
72
93
|
clear(): void;
|