55 configOverride
66} from '../../common/configuration.mjs'
77import { formatErrorMessage } from '../../common/index.mjs'
8- import { ConfigError , ElementError } from '../../errors/index.mjs'
8+ import { ConfigError , ElementError , SupportError } from '../../errors/index.mjs'
99import { I18n } from '../../i18n.mjs'
1010
1111/**
@@ -28,6 +28,11 @@ export class CharacterCount extends ConfigurableComponent {
2828 /** @private */
2929 count = 0
3030
31+ /**
32+ * @type {Intl.Segmenter | null }
33+ */
34+ segmenter = null
35+
3136 /** @private */
3237 $visibleCountMessage
3338
@@ -126,6 +131,21 @@ export class CharacterCount extends ConfigurableComponent {
126131 locale : closestAttributeValue ( this . $root , 'lang' )
127132 } )
128133
134+ if ( this . config . countType === 'characters' ) {
135+ if ( ! ( 'Segmenter' in Intl ) ) {
136+ throw new SupportError (
137+ formatErrorMessage (
138+ CharacterCount ,
139+ 'Support for "Intl.Segmenter" required'
140+ )
141+ )
142+ }
143+
144+ this . segmenter = new Intl . Segmenter ( this . i18n . locale , {
145+ granularity : 'grapheme'
146+ } )
147+ }
148+
129149 // Determine the limit attribute (characters or words)
130150 this . maxLength = this . config . maxlength ?? Infinity
131151
@@ -360,8 +380,18 @@ export class CharacterCount extends ConfigurableComponent {
360380 updateCount ( ) {
361381 const text = this . $textarea . value
362382
383+ // Count grapheme clusters (user-perceived characters)
384+ if ( this . config . countType === 'characters' ) {
385+ this . count = this . segmenter
386+ ? Array . from ( this . segmenter . segment ( text ) ) . length
387+ : 0
388+
389+ return
390+ }
391+
392+ // Count consecutive non-whitespace results
363393 if ( this . config . countType === 'words' ) {
364- const tokens = text . match ( / \S + / g) ?? [ ] // Matches consecutive non-whitespace chars
394+ const tokens = text . match ( / \S + / g) ?? [ ]
365395 this . count = tokens . length
366396 return
367397 }
@@ -515,8 +545,8 @@ export class CharacterCount extends ConfigurableComponent {
515545 * @property {number } [threshold=0] - The percentage value of the limit at
516546 * which point the count message is displayed. If this attribute is set, the
517547 * count message will be hidden by default.
518- * @property {'length' | 'words' } [countType] - The count type (`"length"` or
519- * `"words"`) used to count the text.
548+ * @property {'characters' | ' length' | 'words' } [countType] - The count type
549+ * (`"characters"`, `"length"` or `"words"`) used to count the text.
520550 * @property {CharacterCountTranslations } [i18n=CharacterCount.defaults.i18n] - Character count translations
521551 */
522552
0 commit comments