From b8fb1243b3c4db31bb2a2a15b813053c2bde8695 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:46:12 +0600 Subject: [PATCH 1/9] Add auto space after mention node --- src/components/editor-input/editor-input.jsx | 2 + .../mention-plugin/mention-plugin.jsx | 67 ++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/components/editor-input/editor-input.jsx b/src/components/editor-input/editor-input.jsx index 6a6f3572..a7ec64e7 100644 --- a/src/components/editor-input/editor-input.jsx +++ b/src/components/editor-input/editor-input.jsx @@ -60,6 +60,7 @@ const EditorInputComponent = ( menuItemComponent, className, disabled = false, + autoSpaceAfterMention = true, }, ref ) => { @@ -123,6 +124,7 @@ const EditorInputComponent = ( by={ by } optionsArray={ options } trigger={ trigger } + autoSpace={ autoSpaceAfterMention } /> { // Define PUNCTUATION and other necessary variables inside the component const PUNCTUATION = @@ -96,6 +115,50 @@ const MentionPlugin = ( { return results.map( ( result ) => new OptionItem( result ) ); }, [ editor, results ] ); + const handleAutoSpaceAfterMention = useCallback((event) => { + if ( ! autoSpace ) { + return false; + } + const {key, ctrlKey, metaKey } = event; + + if ( ctrlKey || metaKey || key === ' ' || key.length > 1 ) { + return false; + } + const selection = $getSelection(editor); + const {focus, anchor} = selection; + const [node] = selection.getNodes() + + if ( + !anchor || + !focus || + anchor?.key !== focus?.key || + anchor?.offset !== focus?.offset || + !node + ) { + return false; + } + + if( $isMentionNode(node) ) { + const textNode = $createTextNode(' '); + node.insertAfter(textNode); + } + }, [editor, trigger]); + + + useEffect(() => { + if (!editor) { + return; + } + + return mergeRegister( + editor.registerCommand( + KEY_DOWN_COMMAND, + handleAutoSpaceAfterMention, + COMMAND_PRIORITY_LOW + ), + ) + }, [editor, handleAutoSpaceAfterMention]); + return ( Date: Tue, 15 Oct 2024 12:07:43 +0600 Subject: [PATCH 2/9] chore: Lint --- .../mention-plugin/mention-plugin.jsx | 87 +++++++++---------- src/components/menu-item/menu-item.jsx | 4 +- src/components/toaster/utils.jsx | 27 ++++-- 3 files changed, 62 insertions(+), 56 deletions(-) diff --git a/src/components/editor-input/mention-plugin/mention-plugin.jsx b/src/components/editor-input/mention-plugin/mention-plugin.jsx index 81833fa1..2eee121b 100644 --- a/src/components/editor-input/mention-plugin/mention-plugin.jsx +++ b/src/components/editor-input/mention-plugin/mention-plugin.jsx @@ -6,22 +6,11 @@ import OptionItem from './mention-option-item'; import useMentionLookupService from './mention-hooks'; import EditorCombobox from './mention-combobox'; import { - $createTextNode, - $getSelection, - $isNodeSelection, - $isParagraphNode, - $setSelection, - BLUR_COMMAND, - BaseSelection, - COMMAND_PRIORITY_LOW, - COMMAND_PRIORITY_NORMAL, - KEY_BACKSPACE_COMMAND, - KEY_DOWN_COMMAND, - KEY_SPACE_COMMAND, - PASTE_COMMAND, - SELECTION_CHANGE_COMMAND, - TextNode, -} from "lexical"; + $createTextNode, + $getSelection, + COMMAND_PRIORITY_LOW, + KEY_DOWN_COMMAND, +} from 'lexical'; import { mergeRegister } from '@lexical/utils'; const MentionPlugin = ( { @@ -115,38 +104,40 @@ const MentionPlugin = ( { return results.map( ( result ) => new OptionItem( result ) ); }, [ editor, results ] ); - const handleAutoSpaceAfterMention = useCallback((event) => { - if ( ! autoSpace ) { - return false; - } - const {key, ctrlKey, metaKey } = event; + const handleAutoSpaceAfterMention = useCallback( + ( event ) => { + if ( ! autoSpace ) { + return false; + } + const { key, ctrlKey, metaKey } = event; - if ( ctrlKey || metaKey || key === ' ' || key.length > 1 ) { - return false; - } - const selection = $getSelection(editor); - const {focus, anchor} = selection; - const [node] = selection.getNodes() - - if ( - !anchor || - !focus || - anchor?.key !== focus?.key || - anchor?.offset !== focus?.offset || - !node - ) { - return false; - } - - if( $isMentionNode(node) ) { - const textNode = $createTextNode(' '); - node.insertAfter(textNode); - } - }, [editor, trigger]); + if ( ctrlKey || metaKey || key === ' ' || key.length > 1 ) { + return false; + } + const selection = $getSelection( editor ); + const { focus, anchor } = selection; + const [ node ] = selection.getNodes(); + + if ( + ! anchor || + ! focus || + anchor?.key !== focus?.key || + anchor?.offset !== focus?.offset || + ! node + ) { + return false; + } + if ( $isMentionNode( node ) ) { + const textNode = $createTextNode( ' ' ); + node.insertAfter( textNode ); + } + }, + [ editor, trigger ] + ); - useEffect(() => { - if (!editor) { + useEffect( () => { + if ( ! editor ) { return; } @@ -155,9 +146,9 @@ const MentionPlugin = ( { KEY_DOWN_COMMAND, handleAutoSpaceAfterMention, COMMAND_PRIORITY_LOW - ), - ) - }, [editor, handleAutoSpaceAfterMention]); + ) + ); + }, [ editor, handleAutoSpaceAfterMention ] ); return ( - /* eslint-enable */ - ) : null } + ) : /* eslint-enable */ + null } ) } diff --git a/src/components/toaster/utils.jsx b/src/components/toaster/utils.jsx index b7992f88..fe7c6fc7 100644 --- a/src/components/toaster/utils.jsx +++ b/src/components/toaster/utils.jsx @@ -7,13 +7,28 @@ const DEFAULT_THEME = 'light'; const DEFAULT_VARIANT = 'neutral'; const DEFAULT_ACTION_TYPE = 'button'; -export const getIconColor = ( { theme = DEFAULT_THEME, variant = DEFAULT_VARIANT } ) => { +export const getIconColor = ( { + theme = DEFAULT_THEME, + variant = DEFAULT_VARIANT, +} ) => { let color = theme === 'light' ? 'text-icon-secondary' : 'text-icon-inverse'; const variantClasses = { - info: theme === 'light' ? 'text-support-info' : 'text-support-info-inverse', - success: theme === 'light' ? 'text-support-success' : 'text-support-success-inverse', - warning: theme === 'light' ? 'text-support-warning' : 'text-support-warning-inverse', - error: theme === 'light' ? 'text-support-error' : 'text-support-error-inverse', + info: + theme === 'light' + ? 'text-support-info' + : 'text-support-info-inverse', + success: + theme === 'light' + ? 'text-support-success' + : 'text-support-success-inverse', + warning: + theme === 'light' + ? 'text-support-warning' + : 'text-support-warning-inverse', + error: + theme === 'light' + ? 'text-support-error' + : 'text-support-error-inverse', }; color = variantClasses[ variant ] || color; @@ -26,7 +41,7 @@ export const getIcon = ( { variant = DEFAULT_VARIANT, } ) => { const commonClasses = '[&>svg]:h-5 [&>svg]:w-5'; - let color = getIconColor( { theme, variant } ); + const color = getIconColor( { theme, variant } ); if ( icon && isValidElement( icon ) ) { const updatedIcon = cloneElement( icon, { From c3f8305b0a2e5a0dc84b8cf97cab254c793a448a Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:14:03 +0600 Subject: [PATCH 3/9] Updated the readme Added new prop `autoSpaceAfterMention` details. --- src/components/editor-input/readme.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/editor-input/readme.md b/src/components/editor-input/readme.md index 92a84369..9969eb11 100644 --- a/src/components/editor-input/readme.md +++ b/src/components/editor-input/readme.md @@ -86,6 +86,11 @@ The Editor Input component is a text input field that allows users to input text - **Default:** `false` - **Description:** If `true`, the editor input field will be focused when the component is mounted. +### autoSpaceAfterMention +- **Type:** `boolean` +- **Default:** `true` +- **Description:** If `true`, a space will be added after the mention/tag node. If any other character is pressed after selecting a mention/tag option, the space will be added automatically. + ### className - **Type:** `string` - **Default:** `""` From e946f575fac8e98ac62cadf557b90ca4a0fddea8 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Tue, 15 Oct 2024 13:26:03 +0600 Subject: [PATCH 4/9] Don't apply auto-space if removed --- .../mention-plugin/mention-plugin.jsx | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/components/editor-input/mention-plugin/mention-plugin.jsx b/src/components/editor-input/mention-plugin/mention-plugin.jsx index 2eee121b..41397a36 100644 --- a/src/components/editor-input/mention-plugin/mention-plugin.jsx +++ b/src/components/editor-input/mention-plugin/mention-plugin.jsx @@ -1,4 +1,4 @@ -import { useCallback, useState, useMemo, useEffect } from 'react'; +import { useCallback, useState, useMemo, useEffect, useRef } from 'react'; import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { LexicalTypeaheadMenuPlugin } from '@lexical/react/LexicalTypeaheadMenuPlugin'; import { $createMentionNode, $isMentionNode } from './mention-node'; @@ -10,6 +10,7 @@ import { $getSelection, COMMAND_PRIORITY_LOW, KEY_DOWN_COMMAND, + KEY_BACKSPACE_COMMAND, } from 'lexical'; import { mergeRegister } from '@lexical/utils'; @@ -22,6 +23,7 @@ const MentionPlugin = ( { menuItemComponent: MenuItemComponent = EditorCombobox.Item, autoSpace = true, } ) => { + const autoSpaceTempOff = useRef( false ); // Define PUNCTUATION and other necessary variables inside the component const PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;'; @@ -111,7 +113,16 @@ const MentionPlugin = ( { } const { key, ctrlKey, metaKey } = event; - if ( ctrlKey || metaKey || key === ' ' || key.length > 1 ) { + if ( + ctrlKey || + metaKey || + key === ' ' || + key.length > 1 || + autoSpaceTempOff.current + ) { + if ( autoSpaceTempOff.current ) { + autoSpaceTempOff.current = false; + } return false; } const selection = $getSelection( editor ); @@ -136,6 +147,16 @@ const MentionPlugin = ( { [ editor, trigger ] ); + const turnOffAutoSpaceIfNecessary = useCallback( + ( event ) => { + const { key } = event; + if ( key === 'Backspace' ) { + autoSpaceTempOff.current = true; + } + }, + [ autoSpaceTempOff ] + ); + useEffect( () => { if ( ! editor ) { return; @@ -146,6 +167,11 @@ const MentionPlugin = ( { KEY_DOWN_COMMAND, handleAutoSpaceAfterMention, COMMAND_PRIORITY_LOW + ), + editor.registerCommand( + KEY_BACKSPACE_COMMAND, + turnOffAutoSpaceIfNecessary, + COMMAND_PRIORITY_LOW ) ); }, [ editor, handleAutoSpaceAfterMention ] ); From 1130e0d8f9ed7389529eccf9dd3627ec8a9d2c99 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:18:11 +0600 Subject: [PATCH 5/9] Set default auto space flag value to false --- src/components/editor-input/editor-input.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/editor-input/editor-input.jsx b/src/components/editor-input/editor-input.jsx index a7ec64e7..7bc18046 100644 --- a/src/components/editor-input/editor-input.jsx +++ b/src/components/editor-input/editor-input.jsx @@ -60,7 +60,7 @@ const EditorInputComponent = ( menuItemComponent, className, disabled = false, - autoSpaceAfterMention = true, + autoSpaceAfterMention = false, }, ref ) => { From d40ec77f07ec2eb7684dc95ca6be74d1f688877d Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:18:40 +0600 Subject: [PATCH 6/9] Added 2px margin around the mention node --- .../editor-input/mention-plugin/mention-component.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/editor-input/mention-plugin/mention-component.jsx b/src/components/editor-input/mention-plugin/mention-component.jsx index c7d1c50d..ca909be8 100644 --- a/src/components/editor-input/mention-plugin/mention-component.jsx +++ b/src/components/editor-input/mention-plugin/mention-component.jsx @@ -43,7 +43,7 @@ const MentionComponent = ( { data, by, size, nodeKey } ) => { return ( Date: Tue, 15 Oct 2024 14:19:04 +0600 Subject: [PATCH 7/9] Added auto space prop details in the storybook --- .../editor-input/editor-input.stories.jsx | 20 +++++++++++++++++++ .../mention-plugin/mention-plugin.jsx | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/components/editor-input/editor-input.stories.jsx b/src/components/editor-input/editor-input.stories.jsx index cc5f0b72..f0377ea7 100644 --- a/src/components/editor-input/editor-input.stories.jsx +++ b/src/components/editor-input/editor-input.stories.jsx @@ -56,6 +56,16 @@ export default { defaultValue: { summary: false }, }, }, + autoSpaceAfterMention: { + name: 'autoSpaceAfterMention', + description: + 'Defines if the editor input should add a space after selecting a mention/tag option.', + control: 'boolean', + table: { + type: { summary: 'boolean' }, + defaultValue: { summary: false }, + }, + }, onChange: { name: 'On Change', description: @@ -139,6 +149,16 @@ const options = [ 'Pink', ]; +export const Default = { + args: { + size: 'md', + autoSpaceAfterMention: false, + autoFocus: false, + options, + onChange: ( editorState ) => editorState.toJSON(), + }, +} + // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args export const Small = { args: { diff --git a/src/components/editor-input/mention-plugin/mention-plugin.jsx b/src/components/editor-input/mention-plugin/mention-plugin.jsx index 41397a36..b293f69e 100644 --- a/src/components/editor-input/mention-plugin/mention-plugin.jsx +++ b/src/components/editor-input/mention-plugin/mention-plugin.jsx @@ -144,7 +144,7 @@ const MentionPlugin = ( { node.insertAfter( textNode ); } }, - [ editor, trigger ] + [ editor, trigger, autoSpace ] ); const turnOffAutoSpaceIfNecessary = useCallback( From 2f339b4245e96526ef098496f7a1cb0ea63aea2f Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:19:36 +0600 Subject: [PATCH 8/9] chore: Lint --- src/components/editor-input/editor-input.stories.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/editor-input/editor-input.stories.jsx b/src/components/editor-input/editor-input.stories.jsx index f0377ea7..90d52f00 100644 --- a/src/components/editor-input/editor-input.stories.jsx +++ b/src/components/editor-input/editor-input.stories.jsx @@ -157,7 +157,7 @@ export const Default = { options, onChange: ( editorState ) => editorState.toJSON(), }, -} +}; // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args export const Small = { From 43ae40d7906b3c1ed84676bc571582cd5acba65b Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:10:27 +0600 Subject: [PATCH 9/9] Added wrapperClassName prop --- src/components/editor-input/editor-input.jsx | 4 +++- src/components/editor-input/editor-input.stories.jsx | 9 +++++++++ src/components/editor-input/readme.md | 5 +++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/components/editor-input/editor-input.jsx b/src/components/editor-input/editor-input.jsx index 7bc18046..8764cd35 100644 --- a/src/components/editor-input/editor-input.jsx +++ b/src/components/editor-input/editor-input.jsx @@ -59,6 +59,7 @@ const EditorInputComponent = ( menuComponent, menuItemComponent, className, + wrapperClassName, disabled = false, autoSpaceAfterMention = false, }, @@ -95,7 +96,8 @@ const EditorInputComponent = ( 'relative w-full', editorCommonClassNames, editorInputClassNames[ size ], - disabled && editorDisabledClassNames + disabled && editorDisabledClassNames, + wrapperClassName ) } > diff --git a/src/components/editor-input/editor-input.stories.jsx b/src/components/editor-input/editor-input.stories.jsx index 90d52f00..fd3e47aa 100644 --- a/src/components/editor-input/editor-input.stories.jsx +++ b/src/components/editor-input/editor-input.stories.jsx @@ -128,6 +128,15 @@ export default { defaultValue: { summary: '' }, }, }, + wrapperClassName: { + name: 'wrapperClassName', + description: 'Custom class name to be added to the editor input wrapper.', + control: 'text', + table: { + type: { summary: 'string' }, + defaultValue: { summary: '' }, + }, + }, }, decorators: [ ( Story ) => ( diff --git a/src/components/editor-input/readme.md b/src/components/editor-input/readme.md index 9969eb11..776781e8 100644 --- a/src/components/editor-input/readme.md +++ b/src/components/editor-input/readme.md @@ -103,6 +103,11 @@ The Editor Input component is a text input field that allows users to input text /> ``` +### wrapperClassName +- **Type:** `string` +- **Default:** `""` +- **Description:** Additional classes to be added to the editor input wrapper. + ### disabled - **Type:** `boolean` - **Default:** `false`