Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ export class CharacterCount extends ConfigurableComponent {
*/
segmenter = null

/**
* Split words between consecutive whitespace
*
* @type {RegExp}
*/
separator = /\s+/g

/**
* @type {number | null}
*/
Expand Down Expand Up @@ -61,6 +68,7 @@ export class CharacterCount extends ConfigurableComponent {
const {
i18n,
maxlength,
maxwords,
countFunction,
countType,
screenReaderCountMessageClass,
Expand All @@ -73,7 +81,11 @@ export class CharacterCount extends ConfigurableComponent {
locale: closestAttributeValue(this.$root, 'lang')
})

if (countType === 'characters' || !!countFunction) {
if (
countType === 'characters' ||
(countType === 'words' && maxwords === undefined) ||
!!countFunction
) {
if (!('Segmenter' in Intl)) {
throw new SupportError(
formatErrorMessage(
Expand Down Expand Up @@ -479,13 +491,25 @@ export class CharacterCount extends ConfigurableComponent {
},

/**
* Count consecutive non-whitespace results
* Count words
*
* If the (deprecated) `maxwords` option is set, count words between
* consecutive whitespace rather than using the segmenter
*
* @param {string} text - Textarea value
* @returns {number} Count
*/
words(text) {
return text.match(/\S+/g)?.length ?? 0
if (this.config.maxwords !== undefined) {
return text.split(this.separator).filter(Boolean).length
}

const segments = this.segmenter
? Array.from(this.segmenter.segment(text))
: []

// Filter out punctuation and whitespace, leaving only words
return segments.filter((segment) => segment.isWordLike).length
}
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -605,21 +605,23 @@ describe('Character count', () => {
it('configures `maxwords` (deprecated)', async () => {
await initExample('to configure in JavaScript', {
config: {
maxwords: 10
maxwords: 5
}
})

await $textarea.type('Hello '.repeat(11))
await $textarea.type('My mother-in-law—Wait, what?')

// Note that only consecutive whitespace separates words
expect(await getText($visibleCountMessage)).toBe(
'You have 1 word too many'
'You have 2 words remaining'
)

await $textarea.press('Space')
await $textarea.type("what-d'you-call-it")

// Note that words are not split on hyphens or apostrophes by default
expect(await getText($visibleCountMessage)).toBe(
'You have 2 words too many'
'You have 1 word remaining'
)
})

Expand Down Expand Up @@ -672,23 +674,26 @@ describe('Character count', () => {
it('configures `countType: "words"`', async () => {
await initExample('to configure in JavaScript', {
config: {
maxlength: 10,
maxlength: 5,
countType: 'words'
}
})

await $textarea.type('Hello '.repeat(11))
await $textarea.type('My mother-in-law—Wait, what?')

// Note that words are counted regardless of punctuation when
// `countType: "words"` is configured
expect(await getText($visibleCountMessage)).toBe(
'You have 1 word too many'
)

await $textarea.press('Space')
await $textarea.type("what-d'you-call-it")

// Note that words are not split on hyphens or apostrophes
// Note that words are correctly split on hyphens and apostrophes
// when `countType: "words"` is configured
expect(await getText($visibleCountMessage)).toBe(
'You have 2 words too many'
'You have 5 words too many'
)
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ const fixtures = {
id: 'with-words-count-type-error-message',
name: 'example',
countType: 'words',
maxlength: 50,
maxlength: 51,
value:
'👩🏻‍🚀 A content designer works on the end-to-end journey of a service to help users complete their goal and government deliver a policy intent. Their work may involve the creation of, or change to, a transaction, product or single piece of content that stretches across digital and offline channels. They make sure appropriate content is shown to a user in the right place and in the best format.'
}
Expand All @@ -310,7 +310,7 @@ const fixtures = {
countType: 'words',
value:
'Type another word into this field after this message to see the threshold feature',
maxlength: 50,
maxlength: 51,
threshold: 30
}
},
Expand All @@ -324,7 +324,7 @@ const fixtures = {
id: 'with-words-count-type-value',
name: 'example',
countType: 'words',
maxlength: 50,
maxlength: 51,
value:
'👩🏻‍🚀 A content designer works on the end-to-end journey of a service to help users complete their goal and government deliver a policy intent. Their work may involve the creation of, or change to, a transaction, product or single piece of content that stretches across digital and offline channels.'
}
Expand Down
Loading