@financial-times/o-autocomplete 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.2.0](https://github.com/Financial-Times/origami/compare/o-autocomplete-v2.1.0...o-autocomplete-v2.2.0) (2026-01-12)
4
+
5
+
6
+ ### Features
7
+
8
+ * Add o-autocomplete showNoOptionsFound option ([a869a04](https://github.com/Financial-Times/origami/commit/a869a04ffc045d526bb1facf7c89215c4933754f))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Add o-autocomplete clear button post-click logic ([67f4fed](https://github.com/Financial-Times/origami/commit/67f4fed14380e8e7b12d3d36156604e0a51e6446))
14
+
15
+ ## [2.1.0](https://github.com/Financial-Times/origami/compare/o-autocomplete-v2.0.0...o-autocomplete-v2.1.0) (2025-11-13)
16
+
17
+
18
+ ### Features
19
+
20
+ * Add o-autocomplete configurable highlighting ([b0b7d01](https://github.com/Financial-Times/origami/commit/b0b7d015ec9c2fe07c18ab57bddafb49468bc3c1))
21
+
3
22
  ## [2.0.0](https://github.com/Financial-Times/origami/compare/o-autocomplete-v1.10.0...o-autocomplete-v2.0.0) (2025-02-20)
4
23
 
5
24
  ### ⚠ BREAKING CHANGES
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@financial-times/o-autocomplete",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "An origami component for autocomplete inputs",
5
5
  "keywords": [
6
6
  "autocomplete",
@@ -16,18 +16,19 @@ import accessibleAutocomplete from '@financial-times/accessible-autocomplete';
16
16
  /**
17
17
  * @param {string} suggestion - Text which is going to be suggested to the user
18
18
  * @param {string} query - Text which was typed into the autocomplete text input field by the user
19
+ * @param {boolean} isHighlightCorrespondingToMatch - Boolean to determine whether matching or non-matching characters should be highlighted.
19
20
  * @returns {CharacterHighlight[]} An array of arrays which contain two items, the first is the character in the suggestion, the second is a boolean which indicates whether the character should be highlighted.
20
21
  */
21
- function highlightSuggestion(suggestion, query) {
22
+ function highlightSuggestion(suggestion, query, isHighlightCorrespondingToMatch) {
22
23
  const result = suggestion.split('');
23
24
 
24
25
  const matchIndex = suggestion.toLocaleLowerCase().indexOf(query.toLocaleLowerCase());
25
26
  return result.map(function(character, index) {
26
- let shouldHighlight = true;
27
+ let shouldHighlight = !isHighlightCorrespondingToMatch;
27
28
  const hasMatched = matchIndex > -1;
28
29
  const characterIsWithinMatch = index >= matchIndex && index <= matchIndex + query.length - 1;
29
30
  if (hasMatched && characterIsWithinMatch) {
30
- shouldHighlight = false;
31
+ shouldHighlight = isHighlightCorrespondingToMatch;
31
32
  }
32
33
  return [character, shouldHighlight];
33
34
  });
@@ -182,6 +183,7 @@ function initClearButton(instance) {
182
183
  * @property {MapOptionToSuggestedValue} [mapOptionToSuggestedValue] - Function which transforms a suggestion before rendering.
183
184
  * @property {onConfirm} [onConfirm] - Function which is called when the user selects an option
184
185
  * @property {SuggestionTemplate} [suggestionTemplate] - Function to override how a suggestion item is rendered.
186
+ * @property {boolean} [isHighlightCorrespondingToMatch] - Boolean to determine whether matching or non-matching characters should be highlighted.
185
187
  * @property {boolean} [autoselect] - Boolean to specify whether first option in suggestions list is highlighted.
186
188
  */
187
189
 
@@ -207,9 +209,11 @@ class Autocomplete {
207
209
  if (opts.onConfirm) {
208
210
  this.options.onConfirm = opts.onConfirm;
209
211
  }
212
+ this.options.showNoOptionsFound = opts.showNoOptionsFound || false;
210
213
  if (opts.suggestionTemplate) {
211
214
  this.options.suggestionTemplate = opts.suggestionTemplate;
212
215
  }
216
+ this.options.isHighlightCorrespondingToMatch = Boolean(opts.isHighlightCorrespondingToMatch);
213
217
  if (opts.autoselect) {
214
218
  this.options.autoselect = opts.autoselect;
215
219
  }
@@ -244,7 +248,33 @@ class Autocomplete {
244
248
  * @returns {void}
245
249
  */
246
250
  this.options.source = (query, populateOptions) => {
247
- showLoadingPane(this);
251
+ // One way this function can be invoked is following a clearButton click event.
252
+
253
+ // The consumer-provided `customSource()` function can be wrapped in `debounce()`, creating the potential
254
+ // for a user-defined delay between the invocation of the `customSource()` and `callback()` functions.
255
+ // This delay could be longer than the 100ms frequency at which accessible-autocomplete polls the value of the input.
256
+
257
+ // The following code blocks accommodate the coincident of these circumstances.
258
+
259
+ // A clearButton click event will hide the loading pane and set the input value as an empty string.
260
+ if (query) {
261
+ // Therefore only show the loading pane if a `query` (i.e. input) value is present
262
+ // so as to avoid temporarily re-showing the loading pane prior to this function's callback hiding it once again.
263
+ showLoadingPane(this);
264
+ }
265
+
266
+ // A clearButton click event will blur the input field, resulting in the listbox of options becoming hidden
267
+ // (but without de-populating the options).
268
+ if (!query) {
269
+ // If the accessible-autocomplete poll occurs after a clearButton click event
270
+ // but before this function's callback is invoked then the listbox and its options will be re-displayed.
271
+ // When the callback eventually runs, it will de-populate the options, consequently re-hiding the listbox.
272
+ // To prevent this, in the event of no `query` value being present
273
+ // (where, logically, there will be no corresponding options),
274
+ // immediately de-populate the options rather than waiting for the callback to do so.
275
+ populateOptions([]);
276
+ }
277
+
248
278
  /**
249
279
  * @param {Array<string>} options - The options which match the rext which was typed into the autocomplete by the user
250
280
  * @returns {void}
@@ -282,7 +312,7 @@ class Autocomplete {
282
312
  cssNamespace: 'o-autocomplete',
283
313
  displayMenu: 'overlay',
284
314
  defaultValue: this.options.defaultValue || '',
285
- showNoOptionsFound: false,
315
+ showNoOptionsFound: this.options.showNoOptionsFound,
286
316
  autoselect: this.options.autoselect || false,
287
317
  templates: {
288
318
  /**
@@ -293,10 +323,17 @@ class Autocomplete {
293
323
  * @returns {string|undefined} HTML string to represent a single suggestion.
294
324
  */
295
325
  suggestion: (option, query) => {
326
+ const isHighlightCorrespondingToMatch = this.options.isHighlightCorrespondingToMatch;
327
+
296
328
  // If the suggestionTemplate override option is provided,
297
329
  // use that to render the suggestion.
298
330
  if(typeof this.options.suggestionTemplate === 'function') {
299
- return this.options.suggestionTemplate(option, query);
331
+ return this.options.suggestionTemplate(
332
+ option,
333
+ query,
334
+ highlightSuggestion,
335
+ isHighlightCorrespondingToMatch
336
+ );
300
337
  }
301
338
  if (typeof option === 'object') {
302
339
  // If the `mapOptionToSuggestedValue` function is defined
@@ -315,7 +352,7 @@ class Autocomplete {
315
352
  throw new Error(`The option trying to be displayed as a suggestion is not a string, it is "${typeof option}". o-autocomplete can only display strings as suggestions. Define a \`mapOptionToSuggestedValue\` function to convert the option into a string to be used as the suggestion.`);
316
353
  }
317
354
 
318
- return this.suggestionTemplate(option);
355
+ return this.suggestionTemplate(option, isHighlightCorrespondingToMatch);
319
356
  },
320
357
  /**
321
358
  * Used when a suggestion is selected, the return value of this will be used as the value for the input element.
@@ -371,7 +408,7 @@ class Autocomplete {
371
408
  placeholder: '',
372
409
  cssNamespace: 'o-autocomplete',
373
410
  displayMenu: 'overlay',
374
- showNoOptionsFound: false,
411
+ showNoOptionsFound: this.options.showNoOptionsFound,
375
412
  templates: {
376
413
  suggestion: this.suggestionTemplate.bind(this)
377
414
  }
@@ -387,15 +424,20 @@ class Autocomplete {
387
424
  * Used when rendering suggestions, the return value of this will be used as the innerHTML for a single suggestion.
388
425
  *
389
426
  * @param {string} suggestedValue The suggestion to apply the template with.
427
+ * @param {boolean} isHighlightCorrespondingToMatch Boolean to determine whether matching or non-matching characters should be highlighted.
390
428
  * @returns {string} HTML string to be represent a single suggestion.
391
429
  */
392
- suggestionTemplate (suggestedValue) {
430
+ suggestionTemplate (suggestedValue, isHighlightCorrespondingToMatch) {
393
431
  // o-autocomplete has a UI design to highlight characters in the suggestions.
394
432
  const input = this.autocompleteEl.querySelector('input');
395
433
  /**
396
434
  * @type {CharacterHighlight[]} An array of arrays which contain two items, the first is the character in the suggestion, the second is a boolean which indicates whether the character should be highlighted.
397
435
  */
398
- const characters = highlightSuggestion(suggestedValue, input ? input.value : suggestedValue);
436
+ const characters = highlightSuggestion(
437
+ suggestedValue,
438
+ input ? input.value : suggestedValue,
439
+ isHighlightCorrespondingToMatch
440
+ );
399
441
 
400
442
  let output = '';
401
443
  for (const [character, shoudHighlight] of characters) {