@sofereditor/core 0.5.0 → 0.7.0

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/index.d.cts CHANGED
@@ -600,6 +600,20 @@ interface ClipboardSlice {
600
600
  openStart: boolean;
601
601
  /** último bloco é fragmento parcial → conteúdo após o caret continua nele. */
602
602
  openEnd: boolean;
603
+ /**
604
+ * Marca explícita: quando presente (`true`) e `blocks.length === 1`, esse
605
+ * único bloco é uma unidade INTEIRA (ex.: um `<h1>`/`<li>`/`<blockquote>`
606
+ * inteiro colado de fora) — `insertSlice` deve adotar o `type`/`attrs` dele
607
+ * em vez de tratá-lo como fragmento de uma seleção.
608
+ *
609
+ * `serializeSelection` NUNCA define este campo — uma cópia interna é sempre
610
+ * um fragmento de seleção, mesmo quando por coincidência cobre o bloco
611
+ * inteiro (nesse caso `openStart`/`openEnd` também saem `false`, mas isso
612
+ * não significa "bloco externo inteiro": significa apenas que a seleção
613
+ * tocou as bordas do bloco). Só produtores de blocos externos inteiros
614
+ * (hoje: `htmlToSlice`) devem marcar `blockLevel: true`.
615
+ */
616
+ blockLevel?: true;
603
617
  }
604
618
  /**
605
619
  * Lê a seleção e produz um ClipboardSlice, ou null se:
@@ -634,6 +648,22 @@ declare function insertText(ctx: CommandContext, text: string, marks?: Partial<R
634
648
  declare function insertParagraph(ctx: CommandContext): void;
635
649
  declare function deleteBackward(ctx: CommandContext): void;
636
650
  declare function deleteForward(ctx: CommandContext): void;
651
+ /**
652
+ * `true` quando o caractere imediatamente ANTES (`"backward"`) ou DEPOIS
653
+ * (`"forward"`) da posição atual do caret é um embed de imagem. Só considera
654
+ * seleção COLAPSADA — o caso de um embed inteiro SELECIONADO (range de 1
655
+ * caractere) é responsabilidade de `getSelectedEmbed` no chamador.
656
+ *
657
+ * Existe para `Editor.tsx` decidir se Backspace/Delete precisam ser tratados
658
+ * no `onKeyDown` em vez de esperar `beforeinput`: o `<figure>` de um embed é
659
+ * `contenteditable=false`, e quando a seleção do DOM pousa nele (o que
660
+ * acontece depois de inserir ou clicar numa imagem) o navegador NUNCA
661
+ * dispara `beforeinput` — o caminho normal de delete fica mudo. Resolvido
662
+ * consultando o MODELO em vez de inspecionar a seleção do DOM (que pode
663
+ * pousar de formas diferentes por navegador): determinístico e testável sem
664
+ * montar um editor real.
665
+ */
666
+ declare function isEmbedAdjacentToCaret(ctx: CommandContext, direction: "backward" | "forward"): boolean;
637
667
  /**
638
668
  * Toggle a mark across the current selection.
639
669
  * - If the selection is collapsed: no-op (the UI layer handles pendingMarks).
@@ -678,7 +708,18 @@ declare function setCellAttr<K extends keyof CellAttrs>(ctx: CommandContext, key
678
708
  declare function deleteSelection(ctx: CommandContext): void;
679
709
  /**
680
710
  * Replace the current selection with a clipboard slice (A1).
681
- * - Single-block slice → inline splice (Dhead + S0 + Dtail), target keeps its type.
711
+ * - Single-block slice WITHOUT `blockLevel` → always an inline splice
712
+ * (Dhead + S0 + Dtail), target keeps its type. This is the internal
713
+ * copy/paste shape, including a full-block internal copy (selecting an
714
+ * entire line and copying it also yields `openStart:false, openEnd:false`
715
+ * with one block — that is NOT the same thing as an external whole block;
716
+ * `openStart`/`openEnd` do not carry that distinction, so branching on them
717
+ * here regressed plain in-editor copy/paste. See `ClipboardSlice.blockLevel`.
718
+ * - Single-block slice WITH `blockLevel: true` (e.g. one <h1>/<li>/<blockquote>
719
+ * from an HTML paste via `htmlToSlice`) → adopts S0's type/attrs instead,
720
+ * same rules as the multi-block case below (reuse target in place when the
721
+ * caret is at offset 0, otherwise insert S0 as its own block; any tail
722
+ * becomes a separate block with the target's original type).
682
723
  * - Multi-block slice → openStart merges S0 inline; middle/last blocks inserted
683
724
  * discretely; openEnd appends the post-caret tail onto the last pasted block.
684
725
  * - Target inside a table cell → slice flattened to inline (no block structure).
@@ -891,4 +932,4 @@ declare class EditorHistory {
891
932
  destroy(): void;
892
933
  }
893
934
 
894
- export { ANSWER_LINE_COLOR, ANSWER_LINE_MAX, type AlignValue, type AnswerLineSpacing, BLANK_MIN_RUN, BLANK_STYLE, BLOCK_SPECS, type BlockAttrs, type BlockType, CLEAR_ALL_MARKS, COMMAND_ORIGIN, type CellAttrs, type CellBorderColors, type CellBorderPos, type ClipboardSlice, type CommandContext, type CommentAttr, DEFAULT_PAGE_SETTINGS, type DeltaOp, type DirValue, EditorDocument, EditorHistory, type EmbedLoc, type EncodedCursor, type EncodedCursorPoint, type ImageEmbed, type ImageLayout, type InsertContent, type LegacySerializedDocument, type LinkAttr, type ListKind, type ListStyleType, MARGIN_PRESETS, MAX_LIST_LEVEL, type MarkAttrs, type MarkName, type MarkValue, PAGE_PRESETS, type PagePreset, type PageSettings, type Position, SOFER_MIME, type Selection, type SerializedBlock, type SerializedCell, type SerializedDocument, type StyleRecord, TABLE_BORDER_COLOR, TABLE_GUIDE_COLOR, TRACKED_ORIGINS, type TableBorderPreset, type TableLocation, type TableRect, answerLineStyle, cellBorderColors, cellBorderStyle, cellsInRect, clearRectCells, collapsedSelection, comparePositions, createBlock, createCell, createTableBlock, decodeSelection, dedentList, defaultAttrsFor, deleteBackward, deleteForward, deleteSelection, deleteTable, deleteTableColumn, deleteTableRow, deltaLength, detectPreset, encodeSelection, expandTableRect, getBlockId, getMarksInRange, indentList, insertAnswerLines, insertImage, insertParagraph, insertSlice, insertTable, insertTableColumn, insertTableRow, insertText, isCollapsed, isCommandOrigin, isImageEmbed, isKnownBlockType, isLegacySerializedDocument, isMarkUniformInRange, mergeDown, mergeRight, mergeSelection, mmToPx, moveEmbedAnchor, moveToNextCell, moveToPrevCell, newBlockId, orderedRange, positionsEqual, pxToMm, removeMark, sameTextRun, serializeSelection, setBlockAttr, setBlockAttrAtIndex, setBlockType, setCellAttr, setColumnWidth, setImageAttrs, setMark, sliceDelta, sliceToInlineDelta, sliceToText, spanOf, splitCell, splitListItem, splitUnderscoreRuns, styleToCssText, tableLocationOf, tableRectSelection, toggleList, toggleMark, withDefaults };
935
+ export { ANSWER_LINE_COLOR, ANSWER_LINE_MAX, type AlignValue, type AnswerLineSpacing, BLANK_MIN_RUN, BLANK_STYLE, BLOCK_SPECS, type BlockAttrs, type BlockType, CLEAR_ALL_MARKS, COMMAND_ORIGIN, type CellAttrs, type CellBorderColors, type CellBorderPos, type ClipboardSlice, type CommandContext, type CommentAttr, DEFAULT_PAGE_SETTINGS, type DeltaOp, type DirValue, EditorDocument, EditorHistory, type EmbedLoc, type EncodedCursor, type EncodedCursorPoint, type ImageEmbed, type ImageLayout, type InsertContent, type LegacySerializedDocument, type LinkAttr, type ListKind, type ListStyleType, MARGIN_PRESETS, MAX_LIST_LEVEL, type MarkAttrs, type MarkName, type MarkValue, PAGE_PRESETS, type PagePreset, type PageSettings, type Position, SOFER_MIME, type Selection, type SerializedBlock, type SerializedCell, type SerializedDocument, type StyleRecord, TABLE_BORDER_COLOR, TABLE_GUIDE_COLOR, TRACKED_ORIGINS, type TableBorderPreset, type TableLocation, type TableRect, answerLineStyle, cellBorderColors, cellBorderStyle, cellsInRect, clearRectCells, collapsedSelection, comparePositions, createBlock, createCell, createTableBlock, decodeSelection, dedentList, defaultAttrsFor, deleteBackward, deleteForward, deleteSelection, deleteTable, deleteTableColumn, deleteTableRow, deltaLength, detectPreset, encodeSelection, expandTableRect, getBlockId, getMarksInRange, indentList, insertAnswerLines, insertImage, insertParagraph, insertSlice, insertTable, insertTableColumn, insertTableRow, insertText, isCollapsed, isCommandOrigin, isEmbedAdjacentToCaret, isImageEmbed, isKnownBlockType, isLegacySerializedDocument, isMarkUniformInRange, mergeDown, mergeRight, mergeSelection, mmToPx, moveEmbedAnchor, moveToNextCell, moveToPrevCell, newBlockId, orderedRange, positionsEqual, pxToMm, removeMark, sameTextRun, serializeSelection, setBlockAttr, setBlockAttrAtIndex, setBlockType, setCellAttr, setColumnWidth, setImageAttrs, setMark, sliceDelta, sliceToInlineDelta, sliceToText, spanOf, splitCell, splitListItem, splitUnderscoreRuns, styleToCssText, tableLocationOf, tableRectSelection, toggleList, toggleMark, withDefaults };
package/dist/index.d.ts CHANGED
@@ -600,6 +600,20 @@ interface ClipboardSlice {
600
600
  openStart: boolean;
601
601
  /** último bloco é fragmento parcial → conteúdo após o caret continua nele. */
602
602
  openEnd: boolean;
603
+ /**
604
+ * Marca explícita: quando presente (`true`) e `blocks.length === 1`, esse
605
+ * único bloco é uma unidade INTEIRA (ex.: um `<h1>`/`<li>`/`<blockquote>`
606
+ * inteiro colado de fora) — `insertSlice` deve adotar o `type`/`attrs` dele
607
+ * em vez de tratá-lo como fragmento de uma seleção.
608
+ *
609
+ * `serializeSelection` NUNCA define este campo — uma cópia interna é sempre
610
+ * um fragmento de seleção, mesmo quando por coincidência cobre o bloco
611
+ * inteiro (nesse caso `openStart`/`openEnd` também saem `false`, mas isso
612
+ * não significa "bloco externo inteiro": significa apenas que a seleção
613
+ * tocou as bordas do bloco). Só produtores de blocos externos inteiros
614
+ * (hoje: `htmlToSlice`) devem marcar `blockLevel: true`.
615
+ */
616
+ blockLevel?: true;
603
617
  }
604
618
  /**
605
619
  * Lê a seleção e produz um ClipboardSlice, ou null se:
@@ -634,6 +648,22 @@ declare function insertText(ctx: CommandContext, text: string, marks?: Partial<R
634
648
  declare function insertParagraph(ctx: CommandContext): void;
635
649
  declare function deleteBackward(ctx: CommandContext): void;
636
650
  declare function deleteForward(ctx: CommandContext): void;
651
+ /**
652
+ * `true` quando o caractere imediatamente ANTES (`"backward"`) ou DEPOIS
653
+ * (`"forward"`) da posição atual do caret é um embed de imagem. Só considera
654
+ * seleção COLAPSADA — o caso de um embed inteiro SELECIONADO (range de 1
655
+ * caractere) é responsabilidade de `getSelectedEmbed` no chamador.
656
+ *
657
+ * Existe para `Editor.tsx` decidir se Backspace/Delete precisam ser tratados
658
+ * no `onKeyDown` em vez de esperar `beforeinput`: o `<figure>` de um embed é
659
+ * `contenteditable=false`, e quando a seleção do DOM pousa nele (o que
660
+ * acontece depois de inserir ou clicar numa imagem) o navegador NUNCA
661
+ * dispara `beforeinput` — o caminho normal de delete fica mudo. Resolvido
662
+ * consultando o MODELO em vez de inspecionar a seleção do DOM (que pode
663
+ * pousar de formas diferentes por navegador): determinístico e testável sem
664
+ * montar um editor real.
665
+ */
666
+ declare function isEmbedAdjacentToCaret(ctx: CommandContext, direction: "backward" | "forward"): boolean;
637
667
  /**
638
668
  * Toggle a mark across the current selection.
639
669
  * - If the selection is collapsed: no-op (the UI layer handles pendingMarks).
@@ -678,7 +708,18 @@ declare function setCellAttr<K extends keyof CellAttrs>(ctx: CommandContext, key
678
708
  declare function deleteSelection(ctx: CommandContext): void;
679
709
  /**
680
710
  * Replace the current selection with a clipboard slice (A1).
681
- * - Single-block slice → inline splice (Dhead + S0 + Dtail), target keeps its type.
711
+ * - Single-block slice WITHOUT `blockLevel` → always an inline splice
712
+ * (Dhead + S0 + Dtail), target keeps its type. This is the internal
713
+ * copy/paste shape, including a full-block internal copy (selecting an
714
+ * entire line and copying it also yields `openStart:false, openEnd:false`
715
+ * with one block — that is NOT the same thing as an external whole block;
716
+ * `openStart`/`openEnd` do not carry that distinction, so branching on them
717
+ * here regressed plain in-editor copy/paste. See `ClipboardSlice.blockLevel`.
718
+ * - Single-block slice WITH `blockLevel: true` (e.g. one <h1>/<li>/<blockquote>
719
+ * from an HTML paste via `htmlToSlice`) → adopts S0's type/attrs instead,
720
+ * same rules as the multi-block case below (reuse target in place when the
721
+ * caret is at offset 0, otherwise insert S0 as its own block; any tail
722
+ * becomes a separate block with the target's original type).
682
723
  * - Multi-block slice → openStart merges S0 inline; middle/last blocks inserted
683
724
  * discretely; openEnd appends the post-caret tail onto the last pasted block.
684
725
  * - Target inside a table cell → slice flattened to inline (no block structure).
@@ -891,4 +932,4 @@ declare class EditorHistory {
891
932
  destroy(): void;
892
933
  }
893
934
 
894
- export { ANSWER_LINE_COLOR, ANSWER_LINE_MAX, type AlignValue, type AnswerLineSpacing, BLANK_MIN_RUN, BLANK_STYLE, BLOCK_SPECS, type BlockAttrs, type BlockType, CLEAR_ALL_MARKS, COMMAND_ORIGIN, type CellAttrs, type CellBorderColors, type CellBorderPos, type ClipboardSlice, type CommandContext, type CommentAttr, DEFAULT_PAGE_SETTINGS, type DeltaOp, type DirValue, EditorDocument, EditorHistory, type EmbedLoc, type EncodedCursor, type EncodedCursorPoint, type ImageEmbed, type ImageLayout, type InsertContent, type LegacySerializedDocument, type LinkAttr, type ListKind, type ListStyleType, MARGIN_PRESETS, MAX_LIST_LEVEL, type MarkAttrs, type MarkName, type MarkValue, PAGE_PRESETS, type PagePreset, type PageSettings, type Position, SOFER_MIME, type Selection, type SerializedBlock, type SerializedCell, type SerializedDocument, type StyleRecord, TABLE_BORDER_COLOR, TABLE_GUIDE_COLOR, TRACKED_ORIGINS, type TableBorderPreset, type TableLocation, type TableRect, answerLineStyle, cellBorderColors, cellBorderStyle, cellsInRect, clearRectCells, collapsedSelection, comparePositions, createBlock, createCell, createTableBlock, decodeSelection, dedentList, defaultAttrsFor, deleteBackward, deleteForward, deleteSelection, deleteTable, deleteTableColumn, deleteTableRow, deltaLength, detectPreset, encodeSelection, expandTableRect, getBlockId, getMarksInRange, indentList, insertAnswerLines, insertImage, insertParagraph, insertSlice, insertTable, insertTableColumn, insertTableRow, insertText, isCollapsed, isCommandOrigin, isImageEmbed, isKnownBlockType, isLegacySerializedDocument, isMarkUniformInRange, mergeDown, mergeRight, mergeSelection, mmToPx, moveEmbedAnchor, moveToNextCell, moveToPrevCell, newBlockId, orderedRange, positionsEqual, pxToMm, removeMark, sameTextRun, serializeSelection, setBlockAttr, setBlockAttrAtIndex, setBlockType, setCellAttr, setColumnWidth, setImageAttrs, setMark, sliceDelta, sliceToInlineDelta, sliceToText, spanOf, splitCell, splitListItem, splitUnderscoreRuns, styleToCssText, tableLocationOf, tableRectSelection, toggleList, toggleMark, withDefaults };
935
+ export { ANSWER_LINE_COLOR, ANSWER_LINE_MAX, type AlignValue, type AnswerLineSpacing, BLANK_MIN_RUN, BLANK_STYLE, BLOCK_SPECS, type BlockAttrs, type BlockType, CLEAR_ALL_MARKS, COMMAND_ORIGIN, type CellAttrs, type CellBorderColors, type CellBorderPos, type ClipboardSlice, type CommandContext, type CommentAttr, DEFAULT_PAGE_SETTINGS, type DeltaOp, type DirValue, EditorDocument, EditorHistory, type EmbedLoc, type EncodedCursor, type EncodedCursorPoint, type ImageEmbed, type ImageLayout, type InsertContent, type LegacySerializedDocument, type LinkAttr, type ListKind, type ListStyleType, MARGIN_PRESETS, MAX_LIST_LEVEL, type MarkAttrs, type MarkName, type MarkValue, PAGE_PRESETS, type PagePreset, type PageSettings, type Position, SOFER_MIME, type Selection, type SerializedBlock, type SerializedCell, type SerializedDocument, type StyleRecord, TABLE_BORDER_COLOR, TABLE_GUIDE_COLOR, TRACKED_ORIGINS, type TableBorderPreset, type TableLocation, type TableRect, answerLineStyle, cellBorderColors, cellBorderStyle, cellsInRect, clearRectCells, collapsedSelection, comparePositions, createBlock, createCell, createTableBlock, decodeSelection, dedentList, defaultAttrsFor, deleteBackward, deleteForward, deleteSelection, deleteTable, deleteTableColumn, deleteTableRow, deltaLength, detectPreset, encodeSelection, expandTableRect, getBlockId, getMarksInRange, indentList, insertAnswerLines, insertImage, insertParagraph, insertSlice, insertTable, insertTableColumn, insertTableRow, insertText, isCollapsed, isCommandOrigin, isEmbedAdjacentToCaret, isImageEmbed, isKnownBlockType, isLegacySerializedDocument, isMarkUniformInRange, mergeDown, mergeRight, mergeSelection, mmToPx, moveEmbedAnchor, moveToNextCell, moveToPrevCell, newBlockId, orderedRange, positionsEqual, pxToMm, removeMark, sameTextRun, serializeSelection, setBlockAttr, setBlockAttrAtIndex, setBlockType, setCellAttr, setColumnWidth, setImageAttrs, setMark, sliceDelta, sliceToInlineDelta, sliceToText, spanOf, splitCell, splitListItem, splitUnderscoreRuns, styleToCssText, tableLocationOf, tableRectSelection, toggleList, toggleMark, withDefaults };
package/dist/index.js CHANGED
@@ -1044,6 +1044,24 @@ function deleteForward(ctx) {
1044
1044
  }
1045
1045
  });
1046
1046
  }
1047
+ function isEmbedAdjacentToCaret(ctx, direction) {
1048
+ const sel = ctx.getSelection();
1049
+ if (!isCollapsed(sel)) return false;
1050
+ const pos = sel.focus;
1051
+ const yText = ctx.doc.textAt(pos.blockIndex, pos.cellIndex);
1052
+ if (!yText) return false;
1053
+ const delta = yText.toDelta();
1054
+ let cursor = 0;
1055
+ for (const op of delta) {
1056
+ const len = typeof op.insert === "string" ? op.insert.length : 1;
1057
+ const opStart = cursor;
1058
+ const opEnd = cursor + len;
1059
+ cursor = opEnd;
1060
+ const matches = direction === "backward" ? opEnd === pos.offset : opStart === pos.offset;
1061
+ if (matches) return isImageEmbed(op.insert);
1062
+ }
1063
+ return false;
1064
+ }
1047
1065
  var booleanMarks = /* @__PURE__ */ new Set(["bold", "italic", "underline", "strike"]);
1048
1066
  function toggleMark(ctx, name, value) {
1049
1067
  const sel = ctx.getSelection();
@@ -1190,6 +1208,34 @@ function insertSlice(ctx, slice) {
1190
1208
  const tailDelta = sliceDelta(full, offset, deltaLength(full));
1191
1209
  targetText.delete(offset, targetText.length - offset);
1192
1210
  const blocks = slice.blocks;
1211
+ if (blocks.length === 1 && slice.blockLevel) {
1212
+ const originalType2 = ctx.doc.getBlockType(blockIndex);
1213
+ const originalAttrs2 = ctx.doc.getBlockAttrs(blockIndex);
1214
+ let hostIndex = blockIndex;
1215
+ if (offset === 0) {
1216
+ const blk = ctx.doc.blocks.get(blockIndex);
1217
+ blk.set("type", blocks[0].type);
1218
+ const am = blk.get("attrs");
1219
+ am.clear();
1220
+ for (const [k, v] of Object.entries(blocks[0].attrs)) {
1221
+ if (v !== void 0) am.set(k, v);
1222
+ }
1223
+ writeDeltaInto(targetText, 0, blocks[0].delta);
1224
+ } else {
1225
+ hostIndex = blockIndex + 1;
1226
+ const b = createBlock(blocks[0].type, "", blocks[0].attrs);
1227
+ ctx.doc.blocks.insert(hostIndex, [b]);
1228
+ writeDeltaInto(b.get("text"), 0, blocks[0].delta);
1229
+ }
1230
+ const caretPos = ctx.doc.getBlockText(hostIndex).length;
1231
+ if (tailDelta.length > 0) {
1232
+ const tb = createBlock(originalType2 ?? "paragraph", "", originalAttrs2);
1233
+ ctx.doc.blocks.insert(hostIndex + 1, [tb]);
1234
+ writeDeltaInto(tb.get("text"), 0, tailDelta);
1235
+ }
1236
+ ctx.setSelection(collapsedSelection({ blockIndex: hostIndex, offset: caretPos }));
1237
+ return;
1238
+ }
1193
1239
  if (blocks.length === 1) {
1194
1240
  const afterS0 = writeDeltaInto(targetText, offset, blocks[0].delta);
1195
1241
  writeDeltaInto(targetText, afterS0, tailDelta);
@@ -2221,6 +2267,7 @@ export {
2221
2267
  insertText,
2222
2268
  isCollapsed,
2223
2269
  isCommandOrigin,
2270
+ isEmbedAdjacentToCaret,
2224
2271
  isImageEmbed,
2225
2272
  isKnownBlockType,
2226
2273
  isLegacySerializedDocument,