Skip to content

Commit 998561a

Browse files
committed
fix(ui): honor the focus() contract + fix dangling docs reference (SD-3312)
focus now fails with not-ready when setTextSelection is unavailable and not-reachable when it doesn't place the caret, so { success: true } means the caret was actually placed (was reported regardless via optional chaining). Docs: introduce the smartField tag convention inline instead of referencing a "convention above" that wasn't introduced earlier on the page.
1 parent f8f4fdc commit 998561a

2 files changed

Lines changed: 12 additions & 3 deletions

File tree

apps/docs/editor/custom-ui/content-controls.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ The event tells you *what* is active; `getRect` tells you *where* to draw. `acti
5353

5454
## How the model works
5555

56-
You build your UI *over* the control, not inside it. SuperDoc owns how the control's content is painted in the document; you turn off its built-in chrome and draw your own (chips, badges, panels) anchored with `getRect`, react with the events, and change content through `editor.doc.contentControls.*`. Custom field types are expressed as a `tag` (the `{ kind: 'smartField', key }` convention above), interpreted by your own UI - the underlying control stays a standard Word SDT so it round-trips to `.docx`.
56+
You build your UI *over* the control, not inside it. SuperDoc owns how the control's content is painted in the document; you turn off its built-in chrome and draw your own (chips, badges, panels) anchored with `getRect`, react with the events, and change content through `editor.doc.contentControls.*`. Custom field types are expressed as a `tag` - for example `{ kind: 'smartField', key: 'party_name' }`, interpreted by your own UI - the underlying control stays a standard Word SDT so it round-trips to `.docx`.
5757

5858
## See also
5959

packages/super-editor/src/editors/v1/core/presentation-editor/PresentationEditor.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3924,6 +3924,12 @@ export class PresentationEditor extends EventEmitter {
39243924
const pos = this.#resolveContentControlCaretPos(entityId);
39253925
if (pos == null) return { success: false, reason: 'not-found' };
39263926

3927+
// Without setTextSelection the editor can't place the caret, so focus
3928+
// can't honor its "caret placed" contract — fail before scrolling.
3929+
if (typeof editor.commands?.setTextSelection !== 'function') {
3930+
return { success: false, reason: 'not-ready' };
3931+
}
3932+
39273933
// Scroll first and honor the result. A focus that can't bring the control
39283934
// into view must not report success (it would leave a caret on a page that
39293935
// never mounted) — matches #scrollToBlockCandidate. Model-aware: mounts a
@@ -3934,10 +3940,13 @@ export class PresentationEditor extends EventEmitter {
39343940
});
39353941
if (!scrolled) return { success: false, reason: 'not-reachable' };
39363942

3937-
// Then place the caret inside the control. setTextSelection clamps and
3943+
// Place the caret inside the control and honor the result — report success
3944+
// only if the selection was actually placed. setTextSelection clamps and
39383945
// focuses the (hidden) editor view with preventScroll, so keyboard input
39393946
// goes to the control without re-jumping the viewport.
3940-
editor.commands?.setTextSelection?.({ from: pos, to: pos });
3947+
if (!editor.commands.setTextSelection({ from: pos, to: pos })) {
3948+
return { success: false, reason: 'not-reachable' };
3949+
}
39413950
return { success: true };
39423951
}
39433952

0 commit comments

Comments
 (0)