Skip to content

Commit 9492e12

Browse files
Add character count support for countFunction option
1 parent 17f9921 commit 9492e12

2 files changed

Lines changed: 46 additions & 5 deletions

File tree

packages/nhsuk-frontend/src/nhsuk/components/character-count/character-count.jsdom.test.mjs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,40 @@ describe('Character count', () => {
375375

376376
expect(component.getCountMessage()).toBe('You have 97 words remaining')
377377
})
378+
379+
it('uses custom `countFunction` for `maxlength` limit when set', async () => {
380+
const component = new CharacterCount($root, {
381+
maxlength: 100,
382+
countFunction: jest.fn().mockReturnValue(10)
383+
})
384+
385+
$textarea.focus()
386+
await user.keyboard('Newly updated value')
387+
388+
expect(component.config.countFunction).toHaveBeenLastCalledWith(
389+
'Newly updated value'
390+
)
391+
392+
expect(component.getCountMessage()).toBe(
393+
'You have 90 characters remaining'
394+
)
395+
})
396+
397+
it('uses custom `countFunction` for `maxwords` limit when set', async () => {
398+
const component = new CharacterCount($root, {
399+
maxwords: 100,
400+
countFunction: jest.fn().mockReturnValue(10)
401+
})
402+
403+
$textarea.focus()
404+
await user.keyboard('Newly updated value')
405+
406+
expect(component.config.countFunction).toHaveBeenLastCalledWith(
407+
'Newly updated value'
408+
)
409+
410+
expect(component.getCountMessage()).toBe('You have 90 words remaining')
411+
})
378412
})
379413

380414
describe('with HTML lang attribute', () => {

packages/nhsuk-frontend/src/nhsuk/components/character-count/character-count.mjs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export class CharacterCount extends ConfigurableComponent {
6161
const {
6262
i18n,
6363
maxlength,
64+
countFunction,
6465
countType,
6566
screenReaderCountMessageClass,
6667
textareaDescriptionClass,
@@ -72,9 +73,12 @@ export class CharacterCount extends ConfigurableComponent {
7273
locale: closestAttributeValue(this.$root, 'lang')
7374
})
7475

75-
if ('Segmenter' in Intl && countType === 'characters') {
76+
if (
77+
'Segmenter' in Intl &&
78+
(countType === 'characters' || !!countFunction)
79+
) {
7680
this.segmenter = new Intl.Segmenter(this.i18n.locale, {
77-
granularity: 'grapheme'
81+
granularity: countType === 'words' ? 'word' : 'grapheme'
7882
})
7983
}
8084

@@ -212,10 +216,11 @@ export class CharacterCount extends ConfigurableComponent {
212216
*/
213217
updateCount(text) {
214218
const { $textarea, countFunctions } = this
215-
const { countType = 'length' } = this.config
219+
let { countType = 'length', countFunction } = this.config
216220

217221
text ??= $textarea.value
218-
this.length = countFunctions[countType].call(this, text)
222+
countFunction ??= countFunctions[countType]
223+
this.length = countFunction.call(this, text)
219224
}
220225

221226
/**
@@ -442,7 +447,7 @@ export class CharacterCount extends ConfigurableComponent {
442447
* Character count functions
443448
*
444449
* @constant
445-
* @satisfies {Record<string, (this: CharacterCount, text: string) => number>}
450+
* @satisfies {Record<string, CharacterCountConfig['countFunction']>}
446451
*/
447452
countFunctions = Object.freeze({
448453
length(text) {
@@ -518,6 +523,7 @@ export class CharacterCount extends ConfigurableComponent {
518523
maxlength: { type: 'number' },
519524
threshold: { type: 'number' },
520525
countType: { type: 'string' },
526+
countFunction: { type: 'function' },
521527
textareaDescriptionClass: { type: 'string' },
522528
visibleCountMessageClass: { type: 'string' },
523529
screenReaderCountMessageClass: { type: 'string' },
@@ -564,6 +570,7 @@ export function initCharacterCounts(options) {
564570
* @property {number} [threshold=0] - The percentage value of the limit at which point the count message is displayed.
565571
* If this attribute is set, the count message will be hidden by default.
566572
* @property {'length' | 'characters' | 'words'} [countType] - The count type (`"characters"` or `"words"`) used to count the text.
573+
* @property {(this: CharacterCount, text: string) => number} [countFunction] - Custom character or word counting function.
567574
* @property {string} textareaDescriptionClass - Textarea description class
568575
* @property {string} visibleCountMessageClass - Visible count message class
569576
* @property {string} screenReaderCountMessageClass - Screen reader count message class

0 commit comments

Comments
 (0)