@@ -10,9 +10,11 @@ const UNWANTED_ENCLOSURES_REGEX: RegExp = /[\(\[\{][^\)\]\}]*[\)\]\}]/g;
1010
1111/**
1212 * Regular expression matching special ASCII characters except space, plus some unicode special characters.
13- * Applies after unwanted enclosures have been removed
13+ * Applies after unwanted enclosures have been removed.
14+ * Note: the range starts at \uE000 (not \uD800) to avoid matching surrogate code units, which would break
15+ * supplementary Unicode characters (encoded as surrogate pairs in UTF-16) such as GB18030-2022 extension characters.
1416 */
15- const UNWANTED_CHARS_REGEX : RegExp = / [ \0 - \u001F \! - / : - @ \[ - ` \{ - \u00BF \u0250 - \u036F \uD800 - \uFFFF ] / g;
17+ const UNWANTED_CHARS_REGEX : RegExp = / [ \0 - \u001F \! - / : - @ \[ - ` \{ - \u00BF \u0250 - \u036F \uE000 - \uFFFF ] / g;
1618
1719/**
1820 * Regular expression matching phone numbers. Applied after chars matching UNWANTED_CHARS_REGEX have been removed
@@ -28,30 +30,43 @@ const MULTIPLE_WHITESPACES_REGEX: RegExp = /\s+/g;
2830 * Arabic: Arabic, Arabic Supplement, Arabic Extended-A.
2931 * Korean: Hangul Jamo, Hangul Compatibility Jamo, Hangul Jamo Extended-A, Hangul Syllables, Hangul Jamo Extended-B.
3032 * Japanese: Hiragana, Katakana.
31- * CJK: CJK Unified Ideographs Extension A, CJK Unified Ideographs, CJK Compatibility Ideographs,
32- * CJK Unified Ideographs Extension B
33+ * CJK: CJK Unified Ideographs Extension A, CJK Unified Ideographs, CJK Compatibility Ideographs.
34+ * Note: Supplementary CJK characters (GB18030-2022 extension characters in Ext B-I) are intentionally not listed
35+ * here so they can be rendered as initials.
3336 */
3437const UNSUPPORTED_TEXT_REGEX : RegExp =
35- / [ \u0600 - \u06FF \u0750 - \u077F \u08A0 - \u08FF \u1100 - \u11FF \u3130 - \u318F \uA960 - \uA97F \uAC00 - \uD7AF \uD7B0 - \uD7FF \u3040 - \u309F \u30A0 - \u30FF \u3400 - \u4DBF \u4E00 - \u9FFF \uF900 - \uFAFF ] | [ \uD840 - \uD869 ] [ \uDC00 - \uDED6 ] / ;
38+ / [ \u0600 - \u06FF \u0750 - \u077F \u08A0 - \u08FF \u1100 - \u11FF \u3130 - \u318F \uA960 - \uA97F \uAC00 - \uD7AF \uD7B0 - \uD7FF \u3040 - \u309F \u30A0 - \u30FF \u3400 - \u4DBF \u4E00 - \u9FFF \uF900 - \uFAFF ] / ;
39+
40+ function getFirstCodePoint ( value : string ) : string {
41+ if ( ! value ) {
42+ return '' ;
43+ }
44+
45+ const codePoint = value . codePointAt ( 0 ) ;
46+ return codePoint === undefined ? '' : String . fromCodePoint ( codePoint ) ;
47+ }
3648
3749function getInitialsLatin ( displayName : string , isRtl : boolean , firstInitialOnly ?: boolean ) : string {
3850 let initials = '' ;
3951
4052 const splits : string [ ] = displayName . split ( ' ' ) ;
4153 if ( splits . length !== 0 ) {
42- initials += splits [ 0 ] . charAt ( 0 ) . toUpperCase ( ) ;
54+ // Use code point-aware helper to correctly handle supplementary characters (e.g. GB18030-2022 extension chars)
55+ // that are encoded as surrogate pairs; charAt(0) would only return half of such a character.
56+ initials += getFirstCodePoint ( splits [ 0 ] ) . toUpperCase ( ) ;
4357 }
4458
4559 if ( ! firstInitialOnly ) {
4660 if ( splits . length === 2 ) {
47- initials += splits [ 1 ] . charAt ( 0 ) . toUpperCase ( ) ;
61+ initials += getFirstCodePoint ( splits [ 1 ] ) . toUpperCase ( ) ;
4862 } else if ( splits . length === 3 ) {
49- initials += splits [ 2 ] . charAt ( 0 ) . toUpperCase ( ) ;
63+ initials += getFirstCodePoint ( splits [ 2 ] ) . toUpperCase ( ) ;
5064 }
5165 }
5266
53- if ( isRtl && initials . length > 1 ) {
54- return initials . charAt ( 1 ) + initials . charAt ( 0 ) ;
67+ if ( isRtl && [ ...initials ] . length > 1 ) {
68+ const chars = [ ...initials ] ;
69+ return chars [ 1 ] + chars [ 0 ] ;
5570 }
5671
5772 return initials ;
@@ -95,9 +110,12 @@ export function getInitials(
95110
96111 displayName = cleanupDisplayName ( displayName ) ;
97112
98- // For names containing CJK characters, and phone numbers, we don't display initials
113+ // Check only the first code point against UNSUPPORTED_TEXT_REGEX so that names starting with a supported
114+ // character (e.g. GB18030-2022 extension characters) produce an initial even when the rest of the string
115+ // contains BMP CJK characters that would otherwise trigger the regex.
116+ const firstCodePoint = getFirstCodePoint ( displayName ) ;
99117 if (
100- UNSUPPORTED_TEXT_REGEX . test ( displayName ) ||
118+ UNSUPPORTED_TEXT_REGEX . test ( firstCodePoint ) ||
101119 ( ! options ?. allowPhoneInitials && PHONENUMBER_REGEX . test ( displayName ) )
102120 ) {
103121 return '' ;
0 commit comments