@financial-times/o-autocomplete 1.9.2 → 2.0.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.
@@ -0,0 +1,127 @@
1
+ import Autocomplete from '../../../main.js';
2
+ import {data} from './data.js';
3
+ import oForms from '@financial-times/o-forms';
4
+ oForms.init();
5
+ /**
6
+ * @typedef {object} CustomOption
7
+ * @property {string} Continent_Code - 2 letter continent code
8
+ * @property {string} Continent_Name - name of continent
9
+ * @property {string} Country_Name - name of country
10
+ * @property {number} Country_Number - id of country
11
+ * @property {string} Three_Letter_Country_Code - three letter country code
12
+ * @property {string} Two_Letter_Country_Code - two letter country code
13
+ */
14
+
15
+ /**
16
+ * @param {CustomOption} option - The option to transform into a suggestion string
17
+ * @returns {string} The string to display in the suggestions dropdown for this option
18
+ */
19
+ function mapOptionToSuggestedValue(option) {
20
+ if (typeof option !== 'object') {
21
+ throw new Error(`Could not map option to suggested value, unexpected type: ${typeof option}.`);
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;
29
+ }
30
+
31
+ /**
32
+ * @typedef CharacterHighlight - The character and whether it should be highlighted
33
+ * @type {Array}
34
+ * @property {string} 0 - the character in the suggestion
35
+ * @property {boolean} 1 - should it be highlighted?
36
+ */
37
+
38
+ /**
39
+ * @param {string} suggestion - Text which is going to be suggested to the user
40
+ * @param {string} query - Text which was typed into the autocomplete text input field by the user
41
+ * @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.
42
+ */
43
+ function highlightSuggestion(suggestion, query) {
44
+ const result = suggestion.split('');
45
+
46
+ const matchIndex = suggestion.toLocaleLowerCase().indexOf(query.toLocaleLowerCase());
47
+ return result.map(function(character, index) {
48
+ let shouldHighlight = true;
49
+ const hasMatched = matchIndex > -1;
50
+ const characterIsWithinMatch = index >= matchIndex && index <= matchIndex + query.length - 1;
51
+ if (hasMatched && characterIsWithinMatch) {
52
+ shouldHighlight = false;
53
+ }
54
+ return [character, shouldHighlight];
55
+ });
56
+ }
57
+
58
+ /**
59
+ * @param {CustomOption} option - The option to transform into a suggestion string
60
+ * @param {string} [query] - Text which was typed into the autocomplete text input field by the user
61
+ * @returns {string} The string to display in the suggestions dropdown for this option
62
+ */
63
+ function suggestionTemplate(option, query) {
64
+ if(typeof option === 'string') return option;
65
+
66
+ /**
67
+ * @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.
68
+ */
69
+ const characters = highlightSuggestion(option.name, query || option.name);
70
+
71
+ let output = '';
72
+ for (const [character, shoudHighlight] of characters) {
73
+ if (shoudHighlight) {
74
+ output += `<span class="o-autocomplete__option--highlight">${character}</span>`;
75
+ } else {
76
+ output += `${character}`;
77
+ }
78
+ }
79
+ output += ` <span>(${option.Continent_Name})</span>`;
80
+
81
+ const span = document.createElement('span');
82
+ span.setAttribute('aria-label', option.Country_Name);
83
+ span.innerHTML = output;
84
+ return span.outerHTML;
85
+ }
86
+
87
+ /**
88
+ * @typedef {Function} PopulateOptions
89
+ * @property {Array<string>} options - The options which match the rext which was typed into the autocomplete by the user
90
+ */
91
+
92
+ /**
93
+ * @param {string} query - Text which was typed into the autocomplete by the user
94
+ * @param {PopulateOptions} populateOptions - Function to call when ready to update the suggestions dropdown
95
+ * @returns {void}
96
+ */
97
+ function customSuggestions(query, populateOptions) {
98
+ const suggestions = data;
99
+
100
+ if (!query) {
101
+ populateOptions([]);
102
+ return;
103
+ }
104
+ suggestions.sort(function(a,b) {
105
+ return a.Country_Name.localeCompare(b.Country_Name);
106
+ });
107
+
108
+ const filteredOptions = [];
109
+ for (const suggestion of suggestions) {
110
+ const lowercaseSuggestion = suggestion.Country_Name.toLocaleLowerCase();
111
+ if (lowercaseSuggestion.startsWith(query.toLocaleLowerCase())) {
112
+ filteredOptions.push(suggestion);
113
+ }
114
+ }
115
+ populateOptions(filteredOptions);
116
+ }
117
+
118
+ new Autocomplete(document.querySelector('[data-o-component="o-autocomplete"]'), {
119
+ source: customSuggestions,
120
+ suggestionTemplate,
121
+ mapOptionToSuggestedValue,
122
+ defaultValue: data.find((d) => d['Two_Letter_Country_Code'] === 'GB')?.Country_Name,
123
+ onConfirm: function (option) {
124
+ // eslint-disable-next-line no-console
125
+ console.log('You chose option', option);
126
+ }
127
+ });
@@ -0,0 +1,14 @@
1
+ <form data-o-component="o-forms">
2
+ <div class="o-forms-field">
3
+ <span class="o-forms-title">
4
+ <label for="my-autocomplete" class="o-forms-title__main">Select your country</label>
5
+ </span>
6
+ <span class="o-forms-input o-forms-input--text">
7
+ <span data-o-component="o-autocomplete" class="o-autocomplete">
8
+ <!-- If the JavaScript executes, then this input will be progressively enhanced to an autocomplete component -->
9
+ <input required type="text" name="text-example" id="my-autocomplete">
10
+ </span>
11
+ <span role="alert" class="o-forms-input__error">Please fill out this field</span>
12
+ </span>
13
+ </div>
14
+ </form>
@@ -29,9 +29,10 @@ function mapOptionToSuggestedValue(option) {
29
29
  }
30
30
  /**
31
31
  * @param {CustomOption} option - The option to transform into a suggestion string
32
+ * @param {string} [query] - Text which was typed into the autocomplete text input field by the user
32
33
  * @returns {string} The string to display in the suggestions dropdown for this option
33
34
  */
34
- function suggestionTemplate(option) {
35
+ function suggestionTemplate(option, query) { //eslint-disable-line no-unused-vars
35
36
  if(typeof option === 'string') return option;
36
37
  return `<div>
37
38
  <strong>${option.Country_Name}</strong>
package/main.scss CHANGED
@@ -1,13 +1,7 @@
1
1
  @import '@financial-times/math';
2
2
  @import '@financial-times/o-brand/main';
3
- @import '@financial-times/o-colors/main';
4
- @import '@financial-times/o-grid/main';
5
- @import '@financial-times/o-icons/main';
6
3
  @import '@financial-times/o-loading/main';
7
- @import '@financial-times/o-normalise/main';
8
- @import '@financial-times/o-spacing/main';
9
- @import '@financial-times/o-typography/main';
10
- @import '@financial-times/o-visual-effects/main';
4
+ @import '@financial-times/o-private-foundation/main';
11
5
 
12
6
  @import 'src/scss/variables';
13
7
  @import 'src/scss/brand';
@@ -19,7 +13,8 @@
19
13
  /// @include oAutocomplete($opts: (
20
14
  /// // your opts here
21
15
  /// ))
22
- @mixin oAutocomplete ($opts: ()) {
16
+ @mixin oAutocomplete($opts: ()) {
17
+ @include oPrivateFoundation();
23
18
  // content of primary mixin
24
19
  .o-autocomplete {
25
20
  position: relative;
@@ -27,17 +22,21 @@
27
22
  }
28
23
 
29
24
  .o-autocomplete__listbox-container {
30
- @include oTypographySans($scale: 0);
25
+ @include oPrivateTypographySans($scale: 0);
31
26
  grid-row: 1;
32
27
  grid-column: 1;
33
28
  }
34
29
 
35
30
  .o-autocomplete__visually-hidden {
36
- @include oNormaliseVisuallyHidden;
31
+ @include oPrivateNormaliseVisuallyHidden;
37
32
  }
38
33
 
39
34
  .o-autocomplete__clear {
40
- @include oIconsContent('cross', _oAutocompleteGet('search-close'), $size: $_o-autocomplete-icon-size);
35
+ @include oPrivateIconsContent(
36
+ 'cross',
37
+ _oAutocompleteGet('search-close'),
38
+ $size: $_o-autocomplete-icon-size
39
+ );
41
40
  border: 0;
42
41
  padding: 0;
43
42
  appearance: none;
@@ -47,7 +46,9 @@
47
46
  align-self: center;
48
47
  justify-self: end;
49
48
 
50
- $margin-size: calc(#{$_o-autocomplete-input-space} - #{$_o-autocomplete-icon-size-excess-whitespace});
49
+ $margin-size: calc(
50
+ #{$_o-autocomplete-input-space} - #{$_o-autocomplete-icon-size-excess-whitespace}
51
+ );
51
52
  margin-left: $margin-size;
52
53
  margin-right: $margin-size;
53
54
 
@@ -60,7 +61,9 @@
60
61
  box-sizing: border-box;
61
62
  // This is to make the input stop before the clear button.
62
63
  // Without this, the input's text would flow behind the clear button.
63
- padding-right: calc(#{$_o-autocomplete-icon-size} + #{$_o-autocomplete-input-space * 2});
64
+ padding-right: calc(
65
+ #{$_o-autocomplete-icon-size} + #{$_o-autocomplete-input-space * 2}
66
+ );
64
67
  }
65
68
 
66
69
  .o-autocomplete__input::-ms-clear {
@@ -68,18 +71,18 @@
68
71
  }
69
72
 
70
73
  .o-autocomplete__menu {
71
- @include oVisualEffectsShadowContent('high');
72
- top: oSpacingByName('s1');
73
- background-color: oColorsByName('white');
74
+ @include oPrivateVisualEffectsShadowContent('high');
75
+ top: oPrivateSpacingByName('s1');
76
+ background-color: oPrivateFoundationGet('o3-color-palette-white');
74
77
  border-top: 0;
75
- color: oColorsByName('black');
78
+ color: oPrivateFoundationGet('o3-color-palette-black');
76
79
  margin: 0;
77
80
  max-height: 200px; // Make this configurable as it decides how many rows to show
78
81
  overflow-x: hidden;
79
- padding-left: oSpacingByName('s2');
80
- padding-right: oSpacingByName('s2');
81
- padding-top: oSpacingByName('s4');
82
- padding-bottom: oSpacingByName('s4');
82
+ padding-left: oPrivateSpacingByName('s2');
83
+ padding-right: oPrivateSpacingByName('s2');
84
+ padding-top: oPrivateSpacingByName('s4');
85
+ padding-bottom: oPrivateSpacingByName('s4');
83
86
  width: 100%;
84
87
  }
85
88
 
@@ -96,7 +99,7 @@
96
99
  }
97
100
 
98
101
  .o-autocomplete__menu--overlay {
99
- @include oVisualEffectsShadowContent('high');
102
+ @include oPrivateVisualEffectsShadowContent('high');
100
103
  box-sizing: border-box;
101
104
  }
102
105
 
@@ -110,10 +113,10 @@
110
113
  cursor: pointer;
111
114
  display: block;
112
115
  position: relative;
113
- padding-left: oSpacingByName('s1');
114
- padding-right: oSpacingByName('s1');
115
- padding-top: oSpacingByName('s2');
116
- padding-bottom: oSpacingByName('s2');
116
+ padding-left: oPrivateSpacingByName('s1');
117
+ padding-right: oPrivateSpacingByName('s1');
118
+ padding-top: oPrivateSpacingByName('s2');
119
+ padding-bottom: oPrivateSpacingByName('s2');
117
120
  }
118
121
 
119
122
  .o-autocomplete__option > * {
@@ -130,15 +133,22 @@
130
133
 
131
134
  .o-autocomplete__option--focused,
132
135
  .o-autocomplete__option:hover {
133
- background-color: oColorsByName('teal');
134
- border-color: oColorsByName('teal');
135
- color: oColorsByName('white');
136
+ background-color: oPrivateFoundationGet('o3-color-palette-teal');
137
+ border-color: oPrivateFoundationGet('o3-color-palette-teal');
138
+ color: oPrivateFoundationGet('o3-color-palette-white');
136
139
  outline: none;
137
140
  }
138
141
 
139
142
  .o-autocomplete__option--no-results {
140
- background-color: oColorsMix(white, black, 98); // #fafafa
141
- color: oColorsGetTextColor(oColorsMix(white, black, 98), $minimum-contrast: 'aa-normal');
143
+ background-color: oPrivateColorsMix(
144
+ 'o3-color-palette-white',
145
+ 'o3-color-palette-black',
146
+ 98
147
+ ); // #fafafa
148
+ color: oPrivateColorsGetTextColor(
149
+ oPrivateColorsMix('o3-color-palette-white', 'o3-color-palette-black', 98),
150
+ $minimum-contrast: 'aa-normal'
151
+ );
142
152
  cursor: not-allowed;
143
153
  }
144
154
 
@@ -146,12 +156,13 @@
146
156
  font-weight: bolder;
147
157
  }
148
158
 
149
-
150
159
  .o-autocomplete__menu-loading {
151
- @include oLoadingContent($opts: (
152
- size: 'mini',
153
- theme: 'dark'
154
- ));
160
+ @include oLoadingContent(
161
+ $opts: (
162
+ size: 'mini',
163
+ theme: 'dark',
164
+ )
165
+ );
155
166
  }
156
167
 
157
168
  // `.o-autocomplete__menu-loading` is declared twice intentionally.
@@ -163,21 +174,20 @@
163
174
  grid-row: 2;
164
175
  }
165
176
  .o-autocomplete__menu-loading-container {
166
- @include oVisualEffectsShadowContent('high');
177
+ @include oPrivateVisualEffectsShadowContent('high');
167
178
  display: flex;
168
179
  justify-content: center;
169
180
  align-items: center;
170
- padding: oSpacingByName('s2');
181
+ padding: oPrivateSpacingByName('s2');
171
182
  box-sizing: border-box;
172
183
  min-height: 40px;
173
- background-color: oColorsByName('white');
184
+ background-color: oPrivateFoundationGet('o3-color-palette-white');
174
185
  }
175
186
 
176
-
177
187
  .o-autocomplete__menu-loading-container,
178
188
  .o-autocomplete__menu--overlay {
179
189
  position: absolute;
180
- top: calc(100% + #{oSpacingByName('s1')});
190
+ top: calc(100% + #{oPrivateSpacingByName('s1')});
181
191
  left: 0;
182
192
  right: 0;
183
193
  z-index: 100;
package/origami.json CHANGED
@@ -61,6 +61,14 @@
61
61
  "js": "demos/src/dynamic-custom-suggestion/dynamic-custom-suggestion.js",
62
62
  "hidden": true
63
63
  },
64
+ {
65
+ "title": "Autocomplete with custom highlighted suggestion items",
66
+ "name": "dynamic-custom-highlighted-suggestions",
67
+ "template": "demos/src/dynamic-custom-highlighted-suggestion/dynamic-custom-highlighted-suggestion.mustache",
68
+ "description": "The source function returns objects and the suggestionTemplate transforms the objects into custom highlighted HTML suggestions",
69
+ "js": "demos/src/dynamic-custom-highlighted-suggestion/dynamic-custom-highlighted-suggestion.js",
70
+ "hidden": true
71
+ },
64
72
  {
65
73
  "title": "Pa11y",
66
74
  "name": "pa11y",
package/package.json CHANGED
@@ -1,53 +1,47 @@
1
1
  {
2
- "name": "@financial-times/o-autocomplete",
3
- "version": "1.9.2",
4
- "description": "An origami component for autocomplete inputs",
5
- "keywords": [
6
- "autocomplete",
7
- "typeahead"
8
- ],
9
- "homepage": "https://registry.origami.ft.com/components/o-autocomplete",
10
- "bugs": {
11
- "url": "https://github.com/Financial-Times/origami/issues/new?labels=o-autocomplete,components",
12
- "email": "origami.support@ft.com",
13
- "slack": "#origami-support"
14
- },
15
- "repository": {
16
- "type": "git",
17
- "url": "git+https://github.com/Financial-Times/o-autocomplete.git"
18
- },
19
- "license": "MIT",
20
- "type": "module",
21
- "browser": "main.js",
22
- "scripts": {
23
- "build": "bash ../../scripts/component/build.bash",
24
- "test": "bash ../../scripts/component/test.bash",
25
- "debug:js": "bash ../../scripts/component/debug-js.bash",
26
- "lint": "bash ../../scripts/component/lint.bash",
27
- "watch": "bash ../../scripts/component/watch.bash"
28
- },
29
- "engines": {
30
- "npm": ">7"
31
- },
32
- "peerDependencies": {
33
- "@financial-times/math": "^1.0.0",
34
- "@financial-times/o-brand": "^4.1.0",
35
- "@financial-times/o-colors": "^6.5.0",
36
- "@financial-times/o-grid": "^6.0.0",
37
- "@financial-times/o-icons": "^7.0.1",
38
- "@financial-times/o-loading": "^5.0.0",
39
- "@financial-times/o-normalise": "^3.3.0",
40
- "@financial-times/o-spacing": "^3.0.0",
41
- "@financial-times/o-typography": "^7.4.1",
42
- "@financial-times/o-visual-effects": "^4.0.1"
43
- },
44
- "devDependencies": {
45
- "@financial-times/o-forms": "^9.9.0",
46
- "@financial-times/o-normalise": "^3.3.0",
47
- "@financial-times/o-utils": "^2.1.0"
48
- },
49
- "dependencies": {
50
- "@financial-times/accessible-autocomplete": "^3.0.0"
51
- },
52
- "private": false
2
+ "name": "@financial-times/o-autocomplete",
3
+ "version": "2.0.0",
4
+ "description": "An origami component for autocomplete inputs",
5
+ "keywords": [
6
+ "autocomplete",
7
+ "typeahead"
8
+ ],
9
+ "homepage": "https://registry.origami.ft.com/components/o-autocomplete",
10
+ "bugs": {
11
+ "url": "https://github.com/Financial-Times/origami/issues/new?labels=o-autocomplete,components",
12
+ "email": "origami.support@ft.com",
13
+ "slack": "#origami-support"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/Financial-Times/o-autocomplete.git"
18
+ },
19
+ "license": "MIT",
20
+ "type": "module",
21
+ "browser": "main.js",
22
+ "scripts": {
23
+ "build": "bash ../../scripts/component/build.bash",
24
+ "test": "bash ../../scripts/component/test.bash",
25
+ "debug:js": "bash ../../scripts/component/debug-js.bash",
26
+ "lint": "bash ../../scripts/component/lint.bash",
27
+ "watch": "bash ../../scripts/component/watch.bash"
28
+ },
29
+ "engines": {
30
+ "npm": ">7"
31
+ },
32
+ "peerDependencies": {
33
+ "@financial-times/math": "^1.0.0",
34
+ "@financial-times/o-brand": "^4.1.0",
35
+ "@financial-times/o-loading": "^6.0.0",
36
+ "@financial-times/o-private-foundation": "^1.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "@financial-times/o-forms": "^10.0.0",
40
+ "@financial-times/o-normalise": "^3.3.2",
41
+ "@financial-times/o-utils": "^2.1.0"
42
+ },
43
+ "dependencies": {
44
+ "@financial-times/accessible-autocomplete": "^3.1.0"
45
+ },
46
+ "private": false
53
47
  }
@@ -3,10 +3,6 @@
3
3
  // Below are the pull-requests to accessible-autocomplete which would fix the bugs:
4
4
  // https://github.com/alphagov/accessible-autocomplete/pull/491
5
5
  // If the above pull-requests are merged and published, then we can stop using our fork
6
- // though switching to alphagov/accessible-autocomplete will require that consumers
7
- // applying `aria-labelledby` to the `ul` element
8
- // via the `ariaLabelledBy` option (https://github.com/Financial-Times/accessible-autocomplete/pull/6)
9
- // instead do so via the `menuAttributes` option (https://github.com/alphagov/accessible-autocomplete/pull/591)
10
6
  import accessibleAutocomplete from '@financial-times/accessible-autocomplete';
11
7
 
12
8
  /**
@@ -19,7 +15,7 @@ import accessibleAutocomplete from '@financial-times/accessible-autocomplete';
19
15
 
20
16
  /**
21
17
  * @param {string} suggestion - Text which is going to be suggested to the user
22
- * @param {string} query - Text which was typed into the autocomplete by the user
18
+ * @param {string} query - Text which was typed into the autocomplete text input field by the user
23
19
  * @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.
24
20
  */
25
21
  function highlightSuggestion(suggestion, query) {
@@ -293,13 +289,14 @@ class Autocomplete {
293
289
  * Used when rendering suggestions, the return value of this will be used as the innerHTML for a single suggestion.
294
290
  *
295
291
  * @param {*} option The suggestion to apply the template with.
292
+ * @param {string} query Text which was typed into the autocomplete text input field by the user.
296
293
  * @returns {string|undefined} HTML string to represent a single suggestion.
297
294
  */
298
- suggestion: (option) => {
295
+ suggestion: (option, query) => {
299
296
  // If the suggestionTemplate override option is provided,
300
297
  // use that to render the suggestion.
301
298
  if(typeof this.options.suggestionTemplate === 'function') {
302
- return this.options.suggestionTemplate(option);
299
+ return this.options.suggestionTemplate(option, query);
303
300
  }
304
301
  if (typeof option === 'object') {
305
302
  // If the `mapOptionToSuggestedValue` function is defined
@@ -13,7 +13,7 @@
13
13
  @if oBrandIs('core') {
14
14
  @include oBrandDefine('o-autocomplete', 'core', (
15
15
  'variables': (
16
- search-close: oColorsByName('black-80'),
16
+ search-close: oPrivateFoundationGet('o3-color-palette-black-80'),
17
17
  ),
18
18
  'supports-variants': ()
19
19
  ));
@@ -22,7 +22,7 @@
22
22
  @if oBrandIs('internal') {
23
23
  @include oBrandDefine('o-autocomplete', 'internal', (
24
24
  'variables': (
25
- search-close: oColorsByName('black-80'),
25
+ search-close: oPrivateFoundationGet('o3-color-palette-black-80'),
26
26
  ),
27
27
  'supports-variants': ()
28
28
  ));
@@ -1,3 +1,3 @@
1
1
  $_o-autocomplete-icon-size: 25px;
2
2
  $_o-autocomplete-icon-size-excess-whitespace: div($_o-autocomplete-icon-size, 4);
3
- $_o-autocomplete-input-space: oSpacingByName("s2"); // match o-forms, without a direct dependency
3
+ $_o-autocomplete-input-space: oPrivateSpacingByName("s2"); // match o-forms, without a direct dependency