UNPKG

react-text-editor-kit

Version:

React Text Editor Kit is a customizable rich text editor component for React applications. It provides a flexible and easy-to-use interface for users to create and edit content with various formatting options. And also easy to integrate in react applicati

219 lines (194 loc) 6.88 kB
import React, { useEffect, useRef, useState } from "react"; import Styles from "../css/style.module.css"; import ArrowDown from "./SVGImages/ArrowDown"; function FontSize({ editorRef, isDisable }) { const [isOpen, setIsOpen] = useState(false); const [selectedOption, setSelectedOption] = useState("16px"); const selectRef = useRef(null); const fontSizes = [ { label: "10px", value: "10" }, { label: "12px", value: "12" }, { label: "14px", value: "14" }, { label: "16px", value: "16" }, { label: "18px", value: "18" }, { label: "20px", value: "20" }, { label: "24px", value: "24" }, { label: "28px", value: "28" }, { label: "32px", value: "32" }, { label: "36px", value: "36" }, { label: "48px", value: "48" }, { label: "60px", value: "60" }, { label: "72px", value: "72" }, ]; const toggleSelect = (e) => { if (isDisable) { return; } e.preventDefault(); setIsOpen(!isOpen); }; const handleOptionClick = (e, option) => { e.preventDefault(); editorRef.current.focus(); const selection = window.getSelection(); if (!selection.rangeCount) return; const range = selection.getRangeAt(0); const selectedNode = range.commonAncestorContainer; const selectedText = range.toString(); const value = option.value + "px"; const findParentSpan = (node) => { while (node && node !== editorRef.current) { if (node.nodeType === 1) { // Check any element that has fontSize (SPAN, P, DIV, etc.) if (node.style && node.style.fontSize) { const nodeText = (node.textContent || node.innerText || "").trim(); const selText = (selectedText || "").trim(); if (nodeText === selText) { return node; } } // Also check SPAN without fontSize (might have other styles) if (node.tagName === "SPAN") { const nodeText = (node.textContent || node.innerText || "").trim(); const selText = (selectedText || "").trim(); if (nodeText === selText) { return node; } } } node = node.parentNode; } return null; }; const existingSpan = findParentSpan(selectedNode); if (!selection.isCollapsed) { if (existingSpan) { existingSpan.style.fontSize = value; } else { const span = document.createElement("span"); span.style.fontSize = value; // To safely wrap the selection without breaking HTML structure const content = range.extractContents(); // Remove fontSize from all child elements so the new span's font-size takes effect const childElements = content.querySelectorAll("[style]"); childElements.forEach((el) => { if (el.style.fontSize) { el.style.removeProperty("font-size"); // If no styles left, unwrap the element (replace with its children) if (!el.getAttribute("style") || el.getAttribute("style").trim() === "") { if (el.tagName === "SPAN") { while (el.firstChild) { el.parentNode.insertBefore(el.firstChild, el); } el.parentNode.removeChild(el); } } } }); span.appendChild(content); range.insertNode(span); } } else { // No selection — insert empty span at caret const span = document.createElement("span"); span.style.fontSize = value; span.innerHTML = "\u200B"; range.insertNode(span); // Place caret inside span range.setStart(span.firstChild, 1); range.setEnd(span.firstChild, 1); selection.removeAllRanges(); selection.addRange(range); } setSelectedOption(option.label); setIsOpen(false); }; const getCurrentFontSize = () => { const selection = window.getSelection(); if (selection.rangeCount > 0) { const range = selection.getRangeAt(0); const startNode = range.startContainer; let node = startNode; while (node && node !== editorRef.current) { if (node.nodeType === Node.ELEMENT_NODE && node.style.fontSize) { const fontSize = node.style.fontSize; // Remove 'px' and find matching option const sizeValue = fontSize.replace("px", ""); const foundSize = fontSizes.find((size) => size.value === sizeValue); if (foundSize) { return foundSize.label; } } node = node.parentNode; } } return null; }; const handleKeyDown = () => { const editor = editorRef.current; if (!editor) return; if (!editor.contains(window.getSelection().anchorNode)) { return; } editor.focus(); }; useEffect(() => { const handleClickOutside = (event) => { if (selectRef.current && !selectRef.current.contains(event.target)) { setIsOpen(false); } }; const handleSelectionChange = () => { if (editorRef?.current?.contains(window.getSelection().anchorNode)) { const currentSize = getCurrentFontSize(); if (currentSize) { setSelectedOption(currentSize); } else { setSelectedOption("16px"); } } }; document.addEventListener("click", handleClickOutside); document.addEventListener("selectionchange", handleSelectionChange); document.addEventListener("keydown", handleKeyDown); return () => { document.removeEventListener("click", handleClickOutside); document.removeEventListener("selectionchange", handleSelectionChange); document.removeEventListener("keydown", handleKeyDown); }; }, [editorRef]); return ( <button type="button" className={`${Styles.customSelectFormat} ${ isDisable ? Styles.disabledButton : "" }`} onClick={toggleSelect} ref={selectRef} disabled={isDisable} > <div className={Styles.selectSelected}> {selectedOption} <ArrowDown /> </div> {isOpen && ( <div className={Styles.selectItemsFormat} style={{ maxHeight: "200px", overflowY: "auto" }} > {fontSizes.map((option, index) => ( <div key={`key${index}`} onClick={(e) => handleOptionClick(e, option)} className={Styles.selectOption} style={{ fontSize: "14px" }} > {option.label} </div> ))} </div> )} </button> ); } export default FontSize;