diff --git a/packages/@sanity/portable-text-editor/src/editor/Editable.tsx b/packages/@sanity/portable-text-editor/src/editor/Editable.tsx index e8292fdc256..ea3b7f5801f 100644 --- a/packages/@sanity/portable-text-editor/src/editor/Editable.tsx +++ b/packages/@sanity/portable-text-editor/src/editor/Editable.tsx @@ -1,5 +1,5 @@ import {BaseRange, Transforms, Text} from 'slate' -import React, {useCallback, useMemo, useEffect, forwardRef, useState} from 'react' +import React, {useCallback, useMemo, useEffect, forwardRef, useState, KeyboardEvent} from 'react' import { Editable as SlateEditable, ReactEditor, @@ -12,7 +12,6 @@ import {PortableTextBlock} from '@sanity/types' import { EditorChange, EditorSelection, - OnBeforeInputFn, OnCopyFn, OnPasteFn, OnPasteResult, @@ -54,10 +53,10 @@ const EMPTY_DECORATORS: BaseRange[] = [] */ export type PortableTextEditableProps = Omit< React.TextareaHTMLAttributes, - 'onPaste' | 'onCopy' + 'onPaste' | 'onCopy' | 'onBeforeInput' > & { hotkeys?: HotkeyOptions - onBeforeInput?: OnBeforeInputFn + onBeforeInput?: (event: InputEvent) => void onPaste?: OnPasteFn onCopy?: OnCopyFn renderAnnotation?: RenderAnnotationFunction @@ -76,11 +75,14 @@ export type PortableTextEditableProps = Omit< * @public */ export const PortableTextEditable = forwardRef(function PortableTextEditable( - props: PortableTextEditableProps & Omit, 'as' | 'onPaste'>, + props: PortableTextEditableProps & + Omit, 'as' | 'onPaste' | 'onBeforeInput'>, forwardedRef: React.ForwardedRef, ) { const { hotkeys, + onBlur, + onFocus, onBeforeInput, onPaste, onCopy, @@ -294,29 +296,39 @@ export const PortableTextEditable = forwardRef(function PortableTextEditable( const handleOnFocus: React.FocusEventHandler = useCallback( (event) => { - const selection = PortableTextEditor.getSelection(portableTextEditor) - change$.next({type: 'focus', event}) - const newSelection = PortableTextEditor.getSelection(portableTextEditor) - // If the selection is the same, emit it explicitly here as there is no actual onChange event triggered. - if (selection === newSelection) { - change$.next({ - type: 'selection', - selection, - }) + if (onFocus) { + onFocus(event) + } + if (!event.isDefaultPrevented()) { + const selection = PortableTextEditor.getSelection(portableTextEditor) + change$.next({type: 'focus', event}) + const newSelection = PortableTextEditor.getSelection(portableTextEditor) + // If the selection is the same, emit it explicitly here as there is no actual onChange event triggered. + if (selection === newSelection) { + change$.next({ + type: 'selection', + selection, + }) + } } }, - [change$, portableTextEditor], + [change$, portableTextEditor, onFocus], ) const handleOnBlur: React.FocusEventHandler = useCallback( (event) => { - change$.next({type: 'blur', event}) + if (onBlur) { + onBlur(event) + } + if (!event.isPropagationStopped()) { + change$.next({type: 'blur', event}) + } }, - [change$], + [change$, onBlur], ) const handleOnBeforeInput = useCallback( - (event: Event) => { + (event: InputEvent) => { if (onBeforeInput) { onBeforeInput(event) } @@ -354,7 +366,17 @@ export const PortableTextEditable = forwardRef(function PortableTextEditable( } }, [handleDOMChange, ref]) - const handleKeyDown = slateEditor.pteWithHotKeys + const handleKeyDown = useCallback( + (event: KeyboardEvent) => { + if (props.onKeyDown) { + props.onKeyDown(event) + } + if (!event.isDefaultPrevented()) { + slateEditor.pteWithHotKeys(event) + } + }, + [props, slateEditor], + ) const scrollSelectionIntoViewToSlate = useMemo(() => { // Use slate-react default scroll into view diff --git a/packages/@sanity/portable-text-editor/src/types/editor.ts b/packages/@sanity/portable-text-editor/src/types/editor.ts index 25e3484d87d..52b2c9d0e6c 100644 --- a/packages/@sanity/portable-text-editor/src/types/editor.ts +++ b/packages/@sanity/portable-text-editor/src/types/editor.ts @@ -362,7 +362,7 @@ export interface PasteData { export type OnPasteFn = (data: PasteData) => OnPasteResultOrPromise /** @beta */ -export type OnBeforeInputFn = (event: Event) => void +export type OnBeforeInputFn = (event: InputEvent) => void /** @beta */ export type OnCopyFn = (