import { createEditor, type Descendant, Range, Transforms } from 'slate'; import { isKeyHotkey } from 'is-hotkey'; import { Editable, RenderElementProps, RenderLeafProps, Slate, withReact, } from 'slate-react'; import { type CustomElement, type CustomText, type LinkElement, type ParagraphElement, type TextProps, } from '@plait/common'; import React, { useMemo, useCallback, useEffect, CSSProperties } from 'react'; import { withHistory } from 'slate-history'; import { isUrl, LinkEditor } from '@plait/text-plugins'; import { withText } from './plugins/with-text'; import { CustomEditor, RenderElementPropsFor } from './custom-types'; import './styles/index.scss'; import { LinkComponent, withInlineLink } from './plugins/with-link'; export type TextComponentProps = TextProps; export const Text: React.FC = ( props: TextComponentProps ) => { const { text, readonly, onChange, onComposition, afterInit } = props; const renderLeaf = useCallback( (props: RenderLeafProps) => , [] ); const initialValue: Descendant[] = [text]; const editor = useMemo(() => { const editor = withInlineLink( withText(withHistory(withReact(createEditor()))) ); afterInit && afterInit(editor); return editor; }, []); useEffect(() => { if (text === editor.children[0]) { return; } editor.children = [text]; editor.onChange(); }, [text, editor]); const onKeyDown: React.KeyboardEventHandler = (event) => { const { selection } = editor; // Default left/right behavior is unit:'character'. // This fails to distinguish between two cursor positions, such as // foo vs foo. // Here we modify the behavior to unit:'offset'. // This lets the user step into and out of the inline without stepping over characters. // You may wish to customize this further to only use unit:'offset' in specific cases. if (selection && Range.isCollapsed(selection)) { const { nativeEvent } = event; if (isKeyHotkey('left', nativeEvent)) { event.preventDefault(); Transforms.move(editor, { unit: 'offset', reverse: true }); return; } if (isKeyHotkey('right', nativeEvent)) { event.preventDefault(); Transforms.move(editor, { unit: 'offset' }); return; } } }; return ( { onChange && onChange({ newText: editor.children[0] as ParagraphElement, operations: editor.operations, }); }} > } renderLeaf={renderLeaf} readOnly={readonly === undefined ? true : readonly} onCompositionStart={(event) => { if (onComposition) { onComposition(event as unknown as CompositionEvent); } }} onCompositionUpdate={(event) => { if (onComposition) { onComposition(event as unknown as CompositionEvent); } }} onCompositionEnd={(event) => { if (onComposition) { onComposition(event as unknown as CompositionEvent); } }} onKeyDown={onKeyDown} /> ); }; const Element = (props: RenderElementProps) => { const { attributes, children, element } = props as RenderElementPropsFor< CustomElement & { type: string } >; switch (element.type) { case 'link': return ( )} /> ); default: return ( )} /> ); } }; const ParagraphComponent = ({ attributes, children, element, }: RenderElementPropsFor) => { const style = { textAlign: element.align } as CSSProperties; return (
{children}
); }; const Leaf: React.FC = ({ children, leaf, attributes }) => { if ((leaf as CustomText).bold) { children = {children}; } if ((leaf as CustomText).code) { children = {children}; } if ((leaf as CustomText).italic) { children = {children}; } if ((leaf as CustomText).underlined) { children = {children}; } return ( {children} ); };