UNPKG

phx-react

Version:

PHX REACT

162 lines • 6.89 kB
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { $getSelection, $isElementNode, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_CRITICAL, INSERT_PARAGRAPH_COMMAND, } from 'lexical'; import { $isListItemNode } from '@lexical/list'; import { $getSelectionStyleValueForProperty, $patchStyleText, getCSSFromStyleObject, getStyleObjectFromCSS, } from '@lexical/selection'; import { useEffect } from 'react'; /** * Extracts font size from a CSS style string. * @param style CSS style text to parse. * @returns Font size value, or an empty string when absent. */ function getFontSizeFromStyle(style) { var _a; return ((_a = getStyleObjectFromCSS(style)) === null || _a === void 0 ? void 0 : _a['font-size']) || ''; } /** * Gets the font size that should be preserved for a collapsed selection. * @returns Font size value, or an empty string when no font size is available. */ function getCollapsedSelectionFontSize() { const selection = $getSelection(); if (!$isRangeSelection(selection) || !selection.isCollapsed()) { return ''; } const selectionFontSize = $getSelectionStyleValueForProperty(selection, 'font-size', ''); if (selectionFontSize) { return selectionFontSize; } const anchor = selection.anchor; const anchorNode = anchor.getNode(); if ($isTextNode(anchorNode)) { return getFontSizeFromStyle(anchorNode.getStyle()); } if ($isElementNode(anchorNode)) { const previousNode = anchor.offset > 0 ? anchorNode.getChildAtIndex(anchor.offset - 1) : null; const nextNode = anchorNode.getChildAtIndex(anchor.offset); if ($isTextNode(previousNode)) { return getFontSizeFromStyle(previousNode.getStyle()); } if ($isTextNode(nextNode)) { return getFontSizeFromStyle(nextNode.getStyle()); } } return ''; } /** * Checks whether the current selection is inside an empty list item. * @returns True when the collapsed selection is in an empty list item. */ function isSelectionInEmptyListItem() { const selection = $getSelection(); if (!$isRangeSelection(selection) || !selection.isCollapsed()) { return false; } const anchorNode = selection.anchor.getNode(); if ($isListItemNode(anchorNode) && anchorNode.getChildrenSize() === 0) { return true; } if (!$isTextNode(anchorNode)) { return false; } const parent = anchorNode.getParent(); return ($isListItemNode(parent) && parent.getChildren().every((node) => $isTextNode(node) && node.getTextContent().trim() === '')); } /** * Syncs the inserted block and collapsed selection to a font size after Lexical handles list exit. * @param editor Active Lexical editor. * @param fontSize Font size that should drive the next caret height. */ function syncListExitFontSize(editor, fontSize) { const applyFontSize = () => { editor.update(() => { const selection = $getSelection(); if (!$isRangeSelection(selection) || !selection.isCollapsed()) { return; } const anchorNode = selection.anchor.getNode(); const targetNode = $isElementNode(anchorNode) ? anchorNode : $isTextNode(anchorNode) ? anchorNode.getTopLevelElement() : null; if (!$isElementNode(targetNode)) { return; } const targetNodeKey = targetNode.getKey(); const blockStyles = getStyleObjectFromCSS(targetNode.getStyle()); const fontSizeStyle = getCSSFromStyleObject({ 'font-size': fontSize }); targetNode.setStyle(getCSSFromStyleObject({ ...blockStyles, 'font-size': fontSize, })); targetNode.setTextStyle(fontSizeStyle); $patchStyleText(selection, { 'font-size': fontSize, }); const applyFontSizeToElement = () => { const targetElement = editor.getElementByKey(targetNodeKey); targetElement === null || targetElement === void 0 ? void 0 : targetElement.style.setProperty('font-size', fontSize); }; if (typeof queueMicrotask === 'function') { queueMicrotask(applyFontSizeToElement); } requestAnimationFrame(applyFontSizeToElement); }); }; if (typeof queueMicrotask === 'function') { queueMicrotask(applyFontSize); } else { setTimeout(applyFontSize); } } /** * Preserves font size when inserting a paragraph with Enter. * @returns Null because the plugin renders no UI. */ export default function PreserveFontSizeOnEnterPlugin() { const [editor] = useLexicalComposerContext(); useEffect(() => { return editor.registerCommand(INSERT_PARAGRAPH_COMMAND, () => { const selection = $getSelection(); const fontSize = getCollapsedSelectionFontSize(); if (!$isRangeSelection(selection) || !selection.isCollapsed() || !fontSize) { return false; } if (isSelectionInEmptyListItem()) { syncListExitFontSize(editor, fontSize); return false; } const insertedBlock = selection.insertParagraph(); if ($isElementNode(insertedBlock)) { const insertedBlockKey = insertedBlock.getKey(); const blockStyles = getStyleObjectFromCSS(insertedBlock.getStyle()); const fontSizeStyle = getCSSFromStyleObject({ 'font-size': fontSize }); insertedBlock.setStyle(getCSSFromStyleObject({ ...blockStyles, 'font-size': fontSize, })); insertedBlock.setTextStyle(fontSizeStyle); insertedBlock.selectStart(); const applyFontSizeToInsertedElement = () => { const insertedElement = editor.getElementByKey(insertedBlockKey); insertedElement === null || insertedElement === void 0 ? void 0 : insertedElement.style.setProperty('font-size', fontSize); }; if (typeof queueMicrotask === 'function') { queueMicrotask(applyFontSizeToInsertedElement); } requestAnimationFrame(applyFontSizeToInsertedElement); } const nextSelection = $getSelection(); if ($isRangeSelection(nextSelection) && nextSelection.isCollapsed()) { $patchStyleText(nextSelection, { 'font-size': fontSize, }); } return true; }, COMMAND_PRIORITY_CRITICAL); }, [editor]); return null; } //# sourceMappingURL=index.js.map