@@ -42,8 +42,63 @@ const SKIN_TONE_MODIFIERS = [
4242 '\u{1F3FF}' // Dark Skin Tone
4343] ;
4444
45+ const ZERO_WIDTH_JOINER = '\u200D' ;
46+
4547const isSkinToneModifier = ( unicode : string ) : boolean => SKIN_TONE_MODIFIERS . includes ( unicode ) ;
4648
49+ const startsWithEmojiModifier = ( text : string ) : boolean => {
50+ if ( ! text ) return false ;
51+
52+ for ( const tone of SKIN_TONE_MODIFIERS ) {
53+ if ( text . startsWith ( tone ) ) return true ;
54+ }
55+
56+ return text . startsWith ( ZERO_WIDTH_JOINER ) ;
57+ } ;
58+
59+ const extractEmojiModifiers = ( text : string ) : { modifiers : string ; remaining : string } => {
60+ let modifiers = '' ;
61+ let remaining = text ;
62+
63+ while ( remaining . length > 0 ) {
64+ let matched = false ;
65+
66+ for ( const tone of SKIN_TONE_MODIFIERS ) {
67+ if ( remaining . startsWith ( tone ) ) {
68+ modifiers += tone ;
69+ remaining = remaining . slice ( tone . length ) ;
70+ matched = true ;
71+ break ;
72+ }
73+ }
74+
75+ if ( ! matched ) {
76+ if ( remaining . startsWith ( ZERO_WIDTH_JOINER ) ) {
77+ modifiers += ZERO_WIDTH_JOINER ;
78+ remaining = remaining . slice ( ZERO_WIDTH_JOINER . length ) ;
79+ matched = true ;
80+ }
81+ }
82+
83+ if ( ! matched ) {
84+ const char = remaining . charAt ( 0 ) ;
85+ const codePoint = char . codePointAt ( 0 ) ;
86+ if ( codePoint && (
87+ ( codePoint >= 0x2600 && codePoint <= 0x27BF ) || // Misc symbols
88+ ( codePoint >= 0x1F300 && codePoint <= 0x1F9FF ) || // Emoji ranges
89+ ( codePoint >= 0xFE00 && codePoint <= 0xFE0F ) // Variation selectors
90+ ) ) {
91+ modifiers += char ;
92+ remaining = remaining . slice ( char . length ) ;
93+ } else {
94+ break ;
95+ }
96+ }
97+ }
98+
99+ return { modifiers, remaining } ;
100+ } ;
101+
47102const combineEmojisWithSkinTones = ( items : any [ ] ) : any [ ] => {
48103 if ( ! Array . isArray ( items ) || items . length === 0 ) return items ;
49104
@@ -53,6 +108,33 @@ const combineEmojisWithSkinTones = (items: any[]): any[] => {
53108 while ( i < items . length ) {
54109 const current = items [ i ] ;
55110
111+ if (
112+ current ?. type === 'EMOJI' &&
113+ i + 1 < items . length &&
114+ items [ i + 1 ] ?. type === 'PLAIN_TEXT'
115+ ) {
116+ const nextText = items [ i + 1 ] . value ;
117+
118+ if ( startsWithEmojiModifier ( nextText ) ) {
119+ const { modifiers, remaining } = extractEmojiModifiers ( nextText ) ;
120+
121+ combined . push ( {
122+ ...current ,
123+ unicode : current . unicode + modifiers
124+ } ) ;
125+
126+ if ( remaining ) {
127+ combined . push ( {
128+ type : 'PLAIN_TEXT' ,
129+ value : remaining
130+ } ) ;
131+ }
132+
133+ i += 2 ;
134+ continue ;
135+ }
136+ }
137+
56138 if (
57139 current ?. type === 'EMOJI' &&
58140 i + 1 < items . length &&
@@ -64,10 +146,11 @@ const combineEmojisWithSkinTones = (items: any[]): any[] => {
64146 unicode : current . unicode + items [ i + 1 ] . unicode
65147 } ) ;
66148 i += 2 ;
67- } else {
68- combined . push ( current ) ;
69- i += 1 ;
149+ continue ;
70150 }
151+
152+ combined . push ( current ) ;
153+ i += 1 ;
71154 }
72155
73156 return combined ;
0 commit comments