lexical
lexical copied to clipboard
Bug: Font size highlight removed on input
Lexical version: v0.13.1
Steps To Reproduce
- Highlight a any text you want to resize
- Click the font size input (see that the current highlight gets removed)
- Change the input, e.g 50
- Press enter, the text still resizes even though the highlight is removed
https://github.com/facebook/lexical/assets/52998821/917fdfbd-4e9f-4acf-8e0e-6380f26d3df5
The current behavior
When trying to resize using the font size input, the highlight gets removed and still does the resizing on enter
The expected behavior
Maybe this is hard but can we keep the current highlight of the text if we have control of it when we change the font size via the input? It's the same behavior when we change the sizes via the +/-
buttons
I think this is important feature update.
I am trying to implement saving of highlight on my own, but I am faced with bugs everytime I fix previous things:
First of all $patchStyleText
is doing focus on editor everytime it is called.
I tried to do additional highlight on Editor blur, but it is just doing fucus back.
Then I added $setSelection(null)
to remove focus from editor, but in this case I can't do normal font-size change that is written in playground.
const setHighlightData = useCallback(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
setHighlightDataCallback(selection);
$patchStyleText(selection, { 'background-color': 'red' });
$setSelection(null);
}
}, []);
editor.registerCommand(
BLUR_COMMAND,
() => {
setHighlightData();
return false;
},
COMMAND_PRIORITY_LOW,
);
So, how I made it work closely to google-docs:
I added HighlightPlugin:
import { useLayoutEffect } from 'react';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { mergeRegister } from '@lexical/utils';
import { BLUR_COMMAND, COMMAND_PRIORITY_LOW, FOCUS_COMMAND } from 'lexical';
import { useHighlightPluginContext } from './context';
export const HighlightPlugin = () => {
const [editor] = useLexicalComposerContext();
const { setHighlightData, cleanHighlightData } = useHighlightPluginContext();
useLayoutEffect(
() =>
mergeRegister(
editor.registerCommand(
FOCUS_COMMAND,
() => {
cleanHighlightData();
return false;
},
COMMAND_PRIORITY_LOW,
),
editor.registerCommand(
BLUR_COMMAND,
() => {
setHighlightData();
return false;
},
COMMAND_PRIORITY_LOW,
),
),
[editor, cleanHighlightData, setHighlightData],
);
return null;
};
I added HighlightProvider with hook inside of it:
import { useCallback, useState } from 'react';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { $patchStyleText } from '@lexical/selection';
import {
$getSelection,
$isRangeSelection,
$setSelection,
RangeSelection,
} from 'lexical';
export const useHighlightData = () => {
const [editor] = useLexicalComposerContext();
const [highlightedData, setHighlightDataCallback] = useState<
RangeSelection | undefined
>();
const setHighlightData = useCallback(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
setHighlightDataCallback(selection);
$patchStyleText(selection, { 'background-color': '#ACCEF7' });
$setSelection(null);
}
}, []);
const cleanHighlightData = useCallback(() => {
editor.update(() => {
if (editor.isEditable() && highlightedData) {
$patchStyleText(highlightedData.clone(), {
'background-color': 'transparent',
});
setHighlightDataCallback(undefined);
}
});
}, [editor, highlightedData]);
return {
highlightedData,
setHighlightData,
cleanHighlightData,
};
};
After it I modified fontSize hook from playground:
import { KeyboardEvent, useCallback, useEffect, useState } from 'react';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import {
$getSelectionStyleValueForProperty,
$patchStyleText,
} from '@lexical/selection';
import {
$getSelection,
$isRangeSelection,
$setSelection,
COMMAND_PRIORITY_CRITICAL,
SELECTION_CHANGE_COMMAND,
} from 'lexical';
import {
DEFAULT_FONT_SIZE,
MAX_ALLOWED_FONT_SIZE,
MIN_ALLOWED_FONT_SIZE,
PX,
} from '../constants';
import { useHighlightPluginContext } from '../plugins/HighlightPlugin/context';
import { UPDATE_FONT_SIZE_TYPE } from '../types';
import { calculateNextFontSize } from '../utils/calculate-font-size';
const useFontSizeInLexical = (setFontSize: (value: string) => void) => {
const [editor] = useLexicalComposerContext();
const $updateFontSize = useCallback(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
const currentFontSize = $getSelectionStyleValueForProperty(
selection,
'font-size',
`${DEFAULT_FONT_SIZE}${PX}`,
);
setFontSize(currentFontSize.replaceAll(PX, ''));
}
}, [setFontSize]);
useEffect(() => {
return editor.registerCommand(
SELECTION_CHANGE_COMMAND,
() => {
$updateFontSize();
return false;
},
COMMAND_PRIORITY_CRITICAL,
);
}, [editor, $updateFontSize]);
useEffect(() => {
const registerListener = editor.registerUpdateListener(
({ editorState }) => {
editorState.read(() => {
$updateFontSize();
});
},
);
return () => {
registerListener();
};
}, [$updateFontSize, editor]);
};
export const useFontSize = () => {
const [editor] = useLexicalComposerContext();
const [fontSize, setFontSize] = useState<string>(
DEFAULT_FONT_SIZE.toString(),
);
const { highlightedData } = useHighlightPluginContext();
useFontSizeInLexical(setFontSize);
const updateFontSizeInSelection = useCallback(
(newFontSize: string | null, updateType: UPDATE_FONT_SIZE_TYPE | null) => {
const getNextFontSize = (prevFontSize: string | null): string => {
if (!prevFontSize) {
prevFontSize = `${DEFAULT_FONT_SIZE}${PX}`;
}
prevFontSize = prevFontSize.slice(0, -2);
const nextFontSize = calculateNextFontSize(
Number(prevFontSize),
updateType,
);
return `${nextFontSize}${PX}`;
};
editor.update(() => {
if (editor.isEditable()) {
const oldSelection = highlightedData?.clone();
if (oldSelection) {
$patchStyleText(oldSelection, {
'font-size': newFontSize || getNextFontSize,
});
$setSelection(oldSelection);
}
}
});
},
[editor, highlightedData],
);
const handleKeyPress = useCallback(
(e: KeyboardEvent<HTMLInputElement>) => {
const inputValueNumber = Number(fontSize);
if (['e', 'E', '+', '-'].includes(e.key) || isNaN(inputValueNumber)) {
e.preventDefault();
setFontSize('');
return;
}
if (e.key === 'Enter') {
e.preventDefault();
let updatedFontSize = inputValueNumber;
if (inputValueNumber > MAX_ALLOWED_FONT_SIZE) {
updatedFontSize = MAX_ALLOWED_FONT_SIZE;
} else if (inputValueNumber < MIN_ALLOWED_FONT_SIZE) {
updatedFontSize = MIN_ALLOWED_FONT_SIZE;
}
setFontSize(String(updatedFontSize));
updateFontSizeInSelection(`${updatedFontSize}${PX}`, null);
}
},
[fontSize, updateFontSizeInSelection],
);
const handleButtonClick = useCallback(
(updateType: UPDATE_FONT_SIZE_TYPE) => {
if (fontSize !== '') {
const nextFontSize = calculateNextFontSize(
Number(fontSize),
updateType,
);
updateFontSizeInSelection(`${nextFontSize}${PX}`, null);
} else {
updateFontSizeInSelection(null, updateType);
}
},
[fontSize, updateFontSizeInSelection],
);
return {
handleButtonClick,
handleKeyPress,
fontSize,
setFontSize,
};
};
But I am facing with bug in historyPlugin, I can't do normal undo/redo functionality if I used highlight of code.
https://github.com/facebook/lexical/assets/142044988/9e5fe3b5-9834-4873-a94d-82d3c269c0cf
So you can see that history doesn't work correct in video