react contentEditable 可輸入div 高亮關(guān)鍵字

/*
 * 開發(fā)調(diào)試
 */
import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import styles from './style.module.less';

const keyword = ['const'];
const replaceBlank = (html) => html.replace(/ /ig, ' ');

// 獲取光標位置
function getCaretCharacterOffsetWithin(element) {
  let caretOffset = 0;
  const doc = element.ownerDocument || element.document;
  const win = doc.defaultView || doc.parentWindow;
  let sel;
  if (typeof win.getSelection !== 'undefined') {
    sel = win.getSelection();
    if (sel.rangeCount > 0) {
      const range = win.getSelection().getRangeAt(0);
      const preCaretRange = range.cloneRange();
      preCaretRange.selectNodeContents(element);
      preCaretRange.setEnd(range.endContainer, range.endOffset);
      caretOffset = preCaretRange.toString().length;
    }
  } else if ((sel = doc.selection) && sel.type != 'Control') {
    const textRange = sel.createRange();
    const preCaretTextRange = doc.body.createTextRange();
    preCaretTextRange.moveToElementText(element);
    preCaretTextRange.setEndPoint('EndToEnd', textRange);
    caretOffset = preCaretTextRange.text.length;
  }
  return caretOffset;
}

// 設(shè)置光標位置
function setCaretPosition(element, offset) {
  const range = document.createRange();
  const sel = window.getSelection();

  // select appropriate node
  let currentNode = null;
  let previousNode = null;

  for (let i = 0; i < element.childNodes.length; i++) {
    // save previous node
    previousNode = currentNode;

    // get current node
    currentNode = element.childNodes[i];
    // if we get span or something else then we should get child node
    while (currentNode.childNodes.length > 0) {
      [currentNode] = currentNode.childNodes;
    }

    // calc offset in current node
    if (previousNode != null) {
      offset -= previousNode.length;
    }
    // check whether current node has enough length
    if (offset <= currentNode.length) {
      break;
    }
  }
  // move caret to specified offset
  if (currentNode != null) {
    range.setStart(currentNode, offset);
    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
  }
}

// 排序方法
function compareWordLength(a, b) {
  if (a.length > b.length) {
    return -1;
  } if (a.length < b.length) {
    return 1;
  }
  return 0;
}

// 高亮關(guān)鍵字
function addKeyWordHighline(oText, keyWords) {
  let returnVal = oText;
  let i = 0;
  let wordReg;
  keyWords.sort(compareWordLength);

  for (i = 0; i < keyWords.length; i++) {
    if (keyWords[i] !== '') {
      wordReg = new RegExp(`(?!<span+>.[^<]*)${keyWords[i]}(?!.[^<]*<\/span>)`, 'g');
      returnVal = returnVal.replace(wordReg, `<span>${keyWords[i]}</span>`);
    }
  }
  return returnVal;
}

const CodeInput = ({
  value,
  disable,
  children,
  onChange,
}) => {
  const ref = useRef();
  // 是否鎖定輸入
  const isLock = useRef(false);

  const getRef = () => ref && ref.current;

  const onCompositionstart = (e) => {
    isLock.current = true;
  };
  const onCompositionend = (e) => {
    isLock.current = false;
  };

  // 解決中文輸入的時候,直接輸出英文字母的問題(中文輸入期間,不允許輸入字符)
  useEffect(() => {
    // 監(jiān)聽中文輸入
    const el = getRef();
    el.addEventListener('compositionstart', onCompositionstart, false);
    el.addEventListener('compositionend', onCompositionend, false);
    return () => {
      el.removeEventListener('compositionstart', onCompositionstart, false);
      el.removeEventListener('compositionend', onCompositionend, false);
    };
  }, []);

  const onInput = () => {
    const el = getRef();
    // dom是否為空 || 是否為鎖定模式
    if (!el || isLock.current) return;
    // 獲取內(nèi)容
    let text = el.innerHTML;
    // 是否修改了
    if (value !== text) {
      // 獲取光標
      const position = getCaretCharacterOffsetWithin(el);
      // 替換空格
      text = replaceBlank(text);
      // 替換關(guān)鍵字
      text = addKeyWordHighline(text, keyword);
      el.innerHTML = text;
      // 更新父組件
      onChange(text);
      // 恢復(fù)位置
      setCaretPosition(el, position);
    }
  };

  return (
    <pre
      ref={ref}
      className={styles['formula-input']}
      contentEditable={!disable}
      dangerouslySetInnerHTML={{ __html: value }}
      onInput={onInput}
    >

      {children}
    </pre>
  );
};

CodeInput.propTypes = {
  value: PropTypes.array,
  disable: PropTypes.number,
  onChange: PropTypes.func,
  children: PropTypes.node,
};

CodeInput.defaultProps = {
  value: '默認',   // value
  disable: false,   // 是否可用
  children: undefined, // 子元素
  onChange: () => {},
};

export default CodeInput;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容