@@ -61,6 +61,7 @@ export class CharacterCount extends ConfigurableComponent {
6161 const {
6262 i18n,
6363 maxlength,
64+ countFunction,
6465 countType,
6566 screenReaderCountMessageClass,
6667 textareaDescriptionClass,
@@ -72,7 +73,7 @@ export class CharacterCount extends ConfigurableComponent {
7273 locale : closestAttributeValue ( this . $root , 'lang' )
7374 } )
7475
75- if ( countType === 'characters' ) {
76+ if ( countType === 'characters' || ! ! countFunction ) {
7677 if ( ! ( 'Segmenter' in Intl ) ) {
7778 throw new SupportError (
7879 formatErrorMessage (
@@ -83,7 +84,7 @@ export class CharacterCount extends ConfigurableComponent {
8384 }
8485
8586 this . segmenter = new Intl . Segmenter ( this . i18n . locale , {
86- granularity : 'grapheme'
87+ granularity : countType === 'words' ? 'word' : 'grapheme'
8788 } )
8889 }
8990
@@ -220,31 +221,12 @@ export class CharacterCount extends ConfigurableComponent {
220221 * @param {string } [text] - Deprecated
221222 */
222223 updateCount ( text ) {
223- const { $textarea } = this
224- const { countType } = this . config
224+ const { $textarea, countFunctions } = this
225+ let { countType, countFunction } = this . config
225226
226- text = text ?? $textarea . value
227-
228- switch ( countType ) {
229- case 'length' :
230- // Count code points (string length)
231- this . length = text . length
232- break
233-
234- case 'characters' : {
235- // Count grapheme clusters (user-perceived characters)
236- this . length = this . segmenter
237- ? Array . from ( this . segmenter . segment ( text ) ) . length
238- : 0
239-
240- break
241- }
242-
243- case 'words' :
244- // Count consecutive non-whitespace results
245- this . length = text . match ( / \S + / g) ?. length ?? 0
246- break
247- }
227+ text ??= $textarea . value
228+ countFunction ??= countFunctions [ countType ]
229+ this . length = countFunction . call ( this , text )
248230 }
249231
250232 /**
@@ -467,6 +449,46 @@ export class CharacterCount extends ConfigurableComponent {
467449 }
468450 }
469451
452+ /**
453+ * Character count functions
454+ *
455+ * @constant
456+ * @satisfies {Record<string, CharacterCountConfig['countFunction']> }
457+ */
458+ countFunctions = Object . freeze ( {
459+ /**
460+ * Count code points (string length)
461+ *
462+ * @param {string } text - Textarea value
463+ * @returns {number } Count
464+ */
465+ length ( text ) {
466+ return text . length
467+ } ,
468+
469+ /**
470+ * Count grapheme clusters (user-perceived characters)
471+ *
472+ * @param {string } text - Textarea value
473+ * @returns {number } Count
474+ */
475+ characters ( text ) {
476+ return this . segmenter
477+ ? Array . from ( this . segmenter . segment ( text ) ) . length
478+ : 0
479+ } ,
480+
481+ /**
482+ * Count consecutive non-whitespace results
483+ *
484+ * @param {string } text - Textarea value
485+ * @returns {number } Count
486+ */
487+ words ( text ) {
488+ return text . match ( / \S + / g) ?. length ?? 0
489+ }
490+ } )
491+
470492 /**
471493 * Name for the component used when initialising using data-module attributes
472494 */
@@ -524,6 +546,7 @@ export class CharacterCount extends ConfigurableComponent {
524546 maxlength : { type : 'number' } ,
525547 threshold : { type : 'number' } ,
526548 countType : { type : 'string' } ,
549+ countFunction : { type : 'function' } ,
527550 textareaDescriptionClass : { type : 'string' } ,
528551 visibleCountMessageClass : { type : 'string' } ,
529552 screenReaderCountMessageClass : { type : 'string' } ,
@@ -574,12 +597,20 @@ export function initCharacterCounts(options) {
574597 * count message will be hidden by default.
575598 * @property {'characters' | 'length' | 'words' } countType - The count type
576599 * (`"characters"`, `"length"` or `"words"`) used to count the text.
600+ * @property {CharacterCountFunction } [countFunction] - Custom character or
601+ * word counting function.
577602 * @property {string } textareaDescriptionClass - Textarea description class
578603 * @property {string } visibleCountMessageClass - Visible count message class
579604 * @property {string } screenReaderCountMessageClass - Screen reader count message class
580605 * @property {CharacterCountTranslations } [i18n=CharacterCount.defaults.i18n] - Character count translations
581606 */
582607
608+ /**
609+ * Character count function
610+ *
611+ * @typedef {(this: CharacterCount, text: string) => number } CharacterCountFunction
612+ */
613+
583614/**
584615 * Character count translations
585616 *
0 commit comments