@portabletext/plugin-emoji-picker 3.0.22 â 3.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -12
- package/src/create-match-emojis.ts +0 -105
- package/src/emoji-picker-machine.tsx +0 -1080
- package/src/emoji-picker.feature +0 -264
- package/src/emoji-picker.test.tsx +0 -88
- package/src/global.d.ts +0 -4
- package/src/index.ts +0 -3
- package/src/match-emojis.ts +0 -22
- package/src/use-emoji-picker.ts +0 -187
package/src/emoji-picker.feature
DELETED
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
Feature: Emoji Picker
|
|
2
|
-
|
|
3
|
-
Scenario Outline: Picking a direct hit
|
|
4
|
-
When the editor is focused
|
|
5
|
-
And <initial text> is inserted
|
|
6
|
-
And <inserted text> is inserted
|
|
7
|
-
Then the text is <final text>
|
|
8
|
-
|
|
9
|
-
Examples:
|
|
10
|
-
| initial text | inserted text | final text |
|
|
11
|
-
| "" | ":joy:" | "đ" |
|
|
12
|
-
| ":jo" | "y:" | "đ" |
|
|
13
|
-
| ":joy" | ":" | "đ" |
|
|
14
|
-
|
|
15
|
-
Scenario Outline: Is only triggered when an initial colon is typed
|
|
16
|
-
Given the text <text>
|
|
17
|
-
When the caret is put <position>
|
|
18
|
-
And <inserted text> is inserted
|
|
19
|
-
Then the text is <final text>
|
|
20
|
-
And the keyword is <keyword>
|
|
21
|
-
|
|
22
|
-
Examples:
|
|
23
|
-
| text | position | inserted text | final text | keyword |
|
|
24
|
-
| "" | after "" | ":j" | ":j" | "j" |
|
|
25
|
-
| ":" | after ":" | "j" | ":j" | "" |
|
|
26
|
-
| ":j" | after ":j" | "o" | ":jo" | "" |
|
|
27
|
-
| ":jo" | after ":jo" | ":" | ":jo:" | "" |
|
|
28
|
-
| ":joy" | after ":joy" | ":" | ":joy:" | "" |
|
|
29
|
-
|
|
30
|
-
Scenario: Undo after direct hit
|
|
31
|
-
When ":joy:" is typed
|
|
32
|
-
Then the text is "đ"
|
|
33
|
-
When undo is performed
|
|
34
|
-
Then the text is ":joy:"
|
|
35
|
-
|
|
36
|
-
Scenario: Picking direct hit after undo
|
|
37
|
-
When ":joy:" is typed
|
|
38
|
-
And undo is performed
|
|
39
|
-
And "{Backspace}" is pressed
|
|
40
|
-
And ":" is typed
|
|
41
|
-
Then the text is ":joy:"
|
|
42
|
-
And the keyword is ""
|
|
43
|
-
And the matches are ""
|
|
44
|
-
|
|
45
|
-
Scenario: Picking wrong direct hit
|
|
46
|
-
When ":jo:" is typed
|
|
47
|
-
Then the text is ":jo:"
|
|
48
|
-
And the keyword is "jo"
|
|
49
|
-
And the matches are "đ,đš,đšī¸"
|
|
50
|
-
|
|
51
|
-
Scenario: Colon after wrong direct hit
|
|
52
|
-
When ":jo:" is typed
|
|
53
|
-
And ":" is typed
|
|
54
|
-
Then the text is ":jo::"
|
|
55
|
-
And the keyword is "jo:"
|
|
56
|
-
And the matches are ""
|
|
57
|
-
|
|
58
|
-
Scenario: Picking wrong direct hit after undoing direct hit
|
|
59
|
-
When ":joy:" is typed
|
|
60
|
-
And undo is performed
|
|
61
|
-
And "{Backspace}{Backspace}" is pressed
|
|
62
|
-
And ":" is typed
|
|
63
|
-
Then the text is ":jo:"
|
|
64
|
-
And the keyword is ""
|
|
65
|
-
And the matches are ""
|
|
66
|
-
|
|
67
|
-
Scenario: Two consecutive direct hits
|
|
68
|
-
When ":joy:" is typed
|
|
69
|
-
And ":joy_cat:" is typed
|
|
70
|
-
Then the text is "đđš"
|
|
71
|
-
|
|
72
|
-
Scenario: Picking the closest hit with Enter
|
|
73
|
-
When ":joy" is typed
|
|
74
|
-
And "{Enter}" is pressed
|
|
75
|
-
Then the text is "đ"
|
|
76
|
-
|
|
77
|
-
Scenario: Picking the closest hit with Tab
|
|
78
|
-
When ":joy" is typed
|
|
79
|
-
And "{Tab}" is pressed
|
|
80
|
-
Then the text is "đ"
|
|
81
|
-
|
|
82
|
-
Scenario: Navigating down the list
|
|
83
|
-
When ":joy" is typed
|
|
84
|
-
And "{ArrowDown}" is pressed
|
|
85
|
-
And "{Enter}" is pressed
|
|
86
|
-
Then the text is "đš"
|
|
87
|
-
|
|
88
|
-
Scenario: Aborting on Escape
|
|
89
|
-
When ":joy" is typed
|
|
90
|
-
And "{Escape}" is pressed
|
|
91
|
-
And "{Enter}" is pressed
|
|
92
|
-
Then the text is ":joy|"
|
|
93
|
-
|
|
94
|
-
Scenario: Aborting and forwarding Enter if there is no keyword
|
|
95
|
-
When ":" is typed
|
|
96
|
-
And "{Enter}" is pressed
|
|
97
|
-
Then the text is ":|"
|
|
98
|
-
|
|
99
|
-
Scenario: Aborting Enter if there are no matches
|
|
100
|
-
When ":asdf" is typed
|
|
101
|
-
And "{Enter}" is pressed
|
|
102
|
-
Then the text is ":asdf"
|
|
103
|
-
And the keyword is ""
|
|
104
|
-
|
|
105
|
-
Scenario: Backspacing to narrow search
|
|
106
|
-
When ":joy" is typed
|
|
107
|
-
And "{Backspace}" is pressed
|
|
108
|
-
And "{Enter}" is pressed
|
|
109
|
-
Then the text is "đ"
|
|
110
|
-
|
|
111
|
-
Scenario Outline: Inserting longer trigger text
|
|
112
|
-
Given the text <text>
|
|
113
|
-
When <inserted text> is inserted
|
|
114
|
-
And <new text> is typed
|
|
115
|
-
Then the text is <final text>
|
|
116
|
-
|
|
117
|
-
Examples:
|
|
118
|
-
| text | inserted text | new text | final text |
|
|
119
|
-
| "" | ":" | "joy:" | "đ" |
|
|
120
|
-
| "" | ":j" | "oy:" | "đ" |
|
|
121
|
-
| "" | ":joy" | ":" | "đ" |
|
|
122
|
-
| "" | ":joy:" | ":" | "đ:" |
|
|
123
|
-
|
|
124
|
-
Scenario Outline: Matching inside decorator
|
|
125
|
-
Given the text <text>
|
|
126
|
-
And "strong" around <decorated>
|
|
127
|
-
When the caret is put <position>
|
|
128
|
-
And <keyword> is typed
|
|
129
|
-
Then the text is <final text>
|
|
130
|
-
|
|
131
|
-
Examples:
|
|
132
|
-
| text | decorated | position | keyword | final text |
|
|
133
|
-
| "foo bar baz" | "bar" | before "ar baz" | ":joy:" | "foo ,bđar, baz" |
|
|
134
|
-
| "foo bar baz" | "bar" | before "r baz" | ":joy:" | "foo ,bađr, baz" |
|
|
135
|
-
|
|
136
|
-
Scenario Outline: Triggering at the edge of decorator
|
|
137
|
-
Given the text "foo bar baz"
|
|
138
|
-
And "strong" around "bar"
|
|
139
|
-
When the caret is put <position>
|
|
140
|
-
And ":j" is typed
|
|
141
|
-
Then the keyword is "j"
|
|
142
|
-
|
|
143
|
-
Examples:
|
|
144
|
-
| position |
|
|
145
|
-
| after "foo " |
|
|
146
|
-
| before "bar" |
|
|
147
|
-
| after "bar" |
|
|
148
|
-
| before " baz" |
|
|
149
|
-
|
|
150
|
-
Scenario Outline: Matching at the edge of decorator
|
|
151
|
-
Given the text "foo bar baz"
|
|
152
|
-
And "strong" around "bar"
|
|
153
|
-
When the caret is put <position>
|
|
154
|
-
And ":joy:" is typed
|
|
155
|
-
Then the text is <final text>
|
|
156
|
-
|
|
157
|
-
Examples:
|
|
158
|
-
| position | final text |
|
|
159
|
-
| after "foo " | "foo đ,bar, baz" |
|
|
160
|
-
| before "bar" | "foo đ,bar, baz" |
|
|
161
|
-
| after "bar" | "foo ,barđ, baz" |
|
|
162
|
-
| before " baz" | "foo ,barđ, baz" |
|
|
163
|
-
|
|
164
|
-
Scenario Outline: Matching inside annotation
|
|
165
|
-
Given the text <text>
|
|
166
|
-
And a "link" "l1" around <annotated>
|
|
167
|
-
When the caret is put <position>
|
|
168
|
-
And <keyword> is typed
|
|
169
|
-
Then the text is <final text>
|
|
170
|
-
|
|
171
|
-
Examples:
|
|
172
|
-
| text | annotated | position | keyword | final text |
|
|
173
|
-
| "foo bar baz" | "bar" | before "ar baz" | ":joy:" | "foo ,bđar, baz" |
|
|
174
|
-
| "foo bar baz" | "bar" | before "r baz" | ":joy:" | "foo ,bađr, baz" |
|
|
175
|
-
|
|
176
|
-
Scenario Outline: Triggering at the edge of an annotation
|
|
177
|
-
Given the text "foo bar baz"
|
|
178
|
-
And a "link" "l1" around "bar"
|
|
179
|
-
When the caret is put <position>
|
|
180
|
-
And ":j" is typed
|
|
181
|
-
Then the keyword is "j"
|
|
182
|
-
|
|
183
|
-
Examples:
|
|
184
|
-
| position |
|
|
185
|
-
| after "foo " |
|
|
186
|
-
| before "bar" |
|
|
187
|
-
| after "bar" |
|
|
188
|
-
| before " baz" |
|
|
189
|
-
|
|
190
|
-
Scenario Outline: Matching at the edge of an annotation
|
|
191
|
-
Given the text "foo bar baz"
|
|
192
|
-
And a "link" "l1" around "bar"
|
|
193
|
-
When the caret is put <position>
|
|
194
|
-
And ":joy:" is typed
|
|
195
|
-
Then the text is <final text>
|
|
196
|
-
|
|
197
|
-
Examples:
|
|
198
|
-
| position | final text |
|
|
199
|
-
| after "foo " | "foo đ,bar, baz" |
|
|
200
|
-
| before "bar" | "foo đ,bar, baz" |
|
|
201
|
-
| after "bar" | "foo ,bar,đ baz" |
|
|
202
|
-
| before " baz" | "foo ,bar,đ baz" |
|
|
203
|
-
|
|
204
|
-
Scenario Outline: Typing before the colon dismisses the emoji picker
|
|
205
|
-
Given the text <text>
|
|
206
|
-
When <inserted text> is typed
|
|
207
|
-
And <button> is pressed
|
|
208
|
-
And <new text> is typed
|
|
209
|
-
Then the keyword is ""
|
|
210
|
-
|
|
211
|
-
Examples:
|
|
212
|
-
| text | inserted text | button | new text |
|
|
213
|
-
| "" | ":j" | "{ArrowLeft}{ArrowLeft}" | "f" |
|
|
214
|
-
| "fo" | ":j" | "{ArrowLeft}{ArrowLeft}" | "o" |
|
|
215
|
-
| "" | ":j" | "{ArrowLeft}{ArrowLeft}" | ":" |
|
|
216
|
-
|
|
217
|
-
Scenario Outline: Navigating away from the keyword
|
|
218
|
-
Given the text <text>
|
|
219
|
-
When the caret is put <position>
|
|
220
|
-
And <keyword> is typed
|
|
221
|
-
And <button> is pressed
|
|
222
|
-
And "{Enter}" is pressed
|
|
223
|
-
Then the text is <final text>
|
|
224
|
-
|
|
225
|
-
Examples:
|
|
226
|
-
| text | position | keyword | button | final text |
|
|
227
|
-
| "foo bar baz" | after "foo" | ":j" | "{ArrowRight}" | "foo:j \|bar baz" |
|
|
228
|
-
| "foo bar baz" | after "foo" | ":j" | "{ArrowLeft}{ArrowLeft}{ArrowLeft}" | "fo\|o:j bar baz" |
|
|
229
|
-
|
|
230
|
-
Scenario: Dismissing by pressing Space
|
|
231
|
-
Given the text ""
|
|
232
|
-
When ":joy" is typed
|
|
233
|
-
Then the keyword is "joy"
|
|
234
|
-
When " " is typed
|
|
235
|
-
Then the keyword is ""
|
|
236
|
-
|
|
237
|
-
Scenario Outline: Allow special characters
|
|
238
|
-
Given the text <text>
|
|
239
|
-
When <inserted text> is inserted
|
|
240
|
-
Then the keyword is <keyword>
|
|
241
|
-
And the matches are <matches>
|
|
242
|
-
|
|
243
|
-
Examples:
|
|
244
|
-
| text | inserted text | keyword | matches |
|
|
245
|
-
| "" | ":joy!" | "joy!" | "đšī¸" |
|
|
246
|
-
| "" | ":*" | "*" | "đ" |
|
|
247
|
-
| "" | ":!" | "!" | "đšī¸,âī¸,âī¸,âŧī¸" |
|
|
248
|
-
| "" | ":!!" | "!!" | "âŧī¸" |
|
|
249
|
-
| "" | "::)" | ":)" | "đ" |
|
|
250
|
-
| "" | "::" | ":" | "đ" |
|
|
251
|
-
|
|
252
|
-
Scenario: Narrowing keyword by deletion
|
|
253
|
-
Given the text "foo"
|
|
254
|
-
When the caret is put after "fo"
|
|
255
|
-
And <inserted text> is inserted
|
|
256
|
-
And "{ArrowLeft}" is pressed
|
|
257
|
-
And "{Backspace}" is pressed
|
|
258
|
-
Then the text is <final text>
|
|
259
|
-
And the keyword is <keyword>
|
|
260
|
-
|
|
261
|
-
Examples:
|
|
262
|
-
| inserted text | final text | keyword |
|
|
263
|
-
| ":joy" | "fo:jyo" | "jy" |
|
|
264
|
-
| ":jđģy" | "fo:jyo" | "jy" |
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import {parameterTypes} from '@portabletext/editor/test'
|
|
2
|
-
import {
|
|
3
|
-
createTestEditor,
|
|
4
|
-
stepDefinitions,
|
|
5
|
-
type Context,
|
|
6
|
-
} from '@portabletext/editor/test/vitest'
|
|
7
|
-
import {defineSchema} from '@portabletext/schema'
|
|
8
|
-
import {Before, Then} from 'racejar'
|
|
9
|
-
import {Feature} from 'racejar/vitest'
|
|
10
|
-
import {expect, vi} from 'vitest'
|
|
11
|
-
import {page, type Locator} from 'vitest/browser'
|
|
12
|
-
import {createMatchEmojis} from './create-match-emojis'
|
|
13
|
-
import emojiPickerFeature from './emoji-picker.feature?raw'
|
|
14
|
-
import {useEmojiPicker} from './use-emoji-picker'
|
|
15
|
-
|
|
16
|
-
type EmojiPickerTestContext = Context & {
|
|
17
|
-
keywordLocator: Locator
|
|
18
|
-
matchesLocator: Locator
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
Feature({
|
|
22
|
-
hooks: [
|
|
23
|
-
Before(async (context: EmojiPickerTestContext) => {
|
|
24
|
-
const {editor, locator} = await createTestEditor({
|
|
25
|
-
children: <EmojiPickerPlugin />,
|
|
26
|
-
schemaDefinition: defineSchema({
|
|
27
|
-
decorators: [{name: 'strong'}],
|
|
28
|
-
annotations: [{name: 'link'}],
|
|
29
|
-
}),
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
context.locator = locator
|
|
33
|
-
context.editor = editor
|
|
34
|
-
context.keywordLocator = page.getByTestId('keyword')
|
|
35
|
-
context.matchesLocator = page.getByTestId('matches')
|
|
36
|
-
|
|
37
|
-
await vi.waitFor(() =>
|
|
38
|
-
expect.element(context.keywordLocator).toBeInTheDocument(),
|
|
39
|
-
)
|
|
40
|
-
await vi.waitFor(() =>
|
|
41
|
-
expect.element(context.matchesLocator).toBeInTheDocument(),
|
|
42
|
-
)
|
|
43
|
-
}),
|
|
44
|
-
],
|
|
45
|
-
featureText: emojiPickerFeature,
|
|
46
|
-
stepDefinitions: [
|
|
47
|
-
...stepDefinitions,
|
|
48
|
-
Then(
|
|
49
|
-
'the keyword is {string}',
|
|
50
|
-
(context: EmojiPickerTestContext, keyword: string) => {
|
|
51
|
-
expect(context.keywordLocator.element().textContent).toEqual(keyword)
|
|
52
|
-
},
|
|
53
|
-
),
|
|
54
|
-
Then(
|
|
55
|
-
'the matches are {string}',
|
|
56
|
-
(context: EmojiPickerTestContext, matches: string) => {
|
|
57
|
-
expect(context.matchesLocator.element().textContent).toEqual(matches)
|
|
58
|
-
},
|
|
59
|
-
),
|
|
60
|
-
],
|
|
61
|
-
parameterTypes,
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
const emojis: Record<string, Array<string>> = {
|
|
65
|
-
'đ': ['joy'],
|
|
66
|
-
'đš': ['joy_cat'],
|
|
67
|
-
'đšī¸': ['joy!stick'],
|
|
68
|
-
'đ': ['*'],
|
|
69
|
-
'âī¸': ['!'],
|
|
70
|
-
'âī¸': ['!?'],
|
|
71
|
-
'âŧī¸': ['!!'],
|
|
72
|
-
'đ': [':)'],
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const matchEmojis = createMatchEmojis({emojis})
|
|
76
|
-
|
|
77
|
-
function EmojiPickerPlugin() {
|
|
78
|
-
const {keyword, matches} = useEmojiPicker({matchEmojis})
|
|
79
|
-
|
|
80
|
-
return (
|
|
81
|
-
<>
|
|
82
|
-
<div data-testid="keyword">{keyword}</div>
|
|
83
|
-
<div data-testid="matches">
|
|
84
|
-
{matches.map((match) => match.emoji).join(',')}
|
|
85
|
-
</div>
|
|
86
|
-
</>
|
|
87
|
-
)
|
|
88
|
-
}
|
package/src/global.d.ts
DELETED
package/src/index.ts
DELETED
package/src/match-emojis.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The base type representing an emoji match.
|
|
3
|
-
*
|
|
4
|
-
* @beta
|
|
5
|
-
*/
|
|
6
|
-
export type BaseEmojiMatch =
|
|
7
|
-
| {
|
|
8
|
-
type: 'exact'
|
|
9
|
-
emoji: string
|
|
10
|
-
}
|
|
11
|
-
| {
|
|
12
|
-
type: 'partial'
|
|
13
|
-
emoji: string
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* A function that returns an array of emoji matches for a given keyword.
|
|
18
|
-
*
|
|
19
|
-
* @beta
|
|
20
|
-
*/
|
|
21
|
-
export type MatchEmojis<TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch> =
|
|
22
|
-
(query: {keyword: string}) => ReadonlyArray<TEmojiMatch>
|
package/src/use-emoji-picker.ts
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import {useEditor} from '@portabletext/editor'
|
|
2
|
-
import {useActorRef, useSelector} from '@xstate/react'
|
|
3
|
-
import {useCallback} from 'react'
|
|
4
|
-
import {emojiPickerMachine} from './emoji-picker-machine'
|
|
5
|
-
import type {BaseEmojiMatch, MatchEmojis} from './match-emojis'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @beta
|
|
9
|
-
*/
|
|
10
|
-
export type EmojiPicker<TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch> = {
|
|
11
|
-
/**
|
|
12
|
-
* The matched keyword.
|
|
13
|
-
*
|
|
14
|
-
* Can be used to display the keyword in the UI or conditionally render the
|
|
15
|
-
* list of matches.
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```tsx
|
|
19
|
-
* if (keyword.length < 1) {
|
|
20
|
-
* return null
|
|
21
|
-
* }
|
|
22
|
-
* ```
|
|
23
|
-
*/
|
|
24
|
-
keyword: string
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Emoji matches found for the current keyword.
|
|
28
|
-
*
|
|
29
|
-
* Can be used to display the matches in a list.
|
|
30
|
-
*/
|
|
31
|
-
matches: ReadonlyArray<TEmojiMatch>
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* The index of the selected match.
|
|
35
|
-
*
|
|
36
|
-
* Can be used to highlight the selected match in the list.
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* ```tsx
|
|
40
|
-
* <EmojiListItem
|
|
41
|
-
* key={match.key}
|
|
42
|
-
* match={match}
|
|
43
|
-
* selected={selectedIndex === index}
|
|
44
|
-
* />
|
|
45
|
-
* ```
|
|
46
|
-
*/
|
|
47
|
-
selectedIndex: number
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Navigate to a specific match by index.
|
|
51
|
-
*
|
|
52
|
-
* Can be used to control the `selectedIndex`. For example, using
|
|
53
|
-
* `onMouseEnter`.
|
|
54
|
-
*
|
|
55
|
-
* @example
|
|
56
|
-
* ```tsx
|
|
57
|
-
* <EmojiListItem
|
|
58
|
-
* key={match.key}
|
|
59
|
-
* match={match}
|
|
60
|
-
* selected={selectedIndex === index}
|
|
61
|
-
* onMouseEnter={() => {onNavigateTo(index)}}
|
|
62
|
-
* />
|
|
63
|
-
* ```
|
|
64
|
-
*/
|
|
65
|
-
onNavigateTo: (index: number) => void
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Select the current match.
|
|
69
|
-
*
|
|
70
|
-
* Can be used to insert the currently selected match.
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* ```tsx
|
|
75
|
-
* <EmojiListItem
|
|
76
|
-
* key={match.key}
|
|
77
|
-
* match={match}
|
|
78
|
-
* selected={selectedIndex === index}
|
|
79
|
-
* onMouseEnter={() => {onNavigateTo(index)}}
|
|
80
|
-
* onSelect={() => {onSelect()}}
|
|
81
|
-
* />
|
|
82
|
-
* ```
|
|
83
|
-
*
|
|
84
|
-
* Note: The currently selected match is automatically inserted on Enter or
|
|
85
|
-
* Tab.
|
|
86
|
-
*/
|
|
87
|
-
onSelect: () => void
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Dismiss the emoji picker. Can be used to let the user dismiss the picker
|
|
91
|
-
* by clicking a button.
|
|
92
|
-
*
|
|
93
|
-
* @example
|
|
94
|
-
* ```tsx
|
|
95
|
-
* {matches.length === 0 ? (
|
|
96
|
-
* <Button onPress={onDismiss}>Dismiss</Button>
|
|
97
|
-
* ) : <EmojiListBox {...props} />}
|
|
98
|
-
* ```
|
|
99
|
-
*
|
|
100
|
-
* Note: The emoji picker is automatically dismissed on Escape.
|
|
101
|
-
*/
|
|
102
|
-
onDismiss: () => void
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* @beta
|
|
107
|
-
*/
|
|
108
|
-
export type EmojiPickerProps<
|
|
109
|
-
TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch,
|
|
110
|
-
> = {
|
|
111
|
-
matchEmojis: MatchEmojis<TEmojiMatch>
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Handles the state and logic needed to create an emoji picker.
|
|
116
|
-
*
|
|
117
|
-
* The `matchEmojis` function is generic and can return any shape of emoji
|
|
118
|
-
* match required for the emoji picker.
|
|
119
|
-
*
|
|
120
|
-
* However, the default implementation of `matchEmojis` returns an array of
|
|
121
|
-
* `EmojiMatch` objects and can be created using the `createMatchEmojis`
|
|
122
|
-
* function.
|
|
123
|
-
*
|
|
124
|
-
* @example
|
|
125
|
-
*
|
|
126
|
-
* ```tsx
|
|
127
|
-
* const matchEmojis = createMatchEmojis({emojis: {
|
|
128
|
-
* 'đ': ['joy'],
|
|
129
|
-
* 'đš': ['joy_cat'],
|
|
130
|
-
* }})
|
|
131
|
-
*
|
|
132
|
-
* const {keyword, matches, selectedIndex, onDismiss, onNavigateTo, onSelect} =
|
|
133
|
-
* useEmojiPicker({matchEmojis})
|
|
134
|
-
* ```
|
|
135
|
-
*
|
|
136
|
-
* Note: This hook is not concerned with the UI, how the emoji picker is
|
|
137
|
-
* rendered or positioned in the document.
|
|
138
|
-
*
|
|
139
|
-
* @beta
|
|
140
|
-
*/
|
|
141
|
-
export function useEmojiPicker<
|
|
142
|
-
TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch,
|
|
143
|
-
>(props: EmojiPickerProps<TEmojiMatch>): EmojiPicker<TEmojiMatch> {
|
|
144
|
-
const editor = useEditor()
|
|
145
|
-
const emojiPickerActor = useActorRef(emojiPickerMachine, {
|
|
146
|
-
input: {editor, matchEmojis: props.matchEmojis},
|
|
147
|
-
})
|
|
148
|
-
const keyword = useSelector(emojiPickerActor, (snapshot) => {
|
|
149
|
-
const rawKeyword = snapshot.context.keyword.startsWith(':')
|
|
150
|
-
? snapshot.context.keyword.slice(1)
|
|
151
|
-
: snapshot.context.keyword
|
|
152
|
-
return rawKeyword.length > 1 && rawKeyword.endsWith(':')
|
|
153
|
-
? rawKeyword.slice(0, -1)
|
|
154
|
-
: rawKeyword
|
|
155
|
-
})
|
|
156
|
-
const matches = useSelector(
|
|
157
|
-
emojiPickerActor,
|
|
158
|
-
(snapshot) => snapshot.context.matches as ReadonlyArray<TEmojiMatch>,
|
|
159
|
-
)
|
|
160
|
-
const selectedIndex = useSelector(
|
|
161
|
-
emojiPickerActor,
|
|
162
|
-
(snapshot) => snapshot.context.selectedIndex,
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
const onDismiss = useCallback(() => {
|
|
166
|
-
emojiPickerActor.send({type: 'dismiss'})
|
|
167
|
-
}, [emojiPickerActor])
|
|
168
|
-
const onNavigateTo = useCallback(
|
|
169
|
-
(index: number) => {
|
|
170
|
-
emojiPickerActor.send({type: 'navigate to', index})
|
|
171
|
-
},
|
|
172
|
-
[emojiPickerActor],
|
|
173
|
-
)
|
|
174
|
-
const onSelect = useCallback(() => {
|
|
175
|
-
emojiPickerActor.send({type: 'insert selected match'})
|
|
176
|
-
editor.send({type: 'focus'})
|
|
177
|
-
}, [emojiPickerActor, editor])
|
|
178
|
-
|
|
179
|
-
return {
|
|
180
|
-
keyword,
|
|
181
|
-
matches,
|
|
182
|
-
selectedIndex,
|
|
183
|
-
onDismiss,
|
|
184
|
-
onNavigateTo,
|
|
185
|
-
onSelect,
|
|
186
|
-
}
|
|
187
|
-
}
|