All files / roosterjs-content-model-plugins/lib/edit keyboardInput.ts

96.77% Statements 30/31
90.91% Branches 20/22
80% Functions 4/5
96.55% Lines 28/29

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 821x                     1x 1x 1x 1x 1x   1x 1x   1x   1x               1x 11x   11x 7x   7x   7x     7x   7x   6x   6x     6x   1x                       7x         11x 1x 10x         8x   2x      
import {
    ChangeSource,
    createText,
    deleteSelection,
    isModifierKey,
    normalizeContentModel,
} from 'roosterjs-content-model-dom';
import type { DeleteSelectionStep, DOMSelection, IEditor } from 'roosterjs-content-model-types';
 
// Insert a ZeroWidthSpace(ZWS) segment with selection before selection marker
// so that later browser will replace this selection with inputted text and keep format
const ZWS = '\u200B';
const insertZWS: DeleteSelectionStep = context => {
    Eif (context.deleteResult == 'range') {
        const { marker, paragraph } = context.insertPoint;
        const index = paragraph.segments.indexOf(marker);
 
        Eif (index >= 0) {
            const text = createText(ZWS, marker.format, marker.link, marker.code);
 
            text.isSelected = true;
 
            paragraph.segments.splice(index, 0, text);
        }
    }
};
 
/**
 * @internal
 */
export function keyboardInput(editor: IEditor, rawEvent: KeyboardEvent) {
    const selection = editor.getDOMSelection();
 
    if (shouldInputWithContentModel(selection, rawEvent)) {
        editor.takeSnapshot();
 
        editor.formatContentModel(
            (model, context) => {
                const result = deleteSelection(model, [insertZWS], context);
 
                // Skip undo snapshot here and add undo snapshot before the operation so that we don't add another undo snapshot in middle of this replace operation
                context.skipUndoSnapshot = true;
 
                if (result.deleteResult == 'range') {
                    // We have deleted something, next input should inherit the segment format from deleted content, so set pending format here
                    context.newPendingFormat = result.insertPoint?.marker.format;
 
                    normalizeContentModel(model);
 
                    // Do not preventDefault since we still want browser to handle the final input for now
                    return true;
                } else {
                    return false;
                }
            },
            {
                scrollCaretIntoView: true,
                rawEvent,
                changeSource: ChangeSource.Keyboard,
                getChangeData: () => rawEvent.which,
                apiName: 'handleInputKey',
            }
        );
 
        return true;
    }
}
 
function shouldInputWithContentModel(selection: DOMSelection | null, rawEvent: KeyboardEvent) {
    if (!selection) {
        return false; // Nothing to delete
    } else if (
        !isModifierKey(rawEvent) &&
        rawEvent.key &&
        (rawEvent.key == 'Space' || rawEvent.key.length == 1)
    ) {
        return selection.type != 'range' || !selection.range.collapsed;
    } else {
        return false;
    }
}