3
3
$createParagraphNode ,
4
4
$getSelection ,
5
5
$isDecoratorNode ,
6
- COMMAND_PRIORITY_LOW ,
6
+ COMMAND_PRIORITY_LOW , KEY_ARROW_DOWN_COMMAND ,
7
7
KEY_BACKSPACE_COMMAND ,
8
8
KEY_DELETE_COMMAND ,
9
9
KEY_ENTER_COMMAND , KEY_TAB_COMMAND ,
@@ -13,9 +13,10 @@ import {
13
13
import { $isImageNode } from "@lexical/rich-text/LexicalImageNode" ;
14
14
import { $isMediaNode } from "@lexical/rich-text/LexicalMediaNode" ;
15
15
import { getLastSelection } from "../utils/selection" ;
16
- import { $getNearestNodeBlockParent } from "../utils/nodes" ;
16
+ import { $getNearestNodeBlockParent , $getParentOfType } from "../utils/nodes" ;
17
17
import { $setInsetForSelection } from "../utils/lists" ;
18
18
import { $isListItemNode } from "@lexical/list" ;
19
+ import { $isDetailsNode , DetailsNode } from "@lexical/rich-text/LexicalDetailsNode" ;
19
20
20
21
function isSingleSelectedNode ( nodes : LexicalNode [ ] ) : boolean {
21
22
if ( nodes . length === 1 ) {
@@ -28,6 +29,10 @@ function isSingleSelectedNode(nodes: LexicalNode[]): boolean {
28
29
return false ;
29
30
}
30
31
32
+ /**
33
+ * Delete the current node in the selection if the selection contains a single
34
+ * selected node (like image, media etc...).
35
+ */
31
36
function deleteSingleSelectedNode ( editor : LexicalEditor ) {
32
37
const selectionNodes = getLastSelection ( editor ) ?. getNodes ( ) || [ ] ;
33
38
if ( isSingleSelectedNode ( selectionNodes ) ) {
@@ -37,6 +42,10 @@ function deleteSingleSelectedNode(editor: LexicalEditor) {
37
42
}
38
43
}
39
44
45
+ /**
46
+ * Insert a new empty node after the selection if the selection contains a single
47
+ * selected node (like image, media etc...).
48
+ */
40
49
function insertAfterSingleSelectedNode ( editor : LexicalEditor , event : KeyboardEvent | null ) : boolean {
41
50
const selectionNodes = getLastSelection ( editor ) ?. getNodes ( ) || [ ] ;
42
51
if ( isSingleSelectedNode ( selectionNodes ) ) {
@@ -58,6 +67,94 @@ function insertAfterSingleSelectedNode(editor: LexicalEditor, event: KeyboardEve
58
67
return false ;
59
68
}
60
69
70
+ /**
71
+ * Insert a new node after a details node, if inside a details node that's
72
+ * the last element, and if the cursor is at the last block within the details node.
73
+ */
74
+ function insertAfterDetails ( editor : LexicalEditor , event : KeyboardEvent | null ) : boolean {
75
+ const scenario = getDetailsScenario ( editor ) ;
76
+ if ( scenario === null || scenario . detailsSibling ) {
77
+ return false ;
78
+ }
79
+
80
+ editor . update ( ( ) => {
81
+ const newParagraph = $createParagraphNode ( ) ;
82
+ scenario . parentDetails . insertAfter ( newParagraph ) ;
83
+ newParagraph . select ( ) ;
84
+ } ) ;
85
+ event ?. preventDefault ( ) ;
86
+
87
+ return true ;
88
+ }
89
+
90
+ /**
91
+ * If within a details block, move after it, creating a new node if required, if we're on
92
+ * the last empty block element within the details node.
93
+ */
94
+ function moveAfterDetailsOnEmptyLine ( editor : LexicalEditor , event : KeyboardEvent | null ) : boolean {
95
+ const scenario = getDetailsScenario ( editor ) ;
96
+ if ( scenario === null ) {
97
+ return false ;
98
+ }
99
+
100
+ if ( scenario . parentBlock . getTextContent ( ) !== '' ) {
101
+ return false ;
102
+ }
103
+
104
+ event ?. preventDefault ( )
105
+
106
+ const nextSibling = scenario . parentDetails . getNextSibling ( ) ;
107
+ editor . update ( ( ) => {
108
+ if ( nextSibling ) {
109
+ nextSibling . selectStart ( ) ;
110
+ } else {
111
+ const newParagraph = $createParagraphNode ( ) ;
112
+ scenario . parentDetails . insertAfter ( newParagraph ) ;
113
+ newParagraph . select ( ) ;
114
+ }
115
+ scenario . parentBlock . remove ( ) ;
116
+ } ) ;
117
+
118
+ return true ;
119
+ }
120
+
121
+ /**
122
+ * Get the common nodes used for a details node scenario, relative to current selection.
123
+ * Returns null if not found, or if the parent block is not the last in the parent details node.
124
+ */
125
+ function getDetailsScenario ( editor : LexicalEditor ) : {
126
+ parentDetails : DetailsNode ;
127
+ parentBlock : LexicalNode ;
128
+ detailsSibling : LexicalNode | null
129
+ } | null {
130
+ const selection = getLastSelection ( editor ) ;
131
+ const firstNode = selection ?. getNodes ( ) [ 0 ] ;
132
+ if ( ! firstNode ) {
133
+ return null ;
134
+ }
135
+
136
+ const block = $getNearestNodeBlockParent ( firstNode ) ;
137
+ const details = $getParentOfType ( firstNode , $isDetailsNode ) ;
138
+ if ( ! $isDetailsNode ( details ) || block === null ) {
139
+ return null ;
140
+ }
141
+
142
+ if ( block . getKey ( ) !== details . getLastChild ( ) ?. getKey ( ) ) {
143
+ return null ;
144
+ }
145
+
146
+ const nextSibling = details . getNextSibling ( ) ;
147
+ return {
148
+ parentDetails : details ,
149
+ parentBlock : block ,
150
+ detailsSibling : nextSibling ,
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Inset the nodes within selection when a range of nodes is selected
156
+ * or if a list node is selected.
157
+ */
61
158
function handleInsetOnTab ( editor : LexicalEditor , event : KeyboardEvent | null ) : boolean {
62
159
const change = event ?. shiftKey ? - 40 : 40 ;
63
160
const selection = $getSelection ( ) ;
@@ -85,17 +182,23 @@ export function registerKeyboardHandling(context: EditorUiContext): () => void {
85
182
} , COMMAND_PRIORITY_LOW ) ;
86
183
87
184
const unregisterEnter = context . editor . registerCommand ( KEY_ENTER_COMMAND , ( event ) : boolean => {
88
- return insertAfterSingleSelectedNode ( context . editor , event ) ;
185
+ return insertAfterSingleSelectedNode ( context . editor , event )
186
+ || moveAfterDetailsOnEmptyLine ( context . editor , event ) ;
89
187
} , COMMAND_PRIORITY_LOW ) ;
90
188
91
189
const unregisterTab = context . editor . registerCommand ( KEY_TAB_COMMAND , ( event ) : boolean => {
92
190
return handleInsetOnTab ( context . editor , event ) ;
93
191
} , COMMAND_PRIORITY_LOW ) ;
94
192
193
+ const unregisterDown = context . editor . registerCommand ( KEY_ARROW_DOWN_COMMAND , ( event ) : boolean => {
194
+ return insertAfterDetails ( context . editor , event ) ;
195
+ } , COMMAND_PRIORITY_LOW ) ;
196
+
95
197
return ( ) => {
96
198
unregisterBackspace ( ) ;
97
199
unregisterDelete ( ) ;
98
200
unregisterEnter ( ) ;
99
201
unregisterTab ( ) ;
202
+ unregisterDown ( ) ;
100
203
} ;
101
204
}
0 commit comments