@portabletext/plugin-emoji-picker 1.0.0 → 1.0.2
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/README.md +1 -1
- package/dist/index.cjs +23 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -12
- package/dist/index.d.ts +31 -12
- package/dist/index.js +23 -22
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/create-match-emojis.ts +113 -0
- package/src/emoji-picker-machine.tsx +4 -4
- package/src/emoji-picker.feature +2 -2
- package/src/emoji-picker.test.tsx +1 -1
- package/src/emojis.ts +0 -3
- package/src/index.ts +1 -0
- package/src/match-emojis.ts +4 -103
- package/src/use-emoji-picker.ts +17 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/plugin-emoji-picker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Easily configure an Emoji Picker for the Portable Text Editor",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"portabletext",
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"typescript": "5.9.3",
|
|
57
57
|
"typescript-eslint": "^8.46.1",
|
|
58
58
|
"vitest": "^3.2.4",
|
|
59
|
-
"@portabletext/editor": "2.15.5",
|
|
60
59
|
"@portabletext/schema": "1.2.0",
|
|
61
|
-
"racejar": "1.3.2"
|
|
60
|
+
"racejar": "1.3.2",
|
|
61
|
+
"@portabletext/editor": "2.15.5"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
64
|
"@portabletext/editor": "^2.15.5",
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import {emojis} from './emojis'
|
|
2
|
+
import type {MatchEmojis} from './match-emojis'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Proposed, but not required type, to represent an emoji match.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* {
|
|
10
|
+
* type: 'exact',
|
|
11
|
+
* key: '😂-joy',
|
|
12
|
+
* emoji: '😂',
|
|
13
|
+
* keyword: 'joy',
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* {
|
|
19
|
+
* type: 'partial',
|
|
20
|
+
* key: '😹-joy-_cat',
|
|
21
|
+
* emoji: '😹',
|
|
22
|
+
* keyword: 'joy',
|
|
23
|
+
* startSlice: '',
|
|
24
|
+
* endSlice: '_cat',
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @beta
|
|
29
|
+
*/
|
|
30
|
+
export type EmojiMatch =
|
|
31
|
+
| {
|
|
32
|
+
type: 'exact'
|
|
33
|
+
key: string
|
|
34
|
+
emoji: string
|
|
35
|
+
keyword: string
|
|
36
|
+
}
|
|
37
|
+
| {
|
|
38
|
+
type: 'partial'
|
|
39
|
+
key: string
|
|
40
|
+
emoji: string
|
|
41
|
+
keyword: string
|
|
42
|
+
startSlice: string
|
|
43
|
+
endSlice: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Proposed, but not required, default implementation of `MatchEmojis`.
|
|
48
|
+
*
|
|
49
|
+
* @beta
|
|
50
|
+
*/
|
|
51
|
+
export const matchEmojis = createMatchEmojis({emojis})
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Proposed, but not required, function to create a `MatchEmojis` function.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* const matchEmojis = createMatchEmojis({
|
|
59
|
+
* emojis: {
|
|
60
|
+
* '😂': ['joy'],
|
|
61
|
+
* '😹': ['joy_cat'],
|
|
62
|
+
* },
|
|
63
|
+
* })
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* @beta
|
|
67
|
+
*/
|
|
68
|
+
export function createMatchEmojis(config: {
|
|
69
|
+
emojis: Record<string, ReadonlyArray<string>>
|
|
70
|
+
}): MatchEmojis<EmojiMatch> {
|
|
71
|
+
return ({keyword}: {keyword: string}) => {
|
|
72
|
+
const foundEmojis: Array<EmojiMatch> = []
|
|
73
|
+
|
|
74
|
+
if (keyword.length < 1) {
|
|
75
|
+
return foundEmojis
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const emoji in config.emojis) {
|
|
79
|
+
const emojiKeywords = config.emojis[emoji] ?? []
|
|
80
|
+
|
|
81
|
+
for (const emojiKeyword of emojiKeywords) {
|
|
82
|
+
const keywordIndex = emojiKeyword.indexOf(keyword)
|
|
83
|
+
|
|
84
|
+
if (keywordIndex === -1) {
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (emojiKeyword === keyword) {
|
|
89
|
+
foundEmojis.push({
|
|
90
|
+
type: 'exact',
|
|
91
|
+
key: `${emoji}-${keyword}`,
|
|
92
|
+
emoji,
|
|
93
|
+
keyword,
|
|
94
|
+
})
|
|
95
|
+
} else {
|
|
96
|
+
const start = emojiKeyword.slice(0, keywordIndex)
|
|
97
|
+
const end = emojiKeyword.slice(keywordIndex + keyword.length)
|
|
98
|
+
|
|
99
|
+
foundEmojis.push({
|
|
100
|
+
type: 'partial',
|
|
101
|
+
key: `${emoji}-${start}${keyword}${end}`,
|
|
102
|
+
emoji,
|
|
103
|
+
keyword,
|
|
104
|
+
startSlice: start,
|
|
105
|
+
endSlice: end,
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return foundEmojis
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
type AnyEventObject,
|
|
29
29
|
type CallbackLogicFunction,
|
|
30
30
|
} from 'xstate'
|
|
31
|
-
import type {
|
|
31
|
+
import type {BaseEmojiMatch, MatchEmojis} from './match-emojis'
|
|
32
32
|
|
|
33
33
|
/*******************
|
|
34
34
|
* Keyboard shortcuts
|
|
@@ -175,10 +175,10 @@ function createKeywordFoundEvent(payload: {
|
|
|
175
175
|
} as const
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
type EmojiPickerContext
|
|
178
|
+
type EmojiPickerContext = {
|
|
179
179
|
editor: Editor
|
|
180
|
-
matches: ReadonlyArray<
|
|
181
|
-
matchEmojis: MatchEmojis<
|
|
180
|
+
matches: ReadonlyArray<BaseEmojiMatch>
|
|
181
|
+
matchEmojis: MatchEmojis<BaseEmojiMatch>
|
|
182
182
|
selectedIndex: number
|
|
183
183
|
keywordAnchor:
|
|
184
184
|
| {
|
package/src/emoji-picker.feature
CHANGED
|
@@ -32,14 +32,14 @@ Feature: Emoji Picker
|
|
|
32
32
|
Scenario: Picking wrong direct hit
|
|
33
33
|
When ":jo:" is typed
|
|
34
34
|
Then the text is ":jo:"
|
|
35
|
-
And the keyword is "
|
|
35
|
+
And the keyword is "jo"
|
|
36
36
|
And the matches are "😂, 😹"
|
|
37
37
|
|
|
38
38
|
Scenario: Colon after wrong direct hit
|
|
39
39
|
When ":jo:" is typed
|
|
40
40
|
And ":" is typed
|
|
41
41
|
Then the text is ":jo::"
|
|
42
|
-
And the keyword is ":
|
|
42
|
+
And the keyword is "jo:"
|
|
43
43
|
And the matches are ""
|
|
44
44
|
|
|
45
45
|
Scenario: Picking wrong direct hit after undoing direct hit
|
|
@@ -9,8 +9,8 @@ import {page, type Locator} from '@vitest/browser/context'
|
|
|
9
9
|
import {Before, Then} from 'racejar'
|
|
10
10
|
import {Feature} from 'racejar/vitest'
|
|
11
11
|
import {expect, vi} from 'vitest'
|
|
12
|
+
import {createMatchEmojis} from './create-match-emojis'
|
|
12
13
|
import emojiPickerFeature from './emoji-picker.feature?raw'
|
|
13
|
-
import {createMatchEmojis} from './match-emojis'
|
|
14
14
|
import {useEmojiPicker} from './use-emoji-picker'
|
|
15
15
|
|
|
16
16
|
type EmojiPickerTestContext = Context & {
|
package/src/emojis.ts
CHANGED
package/src/index.ts
CHANGED
package/src/match-emojis.ts
CHANGED
|
@@ -1,45 +1,16 @@
|
|
|
1
|
-
import {emojis} from './emojis'
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```tsx
|
|
8
|
-
* {
|
|
9
|
-
* type: 'exact',
|
|
10
|
-
* key: '😂-joy',
|
|
11
|
-
* emoji: '😂',
|
|
12
|
-
* keyword: 'joy',
|
|
13
|
-
* }
|
|
14
|
-
* ```
|
|
15
|
-
* @example
|
|
16
|
-
* ```tsx
|
|
17
|
-
* {
|
|
18
|
-
* type: 'partial',
|
|
19
|
-
* key: '😹-joy-_cat',
|
|
20
|
-
* emoji: '😹',
|
|
21
|
-
* keyword: 'joy',
|
|
22
|
-
* startSlice: '',
|
|
23
|
-
* endSlice: '_cat',
|
|
24
|
-
* }
|
|
25
|
-
* ```
|
|
2
|
+
* The base type representing an emoji match.
|
|
26
3
|
*
|
|
27
4
|
* @beta
|
|
28
5
|
*/
|
|
29
|
-
export type
|
|
6
|
+
export type BaseEmojiMatch =
|
|
30
7
|
| {
|
|
31
8
|
type: 'exact'
|
|
32
|
-
key: string
|
|
33
9
|
emoji: string
|
|
34
|
-
keyword: string
|
|
35
10
|
}
|
|
36
11
|
| {
|
|
37
12
|
type: 'partial'
|
|
38
|
-
key: string
|
|
39
13
|
emoji: string
|
|
40
|
-
keyword: string
|
|
41
|
-
startSlice: string
|
|
42
|
-
endSlice: string
|
|
43
14
|
}
|
|
44
15
|
|
|
45
16
|
/**
|
|
@@ -47,75 +18,5 @@ export type EmojiMatch =
|
|
|
47
18
|
*
|
|
48
19
|
* @beta
|
|
49
20
|
*/
|
|
50
|
-
export type MatchEmojis<TEmojiMatch =
|
|
51
|
-
keyword: string
|
|
52
|
-
}) => ReadonlyArray<TEmojiMatch>
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Proposed, but not required, default implementation of `MatchEmojis`.
|
|
56
|
-
*
|
|
57
|
-
* @beta
|
|
58
|
-
*/
|
|
59
|
-
export const matchEmojis: MatchEmojis = createMatchEmojis({emojis})
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Proposed, but not required, function to create a `MatchEmojis` function.
|
|
63
|
-
*
|
|
64
|
-
* @example
|
|
65
|
-
* ```ts
|
|
66
|
-
* const matchEmojis = createMatchEmojis({
|
|
67
|
-
* emojis: {
|
|
68
|
-
* '😂': ['joy'],
|
|
69
|
-
* '😹': ['joy_cat'],
|
|
70
|
-
* },
|
|
71
|
-
* })
|
|
72
|
-
* ```
|
|
73
|
-
*
|
|
74
|
-
* @beta
|
|
75
|
-
*/
|
|
76
|
-
export function createMatchEmojis(config: {
|
|
77
|
-
emojis: Record<string, ReadonlyArray<string>>
|
|
78
|
-
}): MatchEmojis {
|
|
79
|
-
return ({keyword}: {keyword: string}) => {
|
|
80
|
-
const foundEmojis: Array<EmojiMatch> = []
|
|
81
|
-
|
|
82
|
-
if (keyword.length < 1) {
|
|
83
|
-
return foundEmojis
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
for (const emoji in config.emojis) {
|
|
87
|
-
const emojiKeywords = config.emojis[emoji] ?? []
|
|
88
|
-
|
|
89
|
-
for (const emojiKeyword of emojiKeywords) {
|
|
90
|
-
const keywordIndex = emojiKeyword.indexOf(keyword)
|
|
91
|
-
|
|
92
|
-
if (keywordIndex === -1) {
|
|
93
|
-
continue
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (emojiKeyword === keyword) {
|
|
97
|
-
foundEmojis.push({
|
|
98
|
-
type: 'exact',
|
|
99
|
-
key: `${emoji}-${keyword}`,
|
|
100
|
-
emoji,
|
|
101
|
-
keyword,
|
|
102
|
-
})
|
|
103
|
-
} else {
|
|
104
|
-
const start = emojiKeyword.slice(0, keywordIndex)
|
|
105
|
-
const end = emojiKeyword.slice(keywordIndex + keyword.length)
|
|
106
|
-
|
|
107
|
-
foundEmojis.push({
|
|
108
|
-
type: 'partial',
|
|
109
|
-
key: `${emoji}-${start}${keyword}${end}`,
|
|
110
|
-
emoji,
|
|
111
|
-
keyword,
|
|
112
|
-
startSlice: start,
|
|
113
|
-
endSlice: end,
|
|
114
|
-
})
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return foundEmojis
|
|
120
|
-
}
|
|
121
|
-
}
|
|
21
|
+
export type MatchEmojis<TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch> =
|
|
22
|
+
(query: {keyword: string}) => ReadonlyArray<TEmojiMatch>
|
package/src/use-emoji-picker.ts
CHANGED
|
@@ -2,21 +2,21 @@ import {useEditor} from '@portabletext/editor'
|
|
|
2
2
|
import {useActorRef, useSelector} from '@xstate/react'
|
|
3
3
|
import {useCallback} from 'react'
|
|
4
4
|
import {emojiPickerMachine} from './emoji-picker-machine'
|
|
5
|
-
import type {
|
|
5
|
+
import type {BaseEmojiMatch, MatchEmojis} from './match-emojis'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @beta
|
|
9
9
|
*/
|
|
10
|
-
export type EmojiPicker<TEmojiMatch =
|
|
10
|
+
export type EmojiPicker<TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch> = {
|
|
11
11
|
/**
|
|
12
|
-
* The matched keyword
|
|
12
|
+
* The matched keyword.
|
|
13
13
|
*
|
|
14
14
|
* Can be used to display the keyword in the UI or conditionally render the
|
|
15
15
|
* list of matches.
|
|
16
16
|
*
|
|
17
17
|
* @example
|
|
18
18
|
* ```tsx
|
|
19
|
-
* if (keyword.length <
|
|
19
|
+
* if (keyword.length < 1) {
|
|
20
20
|
* return null
|
|
21
21
|
* }
|
|
22
22
|
* ```
|
|
@@ -105,7 +105,9 @@ export type EmojiPicker<TEmojiMatch = EmojiMatch> = {
|
|
|
105
105
|
/**
|
|
106
106
|
* @beta
|
|
107
107
|
*/
|
|
108
|
-
export type EmojiPickerProps<
|
|
108
|
+
export type EmojiPickerProps<
|
|
109
|
+
TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch,
|
|
110
|
+
> = {
|
|
109
111
|
matchEmojis: MatchEmojis<TEmojiMatch>
|
|
110
112
|
}
|
|
111
113
|
|
|
@@ -136,17 +138,19 @@ export type EmojiPickerProps<TEmojiMatch = EmojiMatch> = {
|
|
|
136
138
|
*
|
|
137
139
|
* @beta
|
|
138
140
|
*/
|
|
139
|
-
export function useEmojiPicker<
|
|
140
|
-
|
|
141
|
-
): EmojiPicker<TEmojiMatch> {
|
|
141
|
+
export function useEmojiPicker<
|
|
142
|
+
TEmojiMatch extends BaseEmojiMatch = BaseEmojiMatch,
|
|
143
|
+
>(props: EmojiPickerProps<TEmojiMatch>): EmojiPicker<TEmojiMatch> {
|
|
142
144
|
const editor = useEditor()
|
|
143
145
|
const emojiPickerActor = useActorRef(emojiPickerMachine, {
|
|
144
|
-
input: {editor, matchEmojis: props.matchEmojis
|
|
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.endsWith(':') ? rawKeyword.slice(0, -1) : rawKeyword
|
|
145
153
|
})
|
|
146
|
-
const keyword = useSelector(
|
|
147
|
-
emojiPickerActor,
|
|
148
|
-
(snapshot) => snapshot.context.keyword,
|
|
149
|
-
)
|
|
150
154
|
const matches = useSelector(
|
|
151
155
|
emojiPickerActor,
|
|
152
156
|
(snapshot) => snapshot.context.matches as ReadonlyArray<TEmojiMatch>,
|