@refraction-ui/astro 0.16.1 → 0.17.1
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/dist/astro-location-selector/LocationSelector.astro +56 -0
- package/dist/astro-location-selector/LocationSelector.stories.ts +15 -0
- package/dist/astro-location-selector/index.ts +1 -0
- package/dist/command-input/command-input.ts +5 -5
- package/dist/composer/emoji-trigger.ts +51 -0
- package/dist/composer/index.ts +1 -0
- package/dist/composer/styles.ts +64 -9
- package/dist/emoji-picker/emoji-data.ts +242 -248
- package/dist/emoji-picker/emoji-dataset.generated.ts +1943 -0
- package/dist/emoji-picker/emoji-picker.styles.ts +11 -3
- package/dist/emoji-picker/index.ts +20 -0
- package/dist/emoji-picker/stickers.ts +146 -0
- package/dist/emoji-picker/twemoji.ts +60 -0
- package/dist/i18n/countries.ts +122 -0
- package/dist/i18n/detect-region.ts +243 -0
- package/dist/i18n/index.ts +47 -0
- package/dist/i18n/languages.ts +181 -0
- package/dist/i18n/locale-utils.ts +123 -0
- package/dist/i18n/voice-registry.ts +92 -0
- package/dist/index.ts +1 -0
- package/dist/location-selector/index.ts +1 -0
- package/dist/location-selector/location-selector.ts +60 -0
- package/dist/rich-editor/emoji.ts +10 -116
- package/package.json +2 -1
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emoji data — the single source of truth for the whole library.
|
|
3
|
+
*
|
|
4
|
+
* The full base emoji set (Unicode 16.0, ~1900 emoji) is generated offline into
|
|
5
|
+
* `emoji-dataset.generated.ts` from the canonical `scripts/emoji/emoji-test-16.0.txt`
|
|
6
|
+
* (regenerate with `node scripts/generate-emoji-data.mjs`). This module decodes
|
|
7
|
+
* that compact data and layers on curated `:shortcode:` aliases, search, and
|
|
8
|
+
* resolution helpers. It is consumed by:
|
|
9
|
+
* - the emoji picker (grid + category tabs + search + recents),
|
|
10
|
+
* - the composer's `:` shortcode trigger (`createEmojiTrigger`),
|
|
11
|
+
* - rich-editor's `EMOJI_MAP` (`:shortcode:` → glyph).
|
|
12
|
+
* so the three no longer diverge.
|
|
13
|
+
*/
|
|
14
|
+
import {
|
|
15
|
+
CATEGORY_ORDER,
|
|
16
|
+
EMOJI_TUPLES,
|
|
17
|
+
EMOJI_COUNT,
|
|
18
|
+
} from './emoji-dataset.generated.js'
|
|
19
|
+
|
|
1
20
|
export type EmojiCategory =
|
|
2
21
|
| 'smileys'
|
|
3
22
|
| 'people'
|
|
@@ -10,260 +29,36 @@ export type EmojiCategory =
|
|
|
10
29
|
| 'flags'
|
|
11
30
|
|
|
12
31
|
export interface EmojiEntry {
|
|
32
|
+
/** The rendered glyph (native codepoints). */
|
|
13
33
|
emoji: string
|
|
34
|
+
/** CLDR short name, e.g. "face with tears of joy". */
|
|
14
35
|
name: string
|
|
15
36
|
category: EmojiCategory
|
|
37
|
+
/** Search keywords (does not include the name words verbatim-repeated). */
|
|
16
38
|
keywords: string[]
|
|
39
|
+
/** Canonical `snake_case` shortcode body (no surrounding colons). */
|
|
40
|
+
shortcode: string
|
|
17
41
|
}
|
|
18
42
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
{ emoji: '\u{1F606}', name: 'grinning squinting face', category: 'smileys', keywords: ['laugh', 'happy'] },
|
|
26
|
-
{ emoji: '\u{1F605}', name: 'grinning face with sweat', category: 'smileys', keywords: ['nervous', 'laugh'] },
|
|
27
|
-
{ emoji: '\u{1F923}', name: 'rolling on the floor laughing', category: 'smileys', keywords: ['lol', 'rofl', 'laugh'] },
|
|
28
|
-
{ emoji: '\u{1F602}', name: 'face with tears of joy', category: 'smileys', keywords: ['laugh', 'cry', 'joy', 'lol'] },
|
|
29
|
-
{ emoji: '\u{1F642}', name: 'slightly smiling face', category: 'smileys', keywords: ['smile', 'happy'] },
|
|
30
|
-
{ emoji: '\u{1F643}', name: 'upside-down face', category: 'smileys', keywords: ['silly', 'sarcasm'] },
|
|
31
|
-
{ emoji: '\u{1F609}', name: 'winking face', category: 'smileys', keywords: ['wink', 'flirt'] },
|
|
32
|
-
{ emoji: '\u{1F60A}', name: 'smiling face with smiling eyes', category: 'smileys', keywords: ['blush', 'happy'] },
|
|
33
|
-
{ emoji: '\u{1F607}', name: 'smiling face with halo', category: 'smileys', keywords: ['angel', 'innocent'] },
|
|
34
|
-
{ emoji: '\u{1F970}', name: 'smiling face with hearts', category: 'smileys', keywords: ['love', 'crush', 'adore'] },
|
|
35
|
-
{ emoji: '\u{1F60D}', name: 'smiling face with heart-eyes', category: 'smileys', keywords: ['love', 'heart'] },
|
|
36
|
-
{ emoji: '\u{1F929}', name: 'star-struck', category: 'smileys', keywords: ['star', 'eyes', 'wow'] },
|
|
37
|
-
{ emoji: '\u{1F618}', name: 'face blowing a kiss', category: 'smileys', keywords: ['kiss', 'love'] },
|
|
38
|
-
{ emoji: '\u{1F617}', name: 'kissing face', category: 'smileys', keywords: ['kiss'] },
|
|
39
|
-
{ emoji: '\u{1F61A}', name: 'kissing face with closed eyes', category: 'smileys', keywords: ['kiss', 'love'] },
|
|
40
|
-
{ emoji: '\u{1F619}', name: 'kissing face with smiling eyes', category: 'smileys', keywords: ['kiss'] },
|
|
41
|
-
{ emoji: '\u{1F60B}', name: 'face savoring food', category: 'smileys', keywords: ['yummy', 'delicious'] },
|
|
42
|
-
{ emoji: '\u{1F61C}', name: 'winking face with tongue', category: 'smileys', keywords: ['tongue', 'wink', 'silly'] },
|
|
43
|
-
{ emoji: '\u{1F61D}', name: 'squinting face with tongue', category: 'smileys', keywords: ['tongue', 'silly'] },
|
|
44
|
-
{ emoji: '\u{1F911}', name: 'money-mouth face', category: 'smileys', keywords: ['money', 'rich'] },
|
|
45
|
-
{ emoji: '\u{1F917}', name: 'hugging face', category: 'smileys', keywords: ['hug', 'warm'] },
|
|
46
|
-
{ emoji: '\u{1F914}', name: 'thinking face', category: 'smileys', keywords: ['think', 'hmm'] },
|
|
47
|
-
{ emoji: '\u{1F910}', name: 'zipper-mouth face', category: 'smileys', keywords: ['secret', 'quiet', 'zip'] },
|
|
48
|
-
{ emoji: '\u{1F928}', name: 'face with raised eyebrow', category: 'smileys', keywords: ['skeptical', 'doubt'] },
|
|
49
|
-
{ emoji: '\u{1F610}', name: 'neutral face', category: 'smileys', keywords: ['meh', 'neutral'] },
|
|
50
|
-
{ emoji: '\u{1F611}', name: 'expressionless face', category: 'smileys', keywords: ['blank', 'expressionless'] },
|
|
51
|
-
],
|
|
52
|
-
people: [
|
|
53
|
-
{ emoji: '\u{1F44B}', name: 'waving hand', category: 'people', keywords: ['wave', 'hello', 'hi', 'bye'] },
|
|
54
|
-
{ emoji: '\u{1F91A}', name: 'raised back of hand', category: 'people', keywords: ['hand', 'backhand'] },
|
|
55
|
-
{ emoji: '\u{1F590}', name: 'hand with fingers splayed', category: 'people', keywords: ['hand', 'fingers'] },
|
|
56
|
-
{ emoji: '\u270B', name: 'raised hand', category: 'people', keywords: ['hand', 'stop', 'high five'] },
|
|
57
|
-
{ emoji: '\u{1F596}', name: 'vulcan salute', category: 'people', keywords: ['spock', 'star trek'] },
|
|
58
|
-
{ emoji: '\u{1F44C}', name: 'OK hand', category: 'people', keywords: ['ok', 'perfect', 'fine'] },
|
|
59
|
-
{ emoji: '\u270C', name: 'victory hand', category: 'people', keywords: ['peace', 'victory', 'v'] },
|
|
60
|
-
{ emoji: '\u{1F91E}', name: 'crossed fingers', category: 'people', keywords: ['luck', 'hope'] },
|
|
61
|
-
{ emoji: '\u{1F918}', name: 'love-you gesture', category: 'people', keywords: ['love', 'rock'] },
|
|
62
|
-
{ emoji: '\u{1F919}', name: 'call me hand', category: 'people', keywords: ['call', 'phone'] },
|
|
63
|
-
{ emoji: '\u{1F448}', name: 'backhand index pointing left', category: 'people', keywords: ['left', 'point'] },
|
|
64
|
-
{ emoji: '\u{1F449}', name: 'backhand index pointing right', category: 'people', keywords: ['right', 'point'] },
|
|
65
|
-
{ emoji: '\u{1F446}', name: 'backhand index pointing up', category: 'people', keywords: ['up', 'point'] },
|
|
66
|
-
{ emoji: '\u{1F447}', name: 'backhand index pointing down', category: 'people', keywords: ['down', 'point'] },
|
|
67
|
-
{ emoji: '\u261D', name: 'index pointing up', category: 'people', keywords: ['up', 'point'] },
|
|
68
|
-
{ emoji: '\u{1F44D}', name: 'thumbs up', category: 'people', keywords: ['like', 'yes', 'approve', 'thumbsup'] },
|
|
69
|
-
{ emoji: '\u{1F44E}', name: 'thumbs down', category: 'people', keywords: ['dislike', 'no', 'disapprove'] },
|
|
70
|
-
{ emoji: '\u270A', name: 'raised fist', category: 'people', keywords: ['fist', 'power'] },
|
|
71
|
-
{ emoji: '\u{1F44A}', name: 'oncoming fist', category: 'people', keywords: ['punch', 'fist bump'] },
|
|
72
|
-
{ emoji: '\u{1F91B}', name: 'left-facing fist', category: 'people', keywords: ['fist'] },
|
|
73
|
-
{ emoji: '\u{1F91C}', name: 'right-facing fist', category: 'people', keywords: ['fist'] },
|
|
74
|
-
{ emoji: '\u{1F44F}', name: 'clapping hands', category: 'people', keywords: ['clap', 'applause', 'bravo'] },
|
|
75
|
-
{ emoji: '\u{1F64C}', name: 'raising hands', category: 'people', keywords: ['celebrate', 'hooray'] },
|
|
76
|
-
{ emoji: '\u{1F450}', name: 'open hands', category: 'people', keywords: ['hands', 'open'] },
|
|
77
|
-
{ emoji: '\u{1F932}', name: 'palms up together', category: 'people', keywords: ['prayer', 'please'] },
|
|
78
|
-
{ emoji: '\u{1F91D}', name: 'handshake', category: 'people', keywords: ['deal', 'agree', 'shake'] },
|
|
79
|
-
{ emoji: '\u{1F64F}', name: 'folded hands', category: 'people', keywords: ['pray', 'please', 'thank you'] },
|
|
80
|
-
],
|
|
81
|
-
nature: [
|
|
82
|
-
{ emoji: '\u{1F436}', name: 'dog face', category: 'nature', keywords: ['dog', 'puppy', 'pet'] },
|
|
83
|
-
{ emoji: '\u{1F431}', name: 'cat face', category: 'nature', keywords: ['cat', 'kitten', 'pet'] },
|
|
84
|
-
{ emoji: '\u{1F42D}', name: 'mouse face', category: 'nature', keywords: ['mouse', 'rodent'] },
|
|
85
|
-
{ emoji: '\u{1F439}', name: 'hamster', category: 'nature', keywords: ['hamster', 'pet'] },
|
|
86
|
-
{ emoji: '\u{1F430}', name: 'rabbit face', category: 'nature', keywords: ['rabbit', 'bunny'] },
|
|
87
|
-
{ emoji: '\u{1F98A}', name: 'fox', category: 'nature', keywords: ['fox', 'animal'] },
|
|
88
|
-
{ emoji: '\u{1F43B}', name: 'bear', category: 'nature', keywords: ['bear', 'animal'] },
|
|
89
|
-
{ emoji: '\u{1F43C}', name: 'panda', category: 'nature', keywords: ['panda', 'animal'] },
|
|
90
|
-
{ emoji: '\u{1F428}', name: 'koala', category: 'nature', keywords: ['koala', 'animal'] },
|
|
91
|
-
{ emoji: '\u{1F42F}', name: 'tiger face', category: 'nature', keywords: ['tiger', 'animal'] },
|
|
92
|
-
{ emoji: '\u{1F981}', name: 'lion', category: 'nature', keywords: ['lion', 'animal'] },
|
|
93
|
-
{ emoji: '\u{1F42E}', name: 'cow face', category: 'nature', keywords: ['cow', 'animal'] },
|
|
94
|
-
{ emoji: '\u{1F437}', name: 'pig face', category: 'nature', keywords: ['pig', 'animal'] },
|
|
95
|
-
{ emoji: '\u{1F438}', name: 'frog', category: 'nature', keywords: ['frog', 'animal'] },
|
|
96
|
-
{ emoji: '\u{1F435}', name: 'monkey face', category: 'nature', keywords: ['monkey', 'animal'] },
|
|
97
|
-
{ emoji: '\u{1F412}', name: 'monkey', category: 'nature', keywords: ['monkey', 'animal'] },
|
|
98
|
-
{ emoji: '\u{1F414}', name: 'chicken', category: 'nature', keywords: ['chicken', 'animal'] },
|
|
99
|
-
{ emoji: '\u{1F427}', name: 'penguin', category: 'nature', keywords: ['penguin', 'animal'] },
|
|
100
|
-
{ emoji: '\u{1F426}', name: 'bird', category: 'nature', keywords: ['bird', 'animal'] },
|
|
101
|
-
{ emoji: '\u{1F40A}', name: 'crocodile', category: 'nature', keywords: ['crocodile', 'animal'] },
|
|
102
|
-
{ emoji: '\u{1F422}', name: 'turtle', category: 'nature', keywords: ['turtle', 'slow'] },
|
|
103
|
-
{ emoji: '\u{1F40D}', name: 'snake', category: 'nature', keywords: ['snake', 'animal'] },
|
|
104
|
-
{ emoji: '\u{1F332}', name: 'evergreen tree', category: 'nature', keywords: ['tree', 'pine'] },
|
|
105
|
-
{ emoji: '\u{1F333}', name: 'deciduous tree', category: 'nature', keywords: ['tree'] },
|
|
106
|
-
{ emoji: '\u{1F334}', name: 'palm tree', category: 'nature', keywords: ['palm', 'tree', 'tropical'] },
|
|
107
|
-
{ emoji: '\u{1F335}', name: 'cactus', category: 'nature', keywords: ['cactus', 'desert'] },
|
|
108
|
-
{ emoji: '\u{1F337}', name: 'tulip', category: 'nature', keywords: ['flower', 'tulip'] },
|
|
109
|
-
{ emoji: '\u{1F339}', name: 'rose', category: 'nature', keywords: ['flower', 'rose', 'love'] },
|
|
110
|
-
],
|
|
111
|
-
food: [
|
|
112
|
-
{ emoji: '\u{1F34E}', name: 'red apple', category: 'food', keywords: ['apple', 'fruit'] },
|
|
113
|
-
{ emoji: '\u{1F34A}', name: 'tangerine', category: 'food', keywords: ['orange', 'fruit'] },
|
|
114
|
-
{ emoji: '\u{1F34B}', name: 'lemon', category: 'food', keywords: ['lemon', 'fruit'] },
|
|
115
|
-
{ emoji: '\u{1F34C}', name: 'banana', category: 'food', keywords: ['banana', 'fruit'] },
|
|
116
|
-
{ emoji: '\u{1F349}', name: 'watermelon', category: 'food', keywords: ['watermelon', 'fruit'] },
|
|
117
|
-
{ emoji: '\u{1F347}', name: 'grapes', category: 'food', keywords: ['grapes', 'fruit'] },
|
|
118
|
-
{ emoji: '\u{1F353}', name: 'strawberry', category: 'food', keywords: ['strawberry', 'fruit'] },
|
|
119
|
-
{ emoji: '\u{1F352}', name: 'cherries', category: 'food', keywords: ['cherry', 'fruit'] },
|
|
120
|
-
{ emoji: '\u{1F351}', name: 'peach', category: 'food', keywords: ['peach', 'fruit'] },
|
|
121
|
-
{ emoji: '\u{1F34D}', name: 'pineapple', category: 'food', keywords: ['pineapple', 'fruit'] },
|
|
122
|
-
{ emoji: '\u{1F354}', name: 'hamburger', category: 'food', keywords: ['burger', 'fast food'] },
|
|
123
|
-
{ emoji: '\u{1F355}', name: 'pizza', category: 'food', keywords: ['pizza', 'food'] },
|
|
124
|
-
{ emoji: '\u{1F32E}', name: 'taco', category: 'food', keywords: ['taco', 'mexican'] },
|
|
125
|
-
{ emoji: '\u{1F32F}', name: 'burrito', category: 'food', keywords: ['burrito', 'mexican'] },
|
|
126
|
-
{ emoji: '\u{1F35F}', name: 'french fries', category: 'food', keywords: ['fries', 'fast food'] },
|
|
127
|
-
{ emoji: '\u{1F357}', name: 'poultry leg', category: 'food', keywords: ['chicken', 'meat'] },
|
|
128
|
-
{ emoji: '\u{1F356}', name: 'meat on bone', category: 'food', keywords: ['meat', 'bone'] },
|
|
129
|
-
{ emoji: '\u{1F363}', name: 'sushi', category: 'food', keywords: ['sushi', 'japanese'] },
|
|
130
|
-
{ emoji: '\u{1F370}', name: 'shortcake', category: 'food', keywords: ['cake', 'dessert'] },
|
|
131
|
-
{ emoji: '\u{1F36B}', name: 'chocolate bar', category: 'food', keywords: ['chocolate', 'candy'] },
|
|
132
|
-
{ emoji: '\u{1F369}', name: 'doughnut', category: 'food', keywords: ['donut', 'dessert'] },
|
|
133
|
-
{ emoji: '\u{1F36A}', name: 'cookie', category: 'food', keywords: ['cookie', 'dessert'] },
|
|
134
|
-
{ emoji: '\u2615', name: 'hot beverage', category: 'food', keywords: ['coffee', 'tea', 'drink'] },
|
|
135
|
-
{ emoji: '\u{1F37A}', name: 'beer mug', category: 'food', keywords: ['beer', 'drink'] },
|
|
136
|
-
{ emoji: '\u{1F377}', name: 'wine glass', category: 'food', keywords: ['wine', 'drink'] },
|
|
137
|
-
],
|
|
138
|
-
travel: [
|
|
139
|
-
{ emoji: '\u{1F697}', name: 'automobile', category: 'travel', keywords: ['car', 'drive'] },
|
|
140
|
-
{ emoji: '\u{1F695}', name: 'taxi', category: 'travel', keywords: ['taxi', 'cab'] },
|
|
141
|
-
{ emoji: '\u{1F68C}', name: 'bus', category: 'travel', keywords: ['bus', 'transit'] },
|
|
142
|
-
{ emoji: '\u{1F682}', name: 'locomotive', category: 'travel', keywords: ['train', 'rail'] },
|
|
143
|
-
{ emoji: '\u2708', name: 'airplane', category: 'travel', keywords: ['plane', 'fly', 'travel'] },
|
|
144
|
-
{ emoji: '\u{1F680}', name: 'rocket', category: 'travel', keywords: ['rocket', 'space', 'launch'] },
|
|
145
|
-
{ emoji: '\u{1F6F8}', name: 'flying saucer', category: 'travel', keywords: ['ufo', 'alien'] },
|
|
146
|
-
{ emoji: '\u{1F6A2}', name: 'ship', category: 'travel', keywords: ['ship', 'boat', 'cruise'] },
|
|
147
|
-
{ emoji: '\u{1F3E0}', name: 'house', category: 'travel', keywords: ['house', 'home'] },
|
|
148
|
-
{ emoji: '\u{1F3E2}', name: 'office building', category: 'travel', keywords: ['office', 'building', 'work'] },
|
|
149
|
-
{ emoji: '\u{1F3EB}', name: 'school', category: 'travel', keywords: ['school', 'education'] },
|
|
150
|
-
{ emoji: '\u{1F3E5}', name: 'hospital', category: 'travel', keywords: ['hospital', 'health'] },
|
|
151
|
-
{ emoji: '\u{1F30D}', name: 'globe showing Europe-Africa', category: 'travel', keywords: ['earth', 'globe', 'world'] },
|
|
152
|
-
{ emoji: '\u{1F30E}', name: 'globe showing Americas', category: 'travel', keywords: ['earth', 'globe', 'world'] },
|
|
153
|
-
{ emoji: '\u{1F30F}', name: 'globe showing Asia-Australia', category: 'travel', keywords: ['earth', 'globe', 'world'] },
|
|
154
|
-
{ emoji: '\u{1F5FC}', name: 'Tokyo tower', category: 'travel', keywords: ['tokyo', 'japan', 'tower'] },
|
|
155
|
-
{ emoji: '\u{1F5FD}', name: 'Statue of Liberty', category: 'travel', keywords: ['statue', 'liberty', 'new york'] },
|
|
156
|
-
{ emoji: '\u26F0', name: 'mountain', category: 'travel', keywords: ['mountain', 'nature'] },
|
|
157
|
-
{ emoji: '\u{1F3D6}', name: 'beach with umbrella', category: 'travel', keywords: ['beach', 'vacation'] },
|
|
158
|
-
{ emoji: '\u{1F3DD}', name: 'desert island', category: 'travel', keywords: ['island', 'tropical'] },
|
|
159
|
-
],
|
|
160
|
-
activities: [
|
|
161
|
-
{ emoji: '\u26BD', name: 'soccer ball', category: 'activities', keywords: ['soccer', 'football', 'sport'] },
|
|
162
|
-
{ emoji: '\u{1F3C0}', name: 'basketball', category: 'activities', keywords: ['basketball', 'sport'] },
|
|
163
|
-
{ emoji: '\u{1F3C8}', name: 'american football', category: 'activities', keywords: ['football', 'sport'] },
|
|
164
|
-
{ emoji: '\u26BE', name: 'baseball', category: 'activities', keywords: ['baseball', 'sport'] },
|
|
165
|
-
{ emoji: '\u{1F3BE}', name: 'tennis', category: 'activities', keywords: ['tennis', 'sport'] },
|
|
166
|
-
{ emoji: '\u{1F3D0}', name: 'volleyball', category: 'activities', keywords: ['volleyball', 'sport'] },
|
|
167
|
-
{ emoji: '\u{1F3B1}', name: 'pool 8 ball', category: 'activities', keywords: ['pool', 'billiards'] },
|
|
168
|
-
{ emoji: '\u{1F3D3}', name: 'ping pong', category: 'activities', keywords: ['table tennis', 'sport'] },
|
|
169
|
-
{ emoji: '\u{1F3C6}', name: 'trophy', category: 'activities', keywords: ['trophy', 'winner', 'award'] },
|
|
170
|
-
{ emoji: '\u{1F3C5}', name: 'sports medal', category: 'activities', keywords: ['medal', 'winner'] },
|
|
171
|
-
{ emoji: '\u{1F947}', name: 'first place medal', category: 'activities', keywords: ['gold', 'first', 'winner'] },
|
|
172
|
-
{ emoji: '\u{1F948}', name: 'second place medal', category: 'activities', keywords: ['silver', 'second'] },
|
|
173
|
-
{ emoji: '\u{1F949}', name: 'third place medal', category: 'activities', keywords: ['bronze', 'third'] },
|
|
174
|
-
{ emoji: '\u{1F3AF}', name: 'bullseye', category: 'activities', keywords: ['target', 'dart'] },
|
|
175
|
-
{ emoji: '\u{1F3AE}', name: 'video game', category: 'activities', keywords: ['game', 'controller'] },
|
|
176
|
-
{ emoji: '\u{1F3B2}', name: 'game die', category: 'activities', keywords: ['dice', 'game'] },
|
|
177
|
-
{ emoji: '\u{1F3B5}', name: 'musical note', category: 'activities', keywords: ['music', 'note'] },
|
|
178
|
-
{ emoji: '\u{1F3B6}', name: 'musical notes', category: 'activities', keywords: ['music', 'notes'] },
|
|
179
|
-
{ emoji: '\u{1F3A4}', name: 'microphone', category: 'activities', keywords: ['karaoke', 'sing', 'mic'] },
|
|
180
|
-
{ emoji: '\u{1F3AC}', name: 'clapper board', category: 'activities', keywords: ['movie', 'film'] },
|
|
181
|
-
],
|
|
182
|
-
objects: [
|
|
183
|
-
{ emoji: '\u{1F4F1}', name: 'mobile phone', category: 'objects', keywords: ['phone', 'mobile', 'cell'] },
|
|
184
|
-
{ emoji: '\u{1F4BB}', name: 'laptop', category: 'objects', keywords: ['laptop', 'computer'] },
|
|
185
|
-
{ emoji: '\u{1F5A5}', name: 'desktop computer', category: 'objects', keywords: ['computer', 'desktop'] },
|
|
186
|
-
{ emoji: '\u2328', name: 'keyboard', category: 'objects', keywords: ['keyboard', 'type'] },
|
|
187
|
-
{ emoji: '\u{1F4F7}', name: 'camera', category: 'objects', keywords: ['camera', 'photo'] },
|
|
188
|
-
{ emoji: '\u{1F4FA}', name: 'television', category: 'objects', keywords: ['tv', 'television'] },
|
|
189
|
-
{ emoji: '\u{1F4E7}', name: 'e-mail', category: 'objects', keywords: ['email', 'mail'] },
|
|
190
|
-
{ emoji: '\u{1F4DD}', name: 'memo', category: 'objects', keywords: ['memo', 'note', 'write'] },
|
|
191
|
-
{ emoji: '\u{1F4D6}', name: 'open book', category: 'objects', keywords: ['book', 'read'] },
|
|
192
|
-
{ emoji: '\u{1F4DA}', name: 'books', category: 'objects', keywords: ['books', 'library'] },
|
|
193
|
-
{ emoji: '\u{1F4C5}', name: 'calendar', category: 'objects', keywords: ['calendar', 'date'] },
|
|
194
|
-
{ emoji: '\u{1F4CB}', name: 'clipboard', category: 'objects', keywords: ['clipboard', 'paste'] },
|
|
195
|
-
{ emoji: '\u{1F4CC}', name: 'pushpin', category: 'objects', keywords: ['pin', 'pushpin'] },
|
|
196
|
-
{ emoji: '\u{1F4CE}', name: 'paperclip', category: 'objects', keywords: ['paperclip', 'attach'] },
|
|
197
|
-
{ emoji: '\u{1F511}', name: 'key', category: 'objects', keywords: ['key', 'lock'] },
|
|
198
|
-
{ emoji: '\u{1F512}', name: 'locked', category: 'objects', keywords: ['lock', 'secure'] },
|
|
199
|
-
{ emoji: '\u{1F513}', name: 'unlocked', category: 'objects', keywords: ['unlock', 'open'] },
|
|
200
|
-
{ emoji: '\u{1F50D}', name: 'magnifying glass tilted left', category: 'objects', keywords: ['search', 'find'] },
|
|
201
|
-
{ emoji: '\u{1F4A1}', name: 'light bulb', category: 'objects', keywords: ['idea', 'light'] },
|
|
202
|
-
{ emoji: '\u{1F527}', name: 'wrench', category: 'objects', keywords: ['tool', 'wrench', 'fix'] },
|
|
203
|
-
{ emoji: '\u{1F528}', name: 'hammer', category: 'objects', keywords: ['tool', 'hammer', 'build'] },
|
|
204
|
-
{ emoji: '\u2699', name: 'gear', category: 'objects', keywords: ['settings', 'gear', 'config'] },
|
|
205
|
-
],
|
|
206
|
-
symbols: [
|
|
207
|
-
{ emoji: '\u2764', name: 'red heart', category: 'symbols', keywords: ['heart', 'love'] },
|
|
208
|
-
{ emoji: '\u{1F9E1}', name: 'orange heart', category: 'symbols', keywords: ['heart', 'orange'] },
|
|
209
|
-
{ emoji: '\u{1F49B}', name: 'yellow heart', category: 'symbols', keywords: ['heart', 'yellow'] },
|
|
210
|
-
{ emoji: '\u{1F49A}', name: 'green heart', category: 'symbols', keywords: ['heart', 'green'] },
|
|
211
|
-
{ emoji: '\u{1F499}', name: 'blue heart', category: 'symbols', keywords: ['heart', 'blue'] },
|
|
212
|
-
{ emoji: '\u{1F49C}', name: 'purple heart', category: 'symbols', keywords: ['heart', 'purple'] },
|
|
213
|
-
{ emoji: '\u{1F494}', name: 'broken heart', category: 'symbols', keywords: ['heart', 'broken', 'sad'] },
|
|
214
|
-
{ emoji: '\u{1F495}', name: 'two hearts', category: 'symbols', keywords: ['heart', 'love'] },
|
|
215
|
-
{ emoji: '\u{1F4AF}', name: 'hundred points', category: 'symbols', keywords: ['100', 'perfect', 'score'] },
|
|
216
|
-
{ emoji: '\u{1F4A5}', name: 'collision', category: 'symbols', keywords: ['boom', 'bang', 'explosion'] },
|
|
217
|
-
{ emoji: '\u{1F4AB}', name: 'dizzy', category: 'symbols', keywords: ['star', 'dizzy'] },
|
|
218
|
-
{ emoji: '\u2728', name: 'sparkles', category: 'symbols', keywords: ['sparkle', 'shine', 'star'] },
|
|
219
|
-
{ emoji: '\u{1F525}', name: 'fire', category: 'symbols', keywords: ['fire', 'hot', 'flame'] },
|
|
220
|
-
{ emoji: '\u2705', name: 'check mark button', category: 'symbols', keywords: ['check', 'done', 'yes'] },
|
|
221
|
-
{ emoji: '\u274C', name: 'cross mark', category: 'symbols', keywords: ['no', 'wrong', 'x'] },
|
|
222
|
-
{ emoji: '\u2757', name: 'exclamation mark', category: 'symbols', keywords: ['exclamation', 'warning', 'alert'] },
|
|
223
|
-
{ emoji: '\u2753', name: 'question mark', category: 'symbols', keywords: ['question', 'what'] },
|
|
224
|
-
{ emoji: '\u{1F6AB}', name: 'prohibited', category: 'symbols', keywords: ['no', 'forbidden', 'stop'] },
|
|
225
|
-
{ emoji: '\u267B', name: 'recycling symbol', category: 'symbols', keywords: ['recycle', 'green'] },
|
|
226
|
-
{ emoji: '\u{1F4AC}', name: 'speech balloon', category: 'symbols', keywords: ['speech', 'chat', 'message'] },
|
|
227
|
-
{ emoji: '\u{1F4AD}', name: 'thought balloon', category: 'symbols', keywords: ['thought', 'think'] },
|
|
228
|
-
{ emoji: '\u{1F514}', name: 'bell', category: 'symbols', keywords: ['bell', 'notification', 'alert'] },
|
|
229
|
-
],
|
|
230
|
-
flags: [
|
|
231
|
-
{ emoji: '\u{1F1FA}\u{1F1F8}', name: 'flag: United States', category: 'flags', keywords: ['us', 'usa', 'america'] },
|
|
232
|
-
{ emoji: '\u{1F1EC}\u{1F1E7}', name: 'flag: United Kingdom', category: 'flags', keywords: ['uk', 'britain', 'england'] },
|
|
233
|
-
{ emoji: '\u{1F1E8}\u{1F1E6}', name: 'flag: Canada', category: 'flags', keywords: ['canada'] },
|
|
234
|
-
{ emoji: '\u{1F1E6}\u{1F1FA}', name: 'flag: Australia', category: 'flags', keywords: ['australia'] },
|
|
235
|
-
{ emoji: '\u{1F1EF}\u{1F1F5}', name: 'flag: Japan', category: 'flags', keywords: ['japan'] },
|
|
236
|
-
{ emoji: '\u{1F1EB}\u{1F1F7}', name: 'flag: France', category: 'flags', keywords: ['france'] },
|
|
237
|
-
{ emoji: '\u{1F1E9}\u{1F1EA}', name: 'flag: Germany', category: 'flags', keywords: ['germany'] },
|
|
238
|
-
{ emoji: '\u{1F1EE}\u{1F1F3}', name: 'flag: India', category: 'flags', keywords: ['india'] },
|
|
239
|
-
{ emoji: '\u{1F1E7}\u{1F1F7}', name: 'flag: Brazil', category: 'flags', keywords: ['brazil'] },
|
|
240
|
-
{ emoji: '\u{1F1F0}\u{1F1F7}', name: 'flag: South Korea', category: 'flags', keywords: ['korea', 'south korea'] },
|
|
241
|
-
{ emoji: '\u{1F1EE}\u{1F1F9}', name: 'flag: Italy', category: 'flags', keywords: ['italy'] },
|
|
242
|
-
{ emoji: '\u{1F1EA}\u{1F1F8}', name: 'flag: Spain', category: 'flags', keywords: ['spain'] },
|
|
243
|
-
{ emoji: '\u{1F1F2}\u{1F1FD}', name: 'flag: Mexico', category: 'flags', keywords: ['mexico'] },
|
|
244
|
-
{ emoji: '\u{1F1F7}\u{1F1FA}', name: 'flag: Russia', category: 'flags', keywords: ['russia'] },
|
|
245
|
-
{ emoji: '\u{1F1E8}\u{1F1F3}', name: 'flag: China', category: 'flags', keywords: ['china'] },
|
|
246
|
-
{ emoji: '\u{1F3C1}', name: 'chequered flag', category: 'flags', keywords: ['finish', 'race'] },
|
|
247
|
-
{ emoji: '\u{1F3F3}', name: 'white flag', category: 'flags', keywords: ['surrender', 'peace'] },
|
|
248
|
-
{ emoji: '\u{1F3F4}', name: 'black flag', category: 'flags', keywords: ['pirate'] },
|
|
249
|
-
{ emoji: '\u{1F6A9}', name: 'triangular flag', category: 'flags', keywords: ['flag', 'marker'] },
|
|
250
|
-
],
|
|
251
|
-
}
|
|
43
|
+
/** Total number of base emoji shipped. */
|
|
44
|
+
export const EMOJI_COUNT_TOTAL = EMOJI_COUNT
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Decode the generated dataset once.
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
252
49
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
'
|
|
259
|
-
|
|
260
|
-
'activities',
|
|
261
|
-
'objects',
|
|
262
|
-
'symbols',
|
|
263
|
-
'flags',
|
|
264
|
-
]
|
|
50
|
+
const ALL_EMOJIS: EmojiEntry[] = EMOJI_TUPLES.map((t) => ({
|
|
51
|
+
emoji: t[0],
|
|
52
|
+
name: t[1],
|
|
53
|
+
category: CATEGORY_ORDER[t[2]] as EmojiCategory,
|
|
54
|
+
shortcode: t[3],
|
|
55
|
+
keywords: t[4] ? t[4].split(' ') : [],
|
|
56
|
+
}))
|
|
265
57
|
|
|
266
|
-
/**
|
|
58
|
+
/** All categories in display order. */
|
|
59
|
+
export const EMOJI_CATEGORIES: EmojiCategory[] = [...CATEGORY_ORDER]
|
|
60
|
+
|
|
61
|
+
/** Category display labels. */
|
|
267
62
|
export const CATEGORY_LABELS: Record<EmojiCategory, string> = {
|
|
268
63
|
smileys: 'Smileys & Emotion',
|
|
269
64
|
people: 'People & Body',
|
|
@@ -276,7 +71,206 @@ export const CATEGORY_LABELS: Record<EmojiCategory, string> = {
|
|
|
276
71
|
flags: 'Flags',
|
|
277
72
|
}
|
|
278
73
|
|
|
279
|
-
/**
|
|
74
|
+
/** Emojis grouped by category (display order preserved within each group). */
|
|
75
|
+
export const EMOJI_DATA: Record<EmojiCategory, EmojiEntry[]> = EMOJI_CATEGORIES.reduce(
|
|
76
|
+
(acc, cat) => {
|
|
77
|
+
acc[cat] = ALL_EMOJIS.filter((e) => e.category === cat)
|
|
78
|
+
return acc
|
|
79
|
+
},
|
|
80
|
+
{} as Record<EmojiCategory, EmojiEntry[]>,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
/** Get all emojis as a flat array (display order). */
|
|
280
84
|
export function getAllEmojis(): EmojiEntry[] {
|
|
281
|
-
return
|
|
85
|
+
return ALL_EMOJIS
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Shortcodes: derived (from names) + curated popular aliases.
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Curated popular aliases (`:body:` → glyph) that don't match the derived
|
|
94
|
+
* snake_case name — the short forms people actually type (`:joy:`, `:100:`,
|
|
95
|
+
* `:thumbsup:`). These take precedence over derived shortcodes on lookup.
|
|
96
|
+
* This is the ONE place aliases live (rich-editor consumes it, no duplication).
|
|
97
|
+
*/
|
|
98
|
+
export const SHORTCODE_ALIASES: Record<string, string> = {
|
|
99
|
+
smile: '\u{1F604}',
|
|
100
|
+
laugh: '\u{1F602}',
|
|
101
|
+
joy: '\u{1F602}',
|
|
102
|
+
grin: '\u{1F601}',
|
|
103
|
+
wink: '\u{1F609}',
|
|
104
|
+
blush: '\u{1F60A}',
|
|
105
|
+
heart_eyes: '\u{1F60D}',
|
|
106
|
+
kissing: '\u{1F617}',
|
|
107
|
+
thinking: '\u{1F914}',
|
|
108
|
+
neutral: '\u{1F610}',
|
|
109
|
+
expressionless: '\u{1F611}',
|
|
110
|
+
unamused: '\u{1F612}',
|
|
111
|
+
sweat: '\u{1F613}',
|
|
112
|
+
pensive: '\u{1F614}',
|
|
113
|
+
confused: '\u{1F615}',
|
|
114
|
+
disappointed: '\u{1F61E}',
|
|
115
|
+
worried: '\u{1F61F}',
|
|
116
|
+
angry: '\u{1F620}',
|
|
117
|
+
rage: '\u{1F621}',
|
|
118
|
+
cry: '\u{1F622}',
|
|
119
|
+
sob: '\u{1F62D}',
|
|
120
|
+
scream: '\u{1F631}',
|
|
121
|
+
fearful: '\u{1F628}',
|
|
122
|
+
cold_sweat: '\u{1F630}',
|
|
123
|
+
relieved: '\u{1F60C}',
|
|
124
|
+
sleepy: '\u{1F62A}',
|
|
125
|
+
sleeping: '\u{1F634}',
|
|
126
|
+
mask: '\u{1F637}',
|
|
127
|
+
sunglasses: '\u{1F60E}',
|
|
128
|
+
nerd: '\u{1F913}',
|
|
129
|
+
heart: '\u{2764}\u{FE0F}',
|
|
130
|
+
broken_heart: '\u{1F494}',
|
|
131
|
+
sparkling_heart: '\u{1F496}',
|
|
132
|
+
two_hearts: '\u{1F495}',
|
|
133
|
+
fire: '\u{1F525}',
|
|
134
|
+
'100': '\u{1F4AF}',
|
|
135
|
+
star: '\u{2B50}',
|
|
136
|
+
star2: '\u{1F31F}',
|
|
137
|
+
sparkles: '\u{2728}',
|
|
138
|
+
thumbsup: '\u{1F44D}',
|
|
139
|
+
thumbsdown: '\u{1F44E}',
|
|
140
|
+
wave: '\u{1F44B}',
|
|
141
|
+
clap: '\u{1F44F}',
|
|
142
|
+
raised_hands: '\u{1F64C}',
|
|
143
|
+
pray: '\u{1F64F}',
|
|
144
|
+
muscle: '\u{1F4AA}',
|
|
145
|
+
point_up: '\u{261D}\u{FE0F}',
|
|
146
|
+
point_down: '\u{1F447}',
|
|
147
|
+
point_left: '\u{1F448}',
|
|
148
|
+
point_right: '\u{1F449}',
|
|
149
|
+
ok_hand: '\u{1F44C}',
|
|
150
|
+
v: '\u{270C}\u{FE0F}',
|
|
151
|
+
eyes: '\u{1F440}',
|
|
152
|
+
tongue: '\u{1F445}',
|
|
153
|
+
lips: '\u{1F444}',
|
|
154
|
+
rocket: '\u{1F680}',
|
|
155
|
+
tada: '\u{1F389}',
|
|
156
|
+
confetti: '\u{1F38A}',
|
|
157
|
+
balloon: '\u{1F388}',
|
|
158
|
+
gift: '\u{1F381}',
|
|
159
|
+
trophy: '\u{1F3C6}',
|
|
160
|
+
medal: '\u{1F3C5}',
|
|
161
|
+
crown: '\u{1F451}',
|
|
162
|
+
gem: '\u{1F48E}',
|
|
163
|
+
bell: '\u{1F514}',
|
|
164
|
+
check: '\u{2705}',
|
|
165
|
+
x: '\u{274C}',
|
|
166
|
+
warning: '\u{26A0}\u{FE0F}',
|
|
167
|
+
no_entry: '\u{26D4}',
|
|
168
|
+
question: '\u{2753}',
|
|
169
|
+
exclamation: '\u{2757}',
|
|
170
|
+
bulb: '\u{1F4A1}',
|
|
171
|
+
book: '\u{1F4D6}',
|
|
172
|
+
memo: '\u{1F4DD}',
|
|
173
|
+
pencil: '\u{270F}\u{FE0F}',
|
|
174
|
+
calendar: '\u{1F4C5}',
|
|
175
|
+
clock: '\u{1F550}',
|
|
176
|
+
hourglass: '\u{231B}',
|
|
177
|
+
lock: '\u{1F512}',
|
|
178
|
+
key: '\u{1F511}',
|
|
179
|
+
hammer: '\u{1F528}',
|
|
180
|
+
wrench: '\u{1F527}',
|
|
181
|
+
link: '\u{1F517}',
|
|
182
|
+
paperclip: '\u{1F4CE}',
|
|
183
|
+
scissors: '\u{2702}\u{FE0F}',
|
|
184
|
+
package: '\u{1F4E6}',
|
|
185
|
+
email: '\u{1F4E7}',
|
|
186
|
+
phone: '\u{1F4DE}',
|
|
187
|
+
computer: '\u{1F4BB}',
|
|
188
|
+
house: '\u{1F3E0}',
|
|
189
|
+
tree: '\u{1F333}',
|
|
190
|
+
sun: '\u{2600}\u{FE0F}',
|
|
191
|
+
moon: '\u{1F319}',
|
|
192
|
+
cloud: '\u{2601}\u{FE0F}',
|
|
193
|
+
rain: '\u{1F327}\u{FE0F}',
|
|
194
|
+
snow: '\u{2744}\u{FE0F}',
|
|
195
|
+
zap: '\u{26A1}',
|
|
196
|
+
rainbow: '\u{1F308}',
|
|
197
|
+
pizza: '\u{1F355}',
|
|
198
|
+
hamburger: '\u{1F354}',
|
|
199
|
+
coffee: '\u{2615}',
|
|
200
|
+
beer: '\u{1F37A}',
|
|
201
|
+
wine: '\u{1F377}',
|
|
202
|
+
cake: '\u{1F370}',
|
|
203
|
+
dog: '\u{1F436}',
|
|
204
|
+
cat: '\u{1F431}',
|
|
205
|
+
poop: '\u{1F4A9}',
|
|
206
|
+
ghost: '\u{1F47B}',
|
|
207
|
+
skull: '\u{1F480}',
|
|
208
|
+
alien: '\u{1F47D}',
|
|
209
|
+
robot: '\u{1F916}',
|
|
210
|
+
handshake: '\u{1F91D}',
|
|
211
|
+
salute: '\u{1FAE1}',
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** shortcode body → EmojiEntry, for the derived (name-based) shortcodes. */
|
|
215
|
+
const BY_SHORTCODE = new Map<string, EmojiEntry>()
|
|
216
|
+
for (const e of ALL_EMOJIS) BY_SHORTCODE.set(e.shortcode, e)
|
|
217
|
+
/** glyph → EmojiEntry (for alias resolution back to a full entry). */
|
|
218
|
+
const BY_GLYPH = new Map<string, EmojiEntry>()
|
|
219
|
+
for (const e of ALL_EMOJIS) if (!BY_GLYPH.has(e.emoji)) BY_GLYPH.set(e.emoji, e)
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Resolve a `:shortcode:` (with or without surrounding colons) to its glyph.
|
|
223
|
+
* Aliases win over derived shortcodes. Returns `null` when unknown.
|
|
224
|
+
*/
|
|
225
|
+
export function resolveShortcode(code: string): string | null {
|
|
226
|
+
const body = code.replace(/^:/, '').replace(/:$/, '').toLowerCase()
|
|
227
|
+
if (body in SHORTCODE_ALIASES) return SHORTCODE_ALIASES[body]
|
|
228
|
+
const entry = BY_SHORTCODE.get(body)
|
|
229
|
+
return entry ? entry.emoji : null
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* `:body:` → glyph map covering the full derived set plus curated aliases.
|
|
234
|
+
* This is the shared source rich-editor's `EMOJI_MAP` is built from.
|
|
235
|
+
*/
|
|
236
|
+
export function buildShortcodeMap(): Record<string, string> {
|
|
237
|
+
const map: Record<string, string> = {}
|
|
238
|
+
for (const e of ALL_EMOJIS) map[`:${e.shortcode}:`] = e.emoji
|
|
239
|
+
for (const [body, glyph] of Object.entries(SHORTCODE_ALIASES)) map[`:${body}:`] = glyph
|
|
240
|
+
return map
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Fuzzy-search emoji by name, keyword, or shortcode. Prefix matches on the
|
|
245
|
+
* shortcode/name rank first. Used by the picker search and the `:` trigger.
|
|
246
|
+
*/
|
|
247
|
+
export function searchEmojis(query: string, limit = 50): EmojiEntry[] {
|
|
248
|
+
const q = query.toLowerCase().replace(/^:/, '').replace(/:$/, '').trim()
|
|
249
|
+
if (q === '') return ALL_EMOJIS.slice(0, limit)
|
|
250
|
+
|
|
251
|
+
const scored: { entry: EmojiEntry; score: number }[] = []
|
|
252
|
+
for (const entry of ALL_EMOJIS) {
|
|
253
|
+
const sc = entry.shortcode
|
|
254
|
+
const name = entry.name.toLowerCase()
|
|
255
|
+
let score = -1
|
|
256
|
+
if (sc === q) score = 100
|
|
257
|
+
else if (sc.startsWith(q)) score = 80
|
|
258
|
+
else if (name.startsWith(q)) score = 70
|
|
259
|
+
else if (sc.includes(q)) score = 50
|
|
260
|
+
else if (name.includes(q)) score = 40
|
|
261
|
+
else if (entry.keywords.some((k) => k.startsWith(q))) score = 30
|
|
262
|
+
else if (entry.keywords.some((k) => k.includes(q))) score = 15
|
|
263
|
+
if (score >= 0) scored.push({ entry, score })
|
|
264
|
+
}
|
|
265
|
+
// Include alias-only matches (e.g. `joy`) that point at a known glyph.
|
|
266
|
+
for (const [body, glyph] of Object.entries(SHORTCODE_ALIASES)) {
|
|
267
|
+
if (!body.includes(q)) continue
|
|
268
|
+
const entry = BY_GLYPH.get(glyph)
|
|
269
|
+
if (!entry) continue
|
|
270
|
+
if (scored.some((s) => s.entry === entry)) continue
|
|
271
|
+
scored.push({ entry, score: body === q ? 95 : body.startsWith(q) ? 60 : 20 })
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
scored.sort((a, b) => b.score - a.score || a.entry.name.localeCompare(b.entry.name))
|
|
275
|
+
return scored.slice(0, limit).map((s) => s.entry)
|
|
282
276
|
}
|