UNPKG

nice-ui

Version:

React design system, components, and utilities

189 lines (188 loc) 6.46 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TextEditor = void 0; const React = require("react"); const loadCodeMirror_1 = require("./loadCodeMirror"); const useAsync_1 = require("react-use/lib/useAsync"); const nano_theme_1 = require("nano-theme"); const codeColor = '#f30'; const { useEffect, useRef, useState, useMemo } = React; const blockClass = (0, nano_theme_1.rule)({ '& .CodeMirror': { ...nano_theme_1.theme.font.mono, bgc: 'transparent', }, '& .CodeMirror-gutters': { bgc: 'transparent', bdr: 0, }, '& .CodeMirror-linenumber': { pad: '1px 4px 0 4px', col: '#ccc', fz: '.7em', }, '& .CodeMirror-placeholder': { op: 0.8, }, '& .CodeMirror .cm-spell-error': { bgc: 'transparent !important', bdb: `1px solid ${nano_theme_1.theme.color.sem.negative[0]}`, }, // Highlighting. '& .CodeMirror .cm-comment': { // Single tick inline code. col: codeColor, }, '& .CodeMirror .cm-link': { col: nano_theme_1.COLOR.LINK, }, '& .CodeMirror .cm-url': { col: nano_theme_1.COLOR.LINK, }, }); const textareaClass = (0, nano_theme_1.rule)({ vis: 'hidden', w: '1px', h: '1px', }); const defaultFontSize = 15; const defaultLineHeight = 1.5; const setEditorLines = (editor, lineHeight, lines) => { const height = 10 + lines * lineHeight + 'px'; editor.setSize('100%', height); }; const adjustLineNumber = (editor, lineHeight, minLines = 1) => { const lines = Math.max(editor.getDoc().lineCount(), minLines); setEditorLines(editor, lineHeight, lines); }; const TextEditor = (props) => { const dynamicClass = (0, nano_theme_1.useRule)((theme) => ({ '& .CodeMirror': { col: theme.name === 'dark' ? '#ddd' : '000', }, '& .CodeMirror-selected': { bg: `${theme.isLight ? theme.g(0.9) : theme.g(0.8)} !important`, }, '& .CodeMirror-cursor': { borderLeftColor: theme.g(0, 0.5), }, })); const fontSize = props.fontSize || defaultFontSize; const lineHeightInPx = Math.round(fontSize * (props.lineHeight || defaultLineHeight)); const { className, minLines = 1, onFocus, onBlur, disabled } = props; const textareaRef = useRef(null); const [editor, setEditor] = useState(null); const { value: CodeMirror } = (0, useAsync_1.default)(loadCodeMirror_1.loadCodeMirror); const controls = useMemo(() => { if (!editor) return; const controls = { getValue: () => editor.getDoc().getValue(), getSelectionValue: () => { return editor.getDoc().getSelection().toString(); }, setValue: (value) => editor.getDoc().setValue(value), insert: (text, select) => { const doc = editor.getDoc(); doc.replaceSelection(text, select); }, clear: () => editor.getDoc().setValue(''), focus: () => editor.focus(), blur: () => editor.getInputField().blur(), gotoEnd: () => editor.getDoc().setCursor(editor.getDoc().lineCount(), 0), hasFocus: () => editor.hasFocus(), selectAll: () => { editor.execCommand('selectAll'); }, }; return controls; }, [editor]); useEffect(() => { if (!CodeMirror) return; if (!textareaRef.current) return; if (typeof CodeMirrorSpellChecker === 'function') { CodeMirrorSpellChecker({ codeMirrorInstance: CodeMirror, }); } const editor = CodeMirror.fromTextArea(textareaRef.current, { mode: 'spell-checker', // backdrop: 'gfm', theme: 'default', // This must be set to "textarea" if set to "contenteditable" or omitted // it does not fire change event on mobile until space is pressed on mobile. // If this setting is omitted, it defaults to "contenteditable" on mobile. inputStyle: 'textarea', placeholder: props.placeholder, lineNumbers: false, lineWrapping: true, }); if (props.showAllLines) { setEditorLines(editor, lineHeightInPx, minLines); } else { editor.setSize('100%', '100%'); } setEditor(editor); }, [textareaRef.current, CodeMirror]); useEffect(() => { if (!editor) return; if (!props.showAllLines) return; const onChange = () => { adjustLineNumber(editor, lineHeightInPx, minLines); }; adjustLineNumber(editor, lineHeightInPx, minLines); editor.on('change', onChange); return () => editor.off('change', onChange); }, [editor, props.showAllLines]); useEffect(() => { if (!editor) return; if (!controls) return; if (!props.onChange) return; const listener = () => props.onChange && props.onChange(controls); editor.on('change', listener); return () => editor.off('change', listener); }, [editor, controls, props.onChange]); useEffect(() => { if (controls && props.onControls) props.onControls(controls); }, [controls]); useEffect(() => { if (!editor) return; if (!onFocus) return; editor.on('focus', onFocus); return () => editor.off('focus', onFocus); }, [editor, onFocus]); useEffect(() => { if (!editor) return; if (!onBlur) return; editor.on('blur', onBlur); return () => editor.off('blur', onBlur); }, [editor, onBlur]); useEffect(() => { if (!editor) return; if (disabled) editor.setOption('readOnly', true); else editor.setOption('readOnly', false); }, [editor, disabled]); const style = { fontSize: fontSize + 'px', lineHeight: lineHeightInPx + 'px', }; return (React.createElement("div", { className: (className || '') + blockClass + dynamicClass, style: style }, React.createElement("textarea", { className: textareaClass, ref: textareaRef }))); }; exports.TextEditor = TextEditor;