@sobree/core 0.1.25 → 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.
@@ -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
  /**
@@ -135,30 +131,6 @@ export declare class Editor {
135
131
  * `editor.history.undo() / redo() / clear() / depth() / on(...)`.
136
132
  */
137
133
  readonly history: History;
138
- /**
139
- * True when DOM mutations since the last sync were user-driven (typing,
140
- * paste, drag-drop image). False right after we render from AST — the
141
- * DOM is then a projection of `this.doc`, and reading it back can't
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.
145
- */
146
- private domDirty;
147
- /**
148
- * Ids of editable textbox frames whose DOM the user has edited since
149
- * the last sync. Frames live in the floating overlay (outside the
150
- * body content hosts), so they need their own read-back path —
151
- * `syncFromDom` re-serialises each dirty frame into
152
- * `anchoredFrames[id].content.body`.
153
- */
154
- private readonly dirtyFrameIds;
155
- /**
156
- * Set by `syncFromDom` when the pending change was a pure live frame
157
- * keystroke; read (and reset) by `emitChangeNow` into the change
158
- * payload's `liveFrameEdit`. Lets the host skip the overlay repaint
159
- * that would clobber the caret, while still repainting on undo/remote.
160
- */
161
- private pendingLiveFrameEdit;
162
134
  /**
163
135
  * The editing context the caret was last in — a frame id, or `"body"`.
164
136
  * When it changes, the undo-capture group is closed so each box's edit
@@ -166,13 +138,19 @@ export declare class Editor {
166
138
  */
167
139
  private lastEditContext;
168
140
  /**
169
- * Selection captured at `beforeinput` (pre-DOM-mutation) for the open
170
- * undo group restored on undo so the caret lands where the edit began.
171
- * `hasPendingPreEdit` distinguishes "nothing stashed" from a stashed
172
- * `null` (a body `Selection` can legitimately be `null`).
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`.
173
145
  */
174
- private pendingPreEdit;
175
- private hasPendingPreEdit;
146
+ private frames;
147
+ /**
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`.
152
+ */
153
+ private pipeline;
176
154
  /**
177
155
  * Kernel seam handed to the behaviour modules (`ops/*`, `query`). Built
178
156
  * once in the constructor; closes over this instance's privates so the
@@ -181,6 +159,32 @@ export declare class Editor {
181
159
  */
182
160
  private readonly ctx;
183
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;
184
188
  /**
185
189
  * Initialise `this.doc` + registry from the Y.Doc. Two paths, run
186
190
  * after the context exists so the adopt path can resolve cached part
@@ -197,12 +201,6 @@ export declare class Editor {
197
201
  * stay private to the class while modules get a curated surface.
198
202
  */
199
203
  private buildContext;
200
- /**
201
- * Re-project the Y.Doc into `this.doc`, sync the BlockRegistry to
202
- * the projected ids, re-render the DOM, and fire `change`. Called
203
- * when a remote provider applies an update we didn't initiate.
204
- */
205
- private adoptYDocState;
206
204
  /**
207
205
  * Resolve any `partRefs` hashes that are currently cached into the
208
206
  * provided document's `rawParts`. In-place mutation — the document
@@ -234,26 +232,6 @@ export declare class Editor {
234
232
  setShowHiddenText(show: boolean): void;
235
233
  /** Replace the document. Fires `change` synchronously. */
236
234
  setDocument(doc: SobreeDocument): void;
237
- /**
238
- * Internal apply path shared by `setDocument` and any other
239
- * full-replace caller. The Y.Doc mirror produces tracked Y
240
- * operations that Y.UndoManager turns into a single stack item.
241
- */
242
- private applyDocument;
243
- /**
244
- * Re-render the current `doc` into the content hosts. Syncs
245
- * `@font-face` registrations BEFORE rendering so newly-embedded fonts
246
- * are available to the render pass. No selection restore, no change
247
- * emit — callers sequence those.
248
- */
249
- private renderCurrent;
250
- /**
251
- * Soft-revert to a doc `snapshot` and re-render. Resets the
252
- * serialised-block cache + dom-dirty flag; no registry reset, mirror, or
253
- * change emit. Used to undo native IME mutations before a tracked
254
- * re-insert (see `ops/trackedInput`).
255
- */
256
- private restoreSnapshot;
257
235
  /**
258
236
  * Drop entries from `rawParts` that nothing in the AST references.
259
237
  * Useful after deleting images (or font embeds — Phase 3) to keep the
@@ -472,136 +450,18 @@ export declare class Editor {
472
450
  _registry(): BlockRegistry;
473
451
  /** @internal */
474
452
  _blockElementAt(index: number): HTMLElement | null;
475
- /**
476
- * Parallel array of live block ids (same length as `doc.body`), used
477
- * by the renderer to stamp `data-block-id` onto every block element.
478
- * Lets external tools (block tools, embedders) locate a block's DOM
479
- * element after the body is re-rendered from scratch.
480
- */
481
- private blockIdsArray;
482
453
  private checkRefs;
483
454
  private checkRange;
484
- /**
485
- * Apply a mutation to `this.doc`, update the registry, re-render, fire
486
- * change. Returns the affected refs (post-bump).
487
- */
488
- private commit;
489
- /**
490
- * Ensure `this.doc` reflects the latest edits. If the DOM has been
491
- * dirtied by user typing / paste / drop, pull the latest content out
492
- * of it and bump affected block versions. If the last mutation came
493
- * from the API, the AST is already current — skip the (lossy)
494
- * DOM-to-AST round-trip.
495
- */
496
- private ensureCurrent;
497
- private syncFromDom;
498
- /**
499
- * Re-read the DOM of each dirty editable textbox frame into the AST.
500
- * The frame element IS the serialization host (the block renderer paints
501
- * its body directly into it), so `serializeHostsToDocument([el])` yields
502
- * the same `Block[]` shape as a body host. Matched to the AST frame by
503
- * its stable `data-anchor-id`. Pure body swap — geometry/anchor untouched.
504
- *
505
- * `captureRunDefaults` promotes each paragraph's rendered base font to
506
- * `runDefaults`, so a frame's text keeps its size/family even when a
507
- * keystroke (or a select-all-retype) strips every run's inline styling —
508
- * the heading no longer collapses to the default font on the next repaint.
509
- */
510
- private syncFramesFromDom;
511
- /**
512
- * The id of the editable textbox frame the caret currently sits in, or
513
- * null when the selection is in ordinary body flow. Used to route an
514
- * `input` event to the frame read-back instead of the body read-back.
515
- */
516
- private editedFrameId;
517
- /** The editable textbox frame element the caret is inside, or null. */
518
- private focusedFrameEl;
519
- /** The (freshly-painted) frame element with this `data-anchor-id`, or null. */
520
- private frameElById;
521
- /**
522
- * Capture the live selection for an undo step. A selection inside an
523
- * editable textbox frame becomes a `FrameSelection` — the body
524
- * `Selection` model is keyed on registry blocks and can't address frame
525
- * content — so undo can restore it the same way it restores a body
526
- * selection. Everything else is an ordinary body selection.
527
- */
528
- private captureSelectionForHistory;
529
- /**
530
- * The selection BEFORE the edit that opened the current undo group,
531
- * stashed by `onBeforeInput` (which fires before the DOM mutates). Falls
532
- * back to the live selection for edits that bypass `beforeinput`
533
- * (programmatic mutations, `setDocument`).
534
- */
535
- private capturePreEditSelection;
536
- /**
537
- * Stash the pre-edit selection on the first input of a new undo group,
538
- * so undo can land the caret where the edit began. `beforeinput` fires
539
- * before the browser mutates the DOM, so the live selection here is the
540
- * pre-edit position. Only the FIRST input of a group stashes; coalesced
541
- * inputs leave the group's `before` intact (`History.onGroupSettled`
542
- * clears the stash once a step has captured).
543
- *
544
- * Scoped to textbox frames. The body already restores its caret through
545
- * the proven `applySelectionToDom` path on `stack-item-popped`; we leave
546
- * its behaviour byte-for-byte unchanged (no pre-edit stash → `before`
547
- * falls back to the post-edit selection, same as `after`). Frames had no
548
- * working restore at all, so they get the full pre-/post-edit treatment.
549
- */
550
- private onBeforeInput;
551
- /** Drop the pending pre-edit stash — a step has captured (or extended) it. */
552
- private clearPendingPreEditSelection;
553
- /**
554
- * Restore an undo step's selection. Fires on `stack-item-popped`, which
555
- * runs AFTER the change handler has already repainted the frame overlay
556
- * (`adoptYDocState` calls `emitChangeNow` synchronously), so a captured
557
- * frame selection lands on the fresh frame element and sticks — the same
558
- * lifecycle the body selection restore relies on.
559
- */
560
- private restoreCapturedSelection;
561
455
  /**
562
456
  * Toggle a mark on the caret inside an editable textbox frame, natively
563
- * (`document.execCommand`), so the body-selection mark path doesn't have
564
- * to understand frame coordinates. The resulting `<b>`/`<i>`/`<u>` tags
565
- * round-trip through the frame read-back (the inline serializer maps them
566
- * to run properties). Returns false when the caret isn't in a frame, so
567
- * 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}.
568
460
  */
569
461
  applyFrameMark(tag: string): boolean;
570
462
  /** Active state of `tag` at a frame caret (toolbar highlight), or null
571
463
  * when the caret isn't in a frame. */
572
464
  frameMarkActive(tag: string): boolean | null;
573
- /**
574
- * Schedule a DOM-driven change emit. Called from the `input` listener
575
- * when the user types — the DOM is the source of truth and we sync the
576
- * AST from it before notifying listeners.
577
- */
578
- private scheduleChange;
579
- /**
580
- * Emit a `change` event using the current in-memory AST verbatim. Do
581
- * NOT sync from DOM — callers that need a DOM sync should call it
582
- * explicitly (user-typing path does). API mutations have already
583
- * rendered their AST into the DOM and must not let the lossy DOM-read
584
- * overwrite properties the renderer doesn't surface
585
- * (column widths, verticalAlign, table properties, …).
586
- */
587
- private emitChangeNow;
588
- /**
589
- * Snapshot of the live block ids in body order — used both as the
590
- * input to `applyDocumentToYDoc` (so each Y.Map carries its stable
591
- * id) and as the `blockIdsArray()` the renderer uses to set the
592
- * `data-block-id` attribute.
593
- */
594
- private allBlockIds;
595
- /**
596
- * Mirror the current `this.doc` into the Y.Doc as a single
597
- * transaction. The diff is performed by `applyDocumentToYDoc`,
598
- * which matches blocks by id so concurrent edits to *different*
599
- * blocks merge cleanly via the Y.Array CRDT.
600
- *
601
- * Origin is `"local"` so a future Y observer can distinguish locally-
602
- * driven mutations (already rendered) from remote ones (need re-render).
603
- */
604
- private mirrorToYDoc;
605
465
  /**
606
466
  * Compose the current selection into a {@link SelectionPayload} and
607
467
  * dispatch to subscribers. Called from the document-level
@@ -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,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
+ }