Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/silly-plants-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": patch
---

Recalculate autocomplete suggestions if the input data changes while the menu is open
51 changes: 40 additions & 11 deletions src/drafts/MarkdownEditor/_MarkdownInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const MarkdownInput = forwardRef<HTMLTextAreaElement, MarkdownInputProps>
forwardedRef,
) => {
const [suggestions, setSuggestions] = useState<Suggestions | null>(null)
const [event, setEvent] = useState<ShowSuggestionsEvent | null>(null)

const {trigger: emojiTrigger, calculateSuggestions: calculateEmojiSuggestions} = useEmojiSuggestions(
emojiSuggestions ?? [],
Expand All @@ -71,17 +72,45 @@ export const MarkdownInput = forwardRef<HTMLTextAreaElement, MarkdownInputProps>
[mentionsTrigger, referencesTrigger, emojiTrigger],
)

const onShowSuggestions = async (event: ShowSuggestionsEvent) => {
setSuggestions('loading')
if (event.trigger.triggerChar === emojiTrigger.triggerChar) {
setSuggestions(await calculateEmojiSuggestions(event.query))
} else if (event.trigger.triggerChar === mentionsTrigger.triggerChar) {
setSuggestions(await calculateMentionSuggestions(event.query))
} else if (event.trigger.triggerChar === referencesTrigger.triggerChar) {
setSuggestions(await calculateReferenceSuggestions(event.query))
}
const lastEventRef = useRef<ShowSuggestionsEvent | null>(null)

const onHideSuggestions = () => {
setEvent(null)
setSuggestions(null) // the effect would do this anyway, but this allows React to batch the update
}

// running the calculation in an effect (rather than in the onShowSuggestions handler) allows us
// to automatically recalculate if the suggestions change while the menu is open
useEffect(() => {
if (!event) {
setSuggestions(null)
return
}

// (prettier vs. eslint conflict)
// eslint-disable-next-line @typescript-eslint/no-extra-semi
;(async function () {
lastEventRef.current = event
setSuggestions('loading')
if (event.trigger.triggerChar === emojiTrigger.triggerChar) {
setSuggestions(await calculateEmojiSuggestions(event.query))
} else if (event.trigger.triggerChar === mentionsTrigger.triggerChar) {
setSuggestions(await calculateMentionSuggestions(event.query))
} else if (event.trigger.triggerChar === referencesTrigger.triggerChar) {
setSuggestions(await calculateReferenceSuggestions(event.query))
}
})()
}, [
event,
calculateEmojiSuggestions,
calculateMentionSuggestions,
calculateReferenceSuggestions,
// The triggers never actually change because they are statically defined
emojiTrigger,
mentionsTrigger,
referencesTrigger,
])

const ref = useRef<HTMLTextAreaElement>(null)
useRefObjectAsForwardedRef(forwardedRef, ref)

Expand All @@ -99,8 +128,8 @@ export const MarkdownInput = forwardRef<HTMLTextAreaElement, MarkdownInputProps>
<InlineAutocomplete
triggers={triggers}
suggestions={suggestions}
onShowSuggestions={e => onShowSuggestions(e)}
onHideSuggestions={() => setSuggestions(null)}
onShowSuggestions={setEvent}
onHideSuggestions={onHideSuggestions}
sx={{flex: 'auto'}}
tabInsertsSuggestions
>
Expand Down
16 changes: 11 additions & 5 deletions src/drafts/MarkdownEditor/suggestions/_useEmojiSuggestions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, {useMemo} from 'react'
import {suggestionsCalculator, UseSuggestionsHook} from '.'
import {ActionList} from '../../../ActionList'
import {Suggestion, Trigger} from '../../InlineAutocomplete'
Expand Down Expand Up @@ -56,7 +56,13 @@ const scoreSuggestion = (query: string, emoji: Emoji): number => {
return score
}

export const useEmojiSuggestions: UseSuggestionsHook<Emoji> = emojis => ({
calculateSuggestions: suggestionsCalculator(emojis, scoreSuggestion, emojiToSugggestion),
trigger,
})
export const useEmojiSuggestions: UseSuggestionsHook<Emoji> = emojis => {
const calculateSuggestions = useMemo(
() => suggestionsCalculator(emojis, scoreSuggestion, emojiToSugggestion),
[emojis],
)
return {
calculateSuggestions,
trigger,
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {score} from 'fzy.js'
import React from 'react'
import React, {useMemo} from 'react'
import {suggestionsCalculator, UseSuggestionsHook} from '.'
import {ActionList} from '../../../ActionList'
import {Suggestion, Trigger} from '../../InlineAutocomplete'
Expand Down Expand Up @@ -37,7 +37,13 @@ const scoreSuggestion = (query: string, mentionable: Mentionable): number => {
return fzyScore
}

export const useMentionSuggestions: UseSuggestionsHook<Mentionable> = mentionables => ({
calculateSuggestions: suggestionsCalculator(mentionables, scoreSuggestion, mentionableToSuggestion),
trigger,
})
export const useMentionSuggestions: UseSuggestionsHook<Mentionable> = mentionables => {
const calculateSuggestions = useMemo(
() => suggestionsCalculator(mentionables, scoreSuggestion, mentionableToSuggestion),
[mentionables],
)
return {
calculateSuggestions,
trigger,
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, {useMemo} from 'react'
import {suggestionsCalculator, UseSuggestionsHook} from '.'
import {ActionList} from '../../../ActionList'
import {Suggestion, Trigger} from '../../InlineAutocomplete'
Expand Down Expand Up @@ -51,10 +51,16 @@ const scoreSuggestion = (query: string, reference: Reference): number => {
return fzyScore === Infinity ? -Infinity : fzyScore
}

export const useReferenceSuggestions: UseSuggestionsHook<Reference> = references => ({
calculateSuggestions: async (query: string) => {
if (/^\d+\s/.test(query)) return [] // don't return anything if the query is in the form #123 ..., assuming they already have the number they want
return suggestionsCalculator(references, scoreSuggestion, referenceToSuggestion)(query)
},
trigger,
})
export const useReferenceSuggestions: UseSuggestionsHook<Reference> = references => {
const calculateSuggestions = useMemo(() => {
const calculator = suggestionsCalculator(references, scoreSuggestion, referenceToSuggestion)
return async (query: string) => {
if (/^\d+\s/.test(query)) return [] // don't return anything if the query is in the form #123 ..., assuming they already have the number they want
return calculator(query)
}
}, [references])
return {
calculateSuggestions,
trigger,
}
}