@financial-times/o-autocomplete 1.6.2 → 1.7.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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.7.0](https://www.github.com/Financial-Times/origami/compare/o-autocomplete-v1.6.2...o-autocomplete-v1.7.0) (2022-09-14)
4
+
5
+
6
+ ### Features
7
+
8
+ * autocomplete, add a `defaultValue` for a default input value ([#812](https://www.github.com/Financial-Times/origami/issues/812)) ([70a77ae](https://www.github.com/Financial-Times/origami/commit/70a77ae218c9c19967fe3bb32c18206d7cd9c2c3))
9
+
3
10
  ### [1.6.2](https://www.github.com/Financial-Times/origami/compare/o-autocomplete-v1.6.1...o-autocomplete-v1.6.2) (2022-04-21)
4
11
 
5
12
 
package/README.md CHANGED
@@ -43,7 +43,7 @@ To provide a static set of suggestions, we recommend using a `select` element. o
43
43
 
44
44
  ### For a dynamic set of suggestions
45
45
 
46
- To provide a dynamic set of suggestions, you will need to provide a javascript function or name of a javascript function on the window object which follows the [dynamic-suggestions-function](dynamic-suggestions-function) <abbr title="application programming interface">API</abbr>.
46
+ To provide a dynamic set of suggestions, provide a javascript function or name of a javascript function on the window object which follows the [dynamic-suggestions-function](#dynamic-suggestions-function) <abbr title="application programming interface">API</abbr>.
47
47
 
48
48
  The input element requires an `id` attribute, this is used within the component to implement the accessibility features.
49
49
  ```html
@@ -56,12 +56,12 @@ The input element requires an `id` attribute, this is used within the component
56
56
 
57
57
  To have styling for labels, you will need to use [o-forms](https://registry.origami.ft.com/components/o-forms) as part of the autocomplete implementation.
58
58
 
59
- Below is an example of how to combine o-forms and o-autocomplete components together:
59
+ Below is an example of how to combine o-forms and o-autocomplete components together. Note the `label` and `select` element are connected using `for` and `id` attributes.
60
60
  ```html
61
- <label class="o-forms-field" >
62
- <span class="o-forms-title">
61
+ <span class="o-forms-field" >
62
+ <label for="my-autocomplete" class="o-forms-title">
63
63
  <span class="o-forms-title__main">Select your country</span>
64
- </span>
64
+ </label>
65
65
  <span class="o-forms-input o-forms-input--select">
66
66
  <span data-o-component="o-autocomplete" class="o-autocomplete">
67
67
  <select id="my-autocomplete">
@@ -70,7 +70,7 @@ Below is an example of how to combine o-forms and o-autocomplete components toge
70
70
  </select>
71
71
  </span>
72
72
  </span>
73
- </label>
73
+ </span>
74
74
  ```
75
75
  ## Sass
76
76
 
@@ -101,7 +101,6 @@ import oAutocomplete from 'o-autocomplete';
101
101
  const oAutocompleteElement = document.getElementById('#my-o-autocomplete-element');
102
102
  new oAutocomplete(oAutocompleteElement);
103
103
  ```
104
-
105
104
  ### dynamic suggestions function
106
105
 
107
106
  #### Example
@@ -111,7 +110,7 @@ import oAutocomplete from 'o-autocomplete';
111
110
 
112
111
  /**
113
112
  * @callback PopulateOptions
114
- * @param {Array<*>} options - The options which match the rext which was typed into the autocomplete by the user
113
+ * @param {Array<*>} options - The options which match the text which was typed into the autocomplete by the user
115
114
  * @returns {void}
116
115
  */
117
116
  /**
@@ -237,6 +236,13 @@ new oAutocomplete(oAutocompleteElement, {
237
236
  | --- | --- | --- |
238
237
  | option | <code>\*</code> | The option the user selected |
239
238
 
239
+ #### `defaultValue` (default: `''`)
240
+
241
+ Type: `string`
242
+
243
+ If setting a default input value for a [dynamic set of suggestions](for-a-dynamic-set-of-suggestions) set the `defaultValue` option.
244
+
245
+ _When progressively enhancing a [static set of suggestions](#for-a-static-set-of-suggestions) set a default value using HTML, by providing an appropriate `option` element._
240
246
 
241
247
  ## Keyboard Support
242
248
 
@@ -1,14 +1,14 @@
1
1
  <form data-o-component="o-forms">
2
- <label class="o-forms-field">
2
+ <div class="o-forms-field">
3
3
  <span class="o-forms-title">
4
- <span class="o-forms-title__main">Select your country</span>
4
+ <label for="my-autocomplete" class="o-forms-title__main">Select your country</label>
5
5
  </span>
6
6
  <span class="o-forms-input o-forms-input--text">
7
- <span data-o-component="o-autocomplete" data-o-autocomplete-source="customSuggestions" class="o-autocomplete">
7
+ <span data-o-component="o-autocomplete" data-o-autocomplete-default-value="United Kingdom" data-o-autocomplete-source="customSuggestions" class="o-autocomplete">
8
8
  <!-- If the JavaScript executes, then this input will be progressively enhanced to an autocomplete component -->
9
9
  <input required type="text" name="text-example" id="my-autocomplete">
10
10
  </span>
11
- <span class="o-forms-input__error">Please fill out this field</span>
11
+ <span role="alert" class="o-forms-input__error">Please fill out this field</span>
12
12
  </span>
13
- </label>
13
+ </div>
14
14
  </form>
@@ -13,13 +13,19 @@ oForms.init();
13
13
  */
14
14
 
15
15
  /**
16
- * @param {CustomOption|undefined} option - The option to transform into a suggestion string
16
+ * @param {CustomOption} option - The option to transform into a suggestion string
17
17
  * @returns {string} The string to display in the suggestions dropdown for this option
18
18
  */
19
19
  function mapOptionToSuggestedValue(option) {
20
- if (option) {
21
- return option.Country_Name;
20
+ if (typeof option !== 'object') {
21
+ throw new Error(`Could not map option to suggested value, unexpected type: ${typeof option}.`);
22
22
  }
23
+
24
+ if (typeof option.Country_Name !== 'string') {
25
+ throw new Error(`Could not map option to suggested value, option.Country_Name is not a string`);
26
+ }
27
+
28
+ return option.Country_Name;
23
29
  }
24
30
 
25
31
  /**
@@ -56,6 +62,7 @@ function customSuggestions(query, populateOptions) {
56
62
  new Autocomplete(document.querySelector('[data-o-component="o-autocomplete"]'), {
57
63
  source: customSuggestions,
58
64
  mapOptionToSuggestedValue,
65
+ defaultValue: data.find((d) => d['Two_Letter_Country_Code'] === 'GB')?.Country_Name,
59
66
  onConfirm: function (option) {
60
67
  // eslint-disable-next-line no-console
61
68
  console.log('You chose option', option);
@@ -1,14 +1,14 @@
1
1
  <form data-o-component="o-forms">
2
- <label class="o-forms-field">
2
+ <div class="o-forms-field">
3
3
  <span class="o-forms-title">
4
- <span class="o-forms-title__main">Select your country</span>
4
+ <label for="my-autocomplete" class="o-forms-title__main">Select your country</label>
5
5
  </span>
6
6
  <span class="o-forms-input o-forms-input--text">
7
7
  <span data-o-component="o-autocomplete" class="o-autocomplete">
8
8
  <!-- If the JavaScript executes, then this input will be progressively enhanced to an autocomplete component -->
9
9
  <input required type="text" name="text-example" id="my-autocomplete">
10
10
  </span>
11
- <span class="o-forms-input__error">Please fill out this field</span>
11
+ <span role="alert" class="o-forms-input__error">Please fill out this field</span>
12
12
  </span>
13
- </label>
13
+ </div>
14
14
  </form>
@@ -1,14 +1,14 @@
1
1
  <form data-o-component="o-forms">
2
- <label class="o-forms-field">
2
+ <div class="o-forms-field">
3
3
  <span class="o-forms-title">
4
- <span class="o-forms-title__main">Select your country</span>
4
+ <label for="my-autocomplete" class="o-forms-title__main">Select your country</label>
5
5
  </span>
6
6
  <span class="o-forms-input o-forms-input--text">
7
7
  <span data-o-component="o-autocomplete" data-o-autocomplete-source="customSuggestions" class="o-autocomplete">
8
8
  <!-- If the JavaScript executes, then this input will be progressively enhanced to an autocomplete component -->
9
9
  <input required type="text" name="text-example" id="my-autocomplete">
10
10
  </span>
11
- <span class="o-forms-input__error">Please fill out this field</span>
11
+ <span role="alert" class="o-forms-input__error">Please fill out this field</span>
12
12
  </span>
13
- </label>
13
+ </div>
14
14
  </form>
@@ -1,6 +1,6 @@
1
- <label class="o-forms-field">
1
+ <div class="o-forms-field">
2
2
  <span class="o-forms-title">
3
- <span class="o-forms-title__main">Select your country</span>
3
+ <label for="my-autocomplete" class="o-forms-title__main">Select your country</label>
4
4
  </span>
5
5
  {{! o-forms styles for select input needed for the core experience }}
6
6
  {{! o-forms styles for text input needed for the enhances JS experience }}
@@ -267,4 +267,4 @@
267
267
  </select>
268
268
  </span>
269
269
  </span>
270
- </label>
270
+ </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@financial-times/o-autocomplete",
3
- "version": "1.6.2",
3
+ "version": "1.7.0",
4
4
  "description": "An origami component for autocomplete inputs",
5
5
  "keywords": [
6
6
  "autocomplete",
@@ -22,7 +22,8 @@
22
22
  "scripts": {
23
23
  "build": "bash ../../scripts/component/build.bash",
24
24
  "test": "bash ../../scripts/component/test.bash",
25
- "lint": "bash ../../scripts/component/lint.bash"
25
+ "lint": "bash ../../scripts/component/lint.bash",
26
+ "watch": "bash ../../scripts/component/watch.bash"
26
27
  },
27
28
  "engines": {
28
29
  "npm": "^7 || ^8"
@@ -173,6 +173,7 @@ function initClearButton(instance) {
173
173
 
174
174
  /**
175
175
  * @typedef {object} AutocompleteOptions
176
+ * @property {string} [defaultValue] - Specify a string to prefill the autocomplete with
176
177
  * @property {Source} [source] - The function which retrieves the suggestions to display
177
178
  * @property {MapOptionToSuggestedValue} [mapOptionToSuggestedValue] - Function which transforms a suggestion before rendering
178
179
  * @property {onConfirm} [onConfirm] - Function which is called when the user selects an option
@@ -192,6 +193,7 @@ class Autocomplete {
192
193
  this.options = {};
193
194
  if (opts.source) {
194
195
  this.options.source = opts.source;
196
+ this.options.defaultValue = opts.defaultValue;
195
197
  }
196
198
  if (opts.mapOptionToSuggestedValue) {
197
199
  this.options.mapOptionToSuggestedValue = opts.mapOptionToSuggestedValue;
@@ -250,6 +252,7 @@ class Autocomplete {
250
252
  if (!id) {
251
253
  throw new Error("Missing `id` attribute on the o-autocomplete input. An `id` needs to be set as it is used within the o-autocomplete to implement the accessibility features.");
252
254
  }
255
+
253
256
  this.autocompleteEl.innerHTML = '';
254
257
  this.autocompleteEl.appendChild(this.container);
255
258
  accessibleAutocomplete({
@@ -266,16 +269,17 @@ class Autocomplete {
266
269
  source: this.options.source,
267
270
  cssNamespace: 'o-autocomplete',
268
271
  displayMenu: 'overlay',
272
+ defaultValue: this.options.defaultValue || '',
269
273
  showNoOptionsFound: false,
270
274
  templates: {
271
275
  /**
272
276
  * Used when rendering suggestions, the return value of this will be used as the innerHTML for a single suggestion.
273
277
  *
274
278
  * @param {*} option The suggestion to apply the template with.
275
- * @returns {string} HTML string to represent a single suggestion.
279
+ * @returns {string|undefined} HTML string to represent a single suggestion.
276
280
  */
277
281
  suggestion: (option) => {
278
- if (typeof option !== 'undefined') {
282
+ if (typeof option === 'object') {
279
283
  // If the `mapOptionToSuggestedValue` function is defined
280
284
  // Apply the function to the option. This is a way for the
281
285
  // consuming application to decide what text should be
@@ -285,21 +289,23 @@ class Autocomplete {
285
289
  // which should be used as the suggestion string.
286
290
  if (typeof this.mapOptionToSuggestedValue === 'function') {
287
291
  option = this.mapOptionToSuggestedValue(option);
288
- } else if (typeof option !== 'string') {
289
- 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.`);
290
292
  }
291
293
  }
292
294
 
295
+ if (typeof option !== 'string' && typeof option !== 'undefined') {
296
+ 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.`);
297
+ }
298
+
293
299
  return this.suggestionTemplate(option);
294
300
  },
295
301
  /**
296
302
  * Used when a suggestion is selected, the return value of this will be used as the value for the input element.
297
303
  *
298
304
  * @param {*} option The suggestion which was selected.
299
- * @returns {string} String to represent the suggestion.
305
+ * @returns {string|undefined} String to represent the suggestion.
300
306
  */
301
307
  inputValue: (option) => {
302
- if (typeof option !== 'undefined') {
308
+ if (typeof option === 'object') {
303
309
  // If the `mapOptionToSuggestedValue` function is defined
304
310
  // Apply the function to the option. This is a way for the
305
311
  // consuming application to decide what text should be
@@ -309,11 +315,13 @@ class Autocomplete {
309
315
  // which should be used as the suggestion string.
310
316
  if (typeof this.mapOptionToSuggestedValue === 'function') {
311
317
  option = this.mapOptionToSuggestedValue(option);
312
- } else if (typeof option !== 'string') {
313
- 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.`);
314
318
  }
315
319
  }
316
320
 
321
+ if (typeof option !== 'string' && typeof option !== 'undefined') {
322
+ 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.`);
323
+ }
324
+
317
325
  return option;
318
326
  }
319
327
  }
@@ -338,6 +346,8 @@ class Autocomplete {
338
346
  }
339
347
  },
340
348
  autoselect: false,
349
+ // To fallback with JS an enhanced element's default value should
350
+ // be set using static html.
341
351
  defaultValue: '',
342
352
  placeholder: '',
343
353
  cssNamespace: 'o-autocomplete',
@@ -362,10 +372,11 @@ class Autocomplete {
362
372
  */
363
373
  suggestionTemplate (suggestedValue) {
364
374
  // o-autocomplete has a UI design to highlight characters in the suggestions.
375
+ const input = this.autocompleteEl.querySelector('input');
365
376
  /**
366
377
  * @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.
367
378
  */
368
- const characters = highlightSuggestion(suggestedValue, this.autocompleteEl.querySelector('input').value);
379
+ const characters = highlightSuggestion(suggestedValue, input ? input.value : suggestedValue);
369
380
 
370
381
  let output = '';
371
382
  for (const [character, shoudHighlight] of characters) {
@@ -395,7 +406,8 @@ class Autocomplete {
395
406
 
396
407
  if (autocompleteEl.dataset.oAutocompleteSource) {
397
408
  return {
398
- source: autocompleteEl.dataset.oAutocompleteSource
409
+ source: autocompleteEl.dataset.oAutocompleteSource,
410
+ defaultValue: autocompleteEl.dataset.oAutocompleteDefaultValue
399
411
  };
400
412
  } else {
401
413
  return {};