Skip to content

Commit 95718cf

Browse files
committed
fix: document-api ref-based insert target resolution
1 parent 71b72f4 commit 95718cf

1 file changed

Lines changed: 16 additions & 10 deletions

File tree

packages/super-editor/src/document-api-adapters/plan-engine/plan-wrappers.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -590,21 +590,23 @@ export function selectionMutationWrapper(
590590
// document state without mutating anything.
591591
const compiled = compilePlan(editor, [step]);
592592

593-
// Insert requires a collapsed (point) target inside a textblock.
594-
// Reject spans, non-collapsed ranges, and positions that resolve to
595-
// block boundaries (e.g., refs to images/tables) so executeTextInsert()
596-
// never attempts tr.insert() outside a textblock.
593+
// Insert validation: reject multi-segment spans and non-textblock targets.
594+
// Single-block range refs (absFrom < absTo) are valid — executeTextInsert()
595+
// inserts at position: 'before' (absFrom), so the range width is irrelevant.
597596
if (request.kind === 'insert') {
598597
const compiledStep = compiled.mutationSteps.find((s) => s.step.id === stepId);
599598
const target = compiledStep?.targets[0];
600599
if (target) {
601-
const isInvalidInsertTarget = target.kind === 'span' || target.absFrom !== target.absTo;
602-
if (isInvalidInsertTarget) {
600+
// Multi-segment refs (span) are never valid for point inserts.
601+
if (target.kind === 'span') {
603602
const resolution = buildSelectionResolutionFromCompiled(compiled, stepId);
604603
return {
605604
success: false,
606605
resolution,
607-
failure: { code: 'INVALID_TARGET', message: 'Insert operations require a collapsed target range.' },
606+
failure: {
607+
code: 'INVALID_TARGET',
608+
message: 'Insert operations require a single-block target, not a multi-segment span.',
609+
},
608610
};
609611
}
610612

@@ -893,7 +895,9 @@ function insertStructuredInner(editor: Editor, input: InsertInput, options?: Mut
893895
{ ref },
894896
);
895897
}
896-
resolvedRange = { from: compiledTarget.absFrom, to: compiledTarget.absTo };
898+
// Collapse to the start position — refs resolve to non-collapsed ranges
899+
// but insert semantics is "insert before", not "replace range".
900+
resolvedRange = { from: compiledTarget.absFrom, to: compiledTarget.absFrom };
897901
const resolution = buildSelectionResolutionFromCompiled(compiled, dummyStepId);
898902
effectiveTarget = resolution.target;
899903
} else {
@@ -923,8 +927,10 @@ function insertStructuredInner(editor: Editor, input: InsertInput, options?: Mut
923927

924928
const { from, to } = resolvedRange;
925929

926-
// Insert semantics are point-only for doc.insert, regardless of content type.
927-
if (from !== to) {
930+
// Explicit targets with a non-collapsed range indicate a text selection —
931+
// that's a replace operation, not an insert. Refs are already collapsed
932+
// to their start position in the ref branch above.
933+
if (target && from !== to) {
928934
return {
929935
success: false,
930936
resolution,

0 commit comments

Comments
 (0)