@@ -278,7 +278,7 @@ export function Suggestion<I = any, TSelected = any>({
278278 const next = this . key ?. getState ( view . state )
279279
280280 // See how the state changed
281- const moved = prev . active && next . active && prev . range . from !== next . range . from
281+ const moved = prev . active && next . active && ! ! prev . range . from && prev . range . from !== next . range . from
282282 const started = ! prev . active && next . active
283283 const stopped = prev . active && ! next . active
284284 const changed = ! started && ! stopped && prev . query !== next . query
@@ -300,8 +300,18 @@ export function Suggestion<I = any, TSelected = any>({
300300 range : state . range ,
301301 query : state . query ,
302302 text : state . text ,
303- items : [ ] ,
303+ items : handleChange || handleStart
304+ ? await items ( {
305+ editor,
306+ query : state . query ,
307+ } )
308+ : [ ] ,
304309 command : commandProps => {
310+ if ( ! commandProps ) {
311+ dispatchExit ( editor . view , pluginKey )
312+ return
313+ }
314+
305315 return command ( {
306316 editor,
307317 range : state . range ,
@@ -312,31 +322,13 @@ export function Suggestion<I = any, TSelected = any>({
312322 clientRect : clientRectFor ( view , decorationNode ) ,
313323 }
314324
315- if ( handleStart ) {
316- renderer ?. onBeforeStart ?.( props )
317- }
318-
319- if ( handleChange ) {
320- renderer ?. onBeforeUpdate ?.( props )
321- }
322-
323- if ( handleChange || handleStart ) {
324- props . items = await items ( {
325- editor,
326- query : state . query ,
327- } )
328- }
329-
330325 if ( handleExit ) {
331- renderer ?. onExit ?.( props )
332- }
333-
334- if ( handleChange ) {
335- renderer ?. onUpdate ?.( props )
336- }
337-
338- if ( handleStart ) {
339- renderer ?. onStart ?.( props )
326+ dispatchExit ( view , pluginKey )
327+ return renderer ?. onExit ?.( props )
328+ } if ( handleChange ) {
329+ return renderer ?. onUpdate ?.( props )
330+ } if ( handleStart ) {
331+ return renderer ?. onStart ?.( props )
340332 }
341333 } ,
342334
@@ -355,13 +347,15 @@ export function Suggestion<I = any, TSelected = any>({
355347 init ( ) {
356348 const state : {
357349 active : boolean
350+ key : null | string
358351 range : Range
359352 query : null | string
360353 text : null | string
361354 composing : boolean
362355 decorationId ?: string | null
363356 } = {
364357 active : false ,
358+ key : null ,
365359 range : {
366360 from : 0 ,
367361 to : 0 ,
@@ -380,25 +374,40 @@ export function Suggestion<I = any, TSelected = any>({
380374 const { composing } = editor . view
381375 const { selection } = transaction
382376 const { empty, from } = selection
383- const next = { ...prev }
377+ let next = { ...prev }
384378
385379 // If a transaction carries the exit meta for this plugin, immediately
386380 // deactivate the suggestion. This allows metadata-only transactions
387381 // (dispatched by escape or programmatic exit) to deterministically
388382 // clear decorations without changing the document.
389383 const meta = transaction . getMeta ( pluginKey )
390- if ( meta && meta . exit ) {
391- next . active = false
392- next . decorationId = null
393- next . range = { from : 0 , to : 0 }
394- next . query = null
395- next . text = null
384+ if ( meta ) {
385+ if ( meta . exit ) {
386+ next . active = false
387+ next . decorationId = null
388+ next . range = { from : 0 , to : 0 }
389+ next . query = null
390+ next . text = null
396391
397- return next
392+ return next
393+ }
394+
395+ if ( meta . state ) {
396+ next = meta . state
397+ }
398398 }
399399
400400 next . composing = composing
401401
402+ // Don't advance with suggestion if not active and not the open character.
403+ if ( ! next . active && next . key !== char ) {
404+ return next
405+ }
406+ if ( next . active && next . key === char ) {
407+ next . key = null
408+ return next
409+ }
410+
402411 // We can only be suggesting if the view is editable, and:
403412 // * there is no selection, or
404413 // * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)
@@ -458,6 +467,16 @@ export function Suggestion<I = any, TSelected = any>({
458467 handleKeyDown ( view , event ) {
459468 const { active, range } = plugin . getState ( view . state )
460469
470+ // Set state and handle start
471+ if ( ! active && event . key === char ) {
472+ const state = plugin . getState ( view . state )
473+ const updatedState = { ...state }
474+ updatedState . active = true
475+ updatedState . key = event . key
476+ view . dispatch ( view . state . tr . setMeta ( pluginKey , { state : updatedState } ) )
477+ return
478+ }
479+
461480 if ( ! active ) {
462481 return false
463482 }
0 commit comments