imnapo / react-native-cn-quill

Quill rich-text editor for react-native
MIT License
190 stars 75 forks source link

Editor looses focus - gets blurred after pressing on Bold/Italic #132

Open dr-zr opened 9 months ago

dr-zr commented 9 months ago

I'm experiencing issues with the bold and italic toolbar options. The issue is that the editor input gets blurred after a user presses on the toolbar buttons. The issues most likely occur because the selection-change fires and has range: null

I've looked at similar issues and found this https://github.com/imnapo/react-native-cn-quill/issues/93 , but nobody answered 😕

I've used the handleSelectionChange function from the examples folder. When pressing bold/italic I get "Cursor not in the editor"

const handleSelectionChange = async (data: SelectionChangeData) => {
            const { range } = data;

            if (range) {
                if (range.length === 0) {
                    console.log("User cursor is on", range.index);
                } else {
                    const text = await editor.current?.getText(
                        range.index,
                        range.length,
                    );
                    console.log("User has highlighted", text);
                }
            } else {
                console.log("Cursor not in the editor");
            }
        };

Code:


const maximizeIcon = require("@resources/assets/icons/maximize-solid.png");
const minimizeIcon = require("@resources/assets/icons/minimize-solid.png");

const baseToolbarOptions: NonNullable<
    NonNullable<EditorProps["quill"]>["modules"]
>["toolbar"] = [
    ["bold", "italic"],
    [{ list: "ordered" }, { list: "bullet" }],
    [{ indent: "-1" }, { indent: "+1" }],
];

const toolbarResizeActions = ["maximize", "minimize"];
const toolbarResizeIcons = {
    maximize: maximizeIcon,
    minimize: minimizeIcon,
};

const webViewProps: EditorProps["webview"] = {
    scrollEnabled: true, // enable scrolling in the web view
    nestedScrollEnabled: true, // enable nested scrolling
};

interface Props extends EditorProps {
    readonly enabled: boolean;
    readonly placeholder?: string;
    readonly allowResize?: boolean;
}

export const TextEditor = forwardRef<QuillEditor, Props>(
    function TextEditor(props, _ref) {
        const styles = useStyles();
        const { colors, dark } = useTheme();
        const { height } = useWindowDimensions();
        useImagePasteHandler();

        const [containerSize, setContainerSize] = useState<
            DimensionValue | undefined
        >();

        const editor = useTextEditorRefStore((state) => state.ref);

        //#region Toolbar
        const toolbarOptions = useMemo(() => {
            if (!props.allowResize) {
                return baseToolbarOptions;
            }
            return baseToolbarOptions.concat([
                [containerSize ? "minimize" : "maximize"],
            ]);
        }, [containerSize, props.allowResize]);
        const toolbarStyles = useMemo((): CustomStyles => {
            return {
                toolbar: {
                    provider: (props) => ({
                        ...props,
                        borderColor: colors.backgroundHighlight,
                    }),
                    root: (props) => ({
                        ...props,
                        backgroundColor: colors.backgroundHighlight,
                    }),
                },
            };
        }, [colors.backgroundHighlight]);
        //#endregion

        //#region Editor
        const quill = useMemo((): EditorProps["quill"] => {
            return {
                modules: undefined,
                placeholder: props.placeholder ?? "",
                theme: "bubble",
            };
        }, [props.placeholder]);

        const theme = useMemo((): EditorProps["theme"] => {
            return {
                placeholder: colors.textMute,
                background: colors.backgroundHighlight,
                color: colors.text,
            };
        }, [colors.backgroundHighlight, colors.text, colors.textMute]);

        const customStyles = useMemo((): EditorProps["customStyles"] => {
            return [
                `.ql-stroke { stroke: ${colors.text} !important; }`,
                `.ql-fill {fill: ${colors.text} !important;}`,
                `.ql-active .ql-stroke {   stroke: ${colors.primary} !important;}`,
                `.ql-active .ql-fill {   stroke: ${colors.primary} !important;}`,
                ".ql-tooltip { visibility: hidden !important } ",
            ];
        }, [colors.primary, colors.text]);
        //#endregion

        const handleSelectionChange = async (data: SelectionChangeData) => {
            const { range } = data;

            if (range) {
                if (range.length === 0) {
                    console.log("User cursor is on", range.index);
                } else {
                    const text = await editor.current?.getText(
                        range.index,
                        range.length,
                    );
                    console.log("User has highlighted", text);
                }
            } else {
                console.log("Cursor not in the editor");
            }
        };

        const handleToolbarActions = (name: string) => {
            const editorState = useTextEditorRefStore.getState();
            switch (name) {
                case "maximize":
                    setContainerSize(height);
                    editorState.isMaximized = true;
                    break;

                default:
                    editorState.isMaximized = false;
                    setContainerSize(undefined);
                    break;
            }
        };

        useEffect(
            function updateWritePermission() {
                useTextEditorRefStore
                    .getState()
                    .ref.current?.enable(props.enabled);
            },
            [props.enabled],
        );

        useEffect(
            function resetText() {
                if (props.initialHtml) {
                    return;
                }

                useTextEditorRefStore.getState().ref.current?.setText("");
            },
            [props.initialHtml],
        );

        return (
            <View style={dss.flex1}>
                <QuillToolbar
                    editor={editor}
                    options={toolbarOptions}
                    styles={toolbarStyles}
                    theme={dark ? "dark" : "light"}
                    custom={{
                        handler: handleToolbarActions,
                        actions: props.allowResize
                            ? toolbarResizeActions
                            : undefined,
                        icons: props.allowResize
                            ? toolbarResizeIcons
                            : undefined,
                    }}
                />
                <QuillEditor
                    style={[styles.editor, props.style]}
                    quill={quill}
                    theme={theme}
                    onSelectionChange={handleSelectionChange}
                    ref={editor}
                    {...props}
                    customStyles={customStyles}
                    webview={webViewProps}
                />
            </View>
        );
    },
);

const useStyles = createStyleSheetHook(() => {
    return StyleSheet.create({
        editor: {
            flexGrow: 1,
            flex: 1,
            padding: 0,
        },
    });
});

Video: https://github.com/imnapo/react-native-cn-quill/assets/155077894/8004ea60-a87c-4844-a095-c2b74761422e

@imnapo I apologize for tagging, but as an owner of this repository, do you have any idea how to solve this issue?

ashirkhan94 commented 9 months ago

Hi @dr-zr , Facing the same Issue in Android version 10 and lower But for me, it works in Android version 12. Any Update on this?

nhuthuynh195 commented 1 month ago

Death lib