From d55c79d75c8a740e8334beefa08460cbd5567460 Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Tue, 11 Feb 2025 20:15:55 +0530 Subject: [PATCH] fix: bubble menu weird flickering fixed --- .../menus/bubble-menu/alignment-selector.tsx | 22 +-- .../menus/bubble-menu/color-selector.tsx | 10 +- .../components/menus/bubble-menu/root.tsx | 126 ++++++++++++------ .../src/core/components/menus/menu-items.ts | 4 +- packages/editor/src/core/constants/common.ts | 4 +- packages/editor/src/core/types/editor.ts | 2 +- web/core/constants/editor.ts | 4 +- 7 files changed, 105 insertions(+), 67 deletions(-) diff --git a/packages/editor/src/core/components/menus/bubble-menu/alignment-selector.tsx b/packages/editor/src/core/components/menus/bubble-menu/alignment-selector.tsx index 24dfeceafdf..b168525a797 100644 --- a/packages/editor/src/core/components/menus/bubble-menu/alignment-selector.tsx +++ b/packages/editor/src/core/components/menus/bubble-menu/alignment-selector.tsx @@ -6,15 +6,15 @@ import { cn } from "@plane/utils"; import { TextAlignItem } from "@/components/menus"; // types import { TEditorCommands } from "@/types"; +import { EditorStateType } from "./root"; type Props = { editor: Editor; - onClose: () => void; + editorState: EditorStateType; }; export const TextAlignmentSelector: React.FC = (props) => { - const { editor, onClose } = props; - + const { editor, editorState } = props; const menuItem = TextAlignItem(editor); const textAlignmentOptions: { @@ -32,10 +32,7 @@ export const TextAlignmentSelector: React.FC = (props) => { menuItem.command({ alignment: "left", }), - isActive: () => - menuItem.isActive({ - alignment: "left", - }), + isActive: () => editorState.left, }, { itemKey: "text-align", @@ -45,10 +42,7 @@ export const TextAlignmentSelector: React.FC = (props) => { menuItem.command({ alignment: "center", }), - isActive: () => - menuItem.isActive({ - alignment: "center", - }), + isActive: () => editorState.center, }, { itemKey: "text-align", @@ -58,10 +52,7 @@ export const TextAlignmentSelector: React.FC = (props) => { menuItem.command({ alignment: "right", }), - isActive: () => - menuItem.isActive({ - alignment: "right", - }), + isActive: () => editorState.right, }, ]; @@ -74,7 +65,6 @@ export const TextAlignmentSelector: React.FC = (props) => { onClick={(e) => { e.stopPropagation(); item.command(); - onClose(); }} className={cn( "size-7 grid place-items-center rounded text-custom-text-300 hover:bg-custom-background-80 active:bg-custom-background-80 transition-colors", diff --git a/packages/editor/src/core/components/menus/bubble-menu/color-selector.tsx b/packages/editor/src/core/components/menus/bubble-menu/color-selector.tsx index fe996a71323..ced7ea1793d 100644 --- a/packages/editor/src/core/components/menus/bubble-menu/color-selector.tsx +++ b/packages/editor/src/core/components/menus/bubble-menu/color-selector.tsx @@ -1,24 +1,26 @@ -import { Dispatch, FC, SetStateAction } from "react"; import { Editor } from "@tiptap/react"; import { ALargeSmall, Ban } from "lucide-react"; +import { Dispatch, FC, SetStateAction } from "react"; // plane utils import { cn } from "@plane/utils"; // constants import { COLORS_LIST } from "@/constants/common"; // helpers import { BackgroundColorItem, TextColorItem } from "../menu-items"; +import { EditorStateType } from "./root"; type Props = { editor: Editor; isOpen: boolean; setIsOpen: Dispatch>; + editorState: EditorStateType; }; export const BubbleMenuColorSelector: FC = (props) => { - const { editor, isOpen, setIsOpen } = props; + const { editor, isOpen, setIsOpen, editorState } = props; - const activeTextColor = COLORS_LIST.find((c) => TextColorItem(editor).isActive({ color: c.key })); - const activeBackgroundColor = COLORS_LIST.find((c) => BackgroundColorItem(editor).isActive({ color: c.key })); + const activeTextColor = editorState.color; + const activeBackgroundColor = editorState.backgroundColor; return (
diff --git a/packages/editor/src/core/components/menus/bubble-menu/root.tsx b/packages/editor/src/core/components/menus/bubble-menu/root.tsx index 3f4e97ca706..149c6f6c24b 100644 --- a/packages/editor/src/core/components/menus/bubble-menu/root.tsx +++ b/packages/editor/src/core/components/menus/bubble-menu/root.tsx @@ -1,9 +1,10 @@ -import { FC, useEffect, useState } from "react"; -import { BubbleMenu, BubbleMenuProps, Editor, isNodeSelection } from "@tiptap/react"; +import { BubbleMenu, BubbleMenuProps, Editor, isNodeSelection, useEditorState } from "@tiptap/react"; +import { FC, useEffect, useState, useRef } from "react"; // plane utils import { cn } from "@plane/utils"; // components import { + BackgroundColorItem, BoldItem, BubbleMenuColorSelector, BubbleMenuLinkSelector, @@ -11,8 +12,12 @@ import { CodeItem, ItalicItem, StrikeThroughItem, + TextAlignItem, + TextColorItem, UnderLineItem, } from "@/components/menus"; +// constants +import { COLORS_LIST } from "@/constants/common"; // extensions import { isCellSelection } from "@/extensions/table/table/utilities/is-cell-selection"; // local components @@ -20,16 +25,61 @@ import { TextAlignmentSelector } from "./alignment-selector"; type EditorBubbleMenuProps = Omit; -export const EditorBubbleMenu: FC = (props: any) => { - // states +export interface EditorStateType { + code: boolean; + bold: boolean; + italic: boolean; + underline: boolean; + strike: boolean; + left: boolean; + right: boolean; + center: boolean; + color: { key: string; label: string; textColor: string; backgroundColor: string } | undefined; + backgroundColor: + | { + key: string; + label: string; + textColor: string; + backgroundColor: string; + } + | undefined; +} + +export const EditorBubbleMenu: FC = (props: { editor: Editor }) => { + const menuRef = useRef(null); const [isNodeSelectorOpen, setIsNodeSelectorOpen] = useState(false); const [isLinkSelectorOpen, setIsLinkSelectorOpen] = useState(false); const [isColorSelectorOpen, setIsColorSelectorOpen] = useState(false); const [isSelecting, setIsSelecting] = useState(false); - const basicFormattingOptions = props.editor.isActive("code") - ? [CodeItem(props.editor)] - : [BoldItem(props.editor), ItalicItem(props.editor), UnderLineItem(props.editor), StrikeThroughItem(props.editor)]; + const formattingItems = { + code: CodeItem(props.editor), + bold: BoldItem(props.editor), + italic: ItalicItem(props.editor), + underline: UnderLineItem(props.editor), + strike: StrikeThroughItem(props.editor), + textAlign: TextAlignItem(props.editor), + }; + + const editorState: EditorStateType = useEditorState({ + editor: props.editor, + selector: ({ editor }: { editor: Editor }) => ({ + code: formattingItems.code.isActive(), + bold: formattingItems.bold.isActive(), + italic: formattingItems.italic.isActive(), + underline: formattingItems.underline.isActive(), + strike: formattingItems.strike.isActive(), + left: formattingItems.textAlign.isActive({ alignment: "left" }), + right: formattingItems.textAlign.isActive({ alignment: "right" }), + center: formattingItems.textAlign.isActive({ alignment: "center" }), + color: COLORS_LIST.find((c) => TextColorItem(editor).isActive({ color: c.key })), + backgroundColor: COLORS_LIST.find((c) => BackgroundColorItem(editor).isActive({ color: c.key })), + }), + }); + + const basicFormattingOptions = editorState.code + ? [formattingItems.code] + : [formattingItems.bold, formattingItems.italic, formattingItems.underline, formattingItems.strike]; const bubbleMenuProps: EditorBubbleMenuProps = { ...props, @@ -51,6 +101,7 @@ export const EditorBubbleMenu: FC = (props: any) => { }, tippyOptions: { moveTransition: "transform 0.15s ease-out", + duration: [300, 0], onHidden: () => { setIsNodeSelectorOpen(false); setIsLinkSelectorOpen(false); @@ -60,7 +111,9 @@ export const EditorBubbleMenu: FC = (props: any) => { }; useEffect(() => { - function handleMouseDown() { + function handleMouseDown(e: MouseEvent) { + if (menuRef.current?.contains(e.target as Node)) return; + function handleMouseMove() { if (!props.editor.state.selection.empty) { setIsSelecting(true); @@ -70,7 +123,6 @@ export const EditorBubbleMenu: FC = (props: any) => { function handleMouseUp() { setIsSelecting(false); - document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); } @@ -84,27 +136,28 @@ export const EditorBubbleMenu: FC = (props: any) => { return () => { document.removeEventListener("mousedown", handleMouseDown); }; - }, []); + }, [props.editor]); return ( {!isSelecting && ( -
+
- {!props.editor.isActive("table") && ( - { - setIsNodeSelectorOpen((prev) => !prev); - setIsLinkSelectorOpen(false); - setIsColorSelectorOpen(false); - }} - /> - )} + { + setIsNodeSelectorOpen((prev) => !prev); + setIsLinkSelectorOpen(false); + setIsColorSelectorOpen(false); + }} + />
-
- {!props.editor.isActive("code") && ( + {!editorState.code && ( +
= (props: any) => { setIsColorSelectorOpen(false); }} /> - )} -
-
- {!props.editor.isActive("code") && ( +
+ )} + {!editorState.code && ( +
{ setIsColorSelectorOpen((prev) => !prev); setIsNodeSelectorOpen(false); setIsLinkSelectorOpen(false); }} /> - )} -
+
+ )}
{basicFormattingOptions.map((item) => ( ))}
- { - const editor = props.editor as Editor; - if (!editor) return; - const pos = editor.state.selection.to; - editor.commands.setTextSelection(pos ?? 0); - }} - /> +
)} diff --git a/packages/editor/src/core/components/menus/menu-items.ts b/packages/editor/src/core/components/menus/menu-items.ts index adbd262eb6e..e87c0984b7f 100644 --- a/packages/editor/src/core/components/menus/menu-items.ts +++ b/packages/editor/src/core/components/menus/menu-items.ts @@ -142,8 +142,8 @@ export const UnderLineItem = (editor: Editor): EditorMenuItem<"underline"> => ({ icon: UnderlineIcon, }); -export const StrikeThroughItem = (editor: Editor): EditorMenuItem<"strikethrough"> => ({ - key: "strikethrough", +export const StrikeThroughItem = (editor: Editor): EditorMenuItem<"strike"> => ({ + key: "strike", name: "Strikethrough", isActive: () => editor?.isActive("strike"), command: () => toggleStrike(editor), diff --git a/packages/editor/src/core/constants/common.ts b/packages/editor/src/core/constants/common.ts index 8961bcd915b..bae06d3031e 100644 --- a/packages/editor/src/core/constants/common.ts +++ b/packages/editor/src/core/constants/common.ts @@ -87,7 +87,7 @@ export const TEXT_ALIGNMENT_ITEMS: ToolbarMenuItem<"text-align">[] = [ }, ]; -const BASIC_MARK_ITEMS: ToolbarMenuItem<"bold" | "italic" | "underline" | "strikethrough">[] = [ +const BASIC_MARK_ITEMS: ToolbarMenuItem<"bold" | "italic" | "underline" | "strike">[] = [ { itemKey: "bold", renderKey: "bold", @@ -113,7 +113,7 @@ const BASIC_MARK_ITEMS: ToolbarMenuItem<"bold" | "italic" | "underline" | "strik editors: ["lite", "document"], }, { - itemKey: "strikethrough", + itemKey: "strike", renderKey: "strikethrough", name: "Strikethrough", icon: Strikethrough, diff --git a/packages/editor/src/core/types/editor.ts b/packages/editor/src/core/types/editor.ts index 628c1ab258e..fd64dc46a61 100644 --- a/packages/editor/src/core/types/editor.ts +++ b/packages/editor/src/core/types/editor.ts @@ -31,7 +31,7 @@ export type TEditorCommands = | "bold" | "italic" | "underline" - | "strikethrough" + | "strike" | "bulleted-list" | "numbered-list" | "to-do-list" diff --git a/web/core/constants/editor.ts b/web/core/constants/editor.ts index 5e8c723d71d..e3cb487b6b0 100644 --- a/web/core/constants/editor.ts +++ b/web/core/constants/editor.ts @@ -93,7 +93,7 @@ export const TEXT_ALIGNMENT_ITEMS: ToolbarMenuItem<"text-align">[] = [ }, ]; -const BASIC_MARK_ITEMS: ToolbarMenuItem<"bold" | "italic" | "underline" | "strikethrough">[] = [ +const BASIC_MARK_ITEMS: ToolbarMenuItem<"bold" | "italic" | "underline" | "strike">[] = [ { itemKey: "bold", renderKey: "bold", @@ -119,7 +119,7 @@ const BASIC_MARK_ITEMS: ToolbarMenuItem<"bold" | "italic" | "underline" | "strik editors: ["lite", "document"], }, { - itemKey: "strikethrough", + itemKey: "strike", renderKey: "strikethrough", name: "Strikethrough", icon: Strikethrough,