UNPKG

phx-react

Version:

PHX REACT

223 lines • 12.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = FloatingTextFormatToolbarPlugin; const tslib_1 = require("tslib"); const link_1 = require("@lexical/link"); const LexicalComposerContext_1 = require("@lexical/react/LexicalComposerContext"); const utils_1 = require("@lexical/utils"); const lexical_1 = require("lexical"); const react_1 = require("react"); const React = tslib_1.__importStar(require("react")); const react_dom_1 = require("react-dom"); const getDOMRangeRect_1 = require("../../utils/getDOMRangeRect"); const getSelectedNode_1 = require("../../utils/getSelectedNode"); const setFloatingElemPosition_1 = require("../../utils/setFloatingElemPosition"); function TextFormatFloatingToolbar({ anchorElem, editor, isBold, isCode, isItalic, isLink, isStrikethrough, isSubscript, isSuperscript, isUnderline, }) { const popupCharStylesEditorRef = (0, react_1.useRef)(null); const insertLink = (0, react_1.useCallback)(() => { if (!isLink) { editor.dispatchCommand(link_1.TOGGLE_LINK_COMMAND, 'https://'); } else { editor.dispatchCommand(link_1.TOGGLE_LINK_COMMAND, null); } }, [editor, isLink]); const insertComment = () => { console.log('insert comment'); }; function mouseMoveListener(e) { if ((popupCharStylesEditorRef === null || popupCharStylesEditorRef === void 0 ? void 0 : popupCharStylesEditorRef.current) && (e.buttons === 1 || e.buttons === 3)) { if (popupCharStylesEditorRef.current.style.pointerEvents !== 'none') { const x = e.clientX; const y = e.clientY; const elementUnderMouse = document.elementFromPoint(x, y); if (!popupCharStylesEditorRef.current.contains(elementUnderMouse)) { // Mouse is not over the target element => not a normal click, but probably a drag popupCharStylesEditorRef.current.style.pointerEvents = 'none'; } } } } function mouseUpListener() { if (popupCharStylesEditorRef === null || popupCharStylesEditorRef === void 0 ? void 0 : popupCharStylesEditorRef.current) { if (popupCharStylesEditorRef.current.style.pointerEvents !== 'auto') { popupCharStylesEditorRef.current.style.pointerEvents = 'auto'; } } } (0, react_1.useEffect)(() => { if (popupCharStylesEditorRef === null || popupCharStylesEditorRef === void 0 ? void 0 : popupCharStylesEditorRef.current) { document.addEventListener('mousemove', mouseMoveListener); document.addEventListener('mouseup', mouseUpListener); return () => { document.removeEventListener('mousemove', mouseMoveListener); document.removeEventListener('mouseup', mouseUpListener); }; } return () => { console.log('clean up'); }; }, [popupCharStylesEditorRef]); const updateTextFormatFloatingToolbar = (0, react_1.useCallback)(() => { const selection = (0, lexical_1.$getSelection)(); const popupCharStylesEditorElem = popupCharStylesEditorRef.current; const nativeSelection = window.getSelection(); if (popupCharStylesEditorElem === null) { return; } const rootElement = editor.getRootElement(); if (selection !== null && nativeSelection !== null && !nativeSelection.isCollapsed && rootElement !== null && rootElement.contains(nativeSelection.anchorNode)) { const rangeRect = (0, getDOMRangeRect_1.getDOMRangeRect)(nativeSelection, rootElement); (0, setFloatingElemPosition_1.setFloatingElemPosition)(rangeRect, popupCharStylesEditorElem, anchorElem, isLink); } }, [editor, anchorElem, isLink]); (0, react_1.useEffect)(() => { const scrollerElem = anchorElem.parentElement; const update = () => { editor.getEditorState().read(() => { updateTextFormatFloatingToolbar(); }); }; window.addEventListener('resize', update); if (scrollerElem) { scrollerElem.addEventListener('scroll', update); } return () => { window.removeEventListener('resize', update); if (scrollerElem) { scrollerElem.removeEventListener('scroll', update); } }; }, [editor, updateTextFormatFloatingToolbar, anchorElem]); (0, react_1.useEffect)(() => { editor.getEditorState().read(() => { updateTextFormatFloatingToolbar(); }); return (0, utils_1.mergeRegister)(editor.registerUpdateListener(({ editorState }) => { editorState.read(() => { updateTextFormatFloatingToolbar(); }); }), editor.registerCommand(lexical_1.SELECTION_CHANGE_COMMAND, () => { updateTextFormatFloatingToolbar(); return false; }, lexical_1.COMMAND_PRIORITY_LOW)); }, [editor, updateTextFormatFloatingToolbar]); return (React.createElement("div", { ref: popupCharStylesEditorRef, className: 'floating-text-format-popup' }, editor.isEditable() && (React.createElement(React.Fragment, null, React.createElement("button", { "aria-label": 'Format text as bold', className: 'popup-item spaced ' + (isBold ? 'active' : ''), onClick: () => { editor.dispatchCommand(lexical_1.FORMAT_TEXT_COMMAND, 'bold'); }, type: 'button' }, React.createElement("i", { className: 'format bold' })), React.createElement("button", { "aria-label": 'Format text as italics', className: 'popup-item spaced ' + (isItalic ? 'active' : ''), onClick: () => { editor.dispatchCommand(lexical_1.FORMAT_TEXT_COMMAND, 'italic'); }, type: 'button' }, React.createElement("i", { className: 'format italic' })), React.createElement("button", { "aria-label": 'Format text to underlined', className: 'popup-item spaced ' + (isUnderline ? 'active' : ''), onClick: () => { editor.dispatchCommand(lexical_1.FORMAT_TEXT_COMMAND, 'underline'); }, type: 'button' }, React.createElement("i", { className: 'format underline' })), React.createElement("button", { "aria-label": 'Format text with a strikethrough', className: 'popup-item spaced ' + (isStrikethrough ? 'active' : ''), onClick: () => { editor.dispatchCommand(lexical_1.FORMAT_TEXT_COMMAND, 'strikethrough'); }, type: 'button' }, React.createElement("i", { className: 'format strikethrough' })), React.createElement("button", { "aria-label": 'Format Subscript', className: 'popup-item spaced ' + (isSubscript ? 'active' : ''), onClick: () => { editor.dispatchCommand(lexical_1.FORMAT_TEXT_COMMAND, 'subscript'); }, title: 'Subscript', type: 'button' }, React.createElement("i", { className: 'format subscript' })), React.createElement("button", { "aria-label": 'Format Superscript', className: 'popup-item spaced ' + (isSuperscript ? 'active' : ''), onClick: () => { editor.dispatchCommand(lexical_1.FORMAT_TEXT_COMMAND, 'superscript'); }, title: 'Superscript', type: 'button' }, React.createElement("i", { className: 'format superscript' })), React.createElement("button", { "aria-label": 'Insert code block', className: 'popup-item spaced ' + (isCode ? 'active' : ''), onClick: () => { editor.dispatchCommand(lexical_1.FORMAT_TEXT_COMMAND, 'code'); }, type: 'button' }, React.createElement("i", { className: 'format code' })), React.createElement("button", { "aria-label": 'Insert link', className: 'popup-item spaced ' + (isLink ? 'active' : ''), onClick: insertLink, type: 'button' }, React.createElement("i", { className: 'format link' })))), React.createElement("button", { "aria-label": 'Insert comment', className: 'popup-item spaced insert-comment', onClick: insertComment, type: 'button' }, React.createElement("i", { className: 'format add-comment' })))); } function useFloatingTextFormatToolbar(editor, anchorElem) { const [isText, setIsText] = (0, react_1.useState)(false); const [isLink, setIsLink] = (0, react_1.useState)(false); const [isBold, setIsBold] = (0, react_1.useState)(false); const [isItalic, setIsItalic] = (0, react_1.useState)(false); const [isUnderline, setIsUnderline] = (0, react_1.useState)(false); const [isStrikethrough, setIsStrikethrough] = (0, react_1.useState)(false); const [isSubscript, setIsSubscript] = (0, react_1.useState)(false); const [isSuperscript, setIsSuperscript] = (0, react_1.useState)(false); const [isCode, setIsCode] = (0, react_1.useState)(false); const updatePopup = (0, react_1.useCallback)(() => { editor.getEditorState().read(() => { // Should not to pop up the floating toolbar when using IME input if (editor.isComposing()) { return; } const selection = (0, lexical_1.$getSelection)(); const nativeSelection = window.getSelection(); const rootElement = editor.getRootElement(); if (nativeSelection !== null && (!(0, lexical_1.$isRangeSelection)(selection) || rootElement === null || !rootElement.contains(nativeSelection.anchorNode))) { setIsText(false); return; } if (!(0, lexical_1.$isRangeSelection)(selection)) { return; } const node = (0, getSelectedNode_1.getSelectedNode)(selection); // Update text format setIsBold(selection.hasFormat('bold')); setIsItalic(selection.hasFormat('italic')); setIsUnderline(selection.hasFormat('underline')); setIsStrikethrough(selection.hasFormat('strikethrough')); setIsSubscript(selection.hasFormat('subscript')); setIsSuperscript(selection.hasFormat('superscript')); setIsCode(selection.hasFormat('code')); // Update links const parent = node.getParent(); if ((0, link_1.$isLinkNode)(parent) || (0, link_1.$isLinkNode)(node)) { setIsLink(true); } else { setIsLink(false); } if (selection.getTextContent() !== '') { setIsText((0, lexical_1.$isTextNode)(node) || (0, lexical_1.$isParagraphNode)(node)); } else { setIsText(false); } const rawTextContent = selection.getTextContent().replace(/\n/g, ''); if (!selection.isCollapsed() && rawTextContent === '') { setIsText(false); return; } }); }, [editor]); (0, react_1.useEffect)(() => { document.addEventListener('selectionchange', updatePopup); return () => { document.removeEventListener('selectionchange', updatePopup); }; }, [updatePopup]); (0, react_1.useEffect)(() => (0, utils_1.mergeRegister)(editor.registerUpdateListener(() => { updatePopup(); }), editor.registerRootListener(() => { if (editor.getRootElement() === null) { setIsText(false); } })), [editor, updatePopup]); if (!isText) { return null; } return (0, react_dom_1.createPortal)(React.createElement(TextFormatFloatingToolbar, { anchorElem: anchorElem, editor: editor, isBold: isBold, isCode: isCode, isItalic: isItalic, isLink: isLink, isStrikethrough: isStrikethrough, isSubscript: isSubscript, isSuperscript: isSuperscript, isUnderline: isUnderline }), anchorElem); } function FloatingTextFormatToolbarPlugin({ anchorElem = document.body, }) { const [editor] = (0, LexicalComposerContext_1.useLexicalComposerContext)(); return useFloatingTextFormatToolbar(editor, anchorElem); } //# sourceMappingURL=index.js.map