@fluid-topics/ft-search-input 2.1.1 → 2.1.3

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.
@@ -4,6 +4,7 @@ export interface SearchInputMessages extends I18nMessages {
4
4
  searchButton(): string;
5
5
  clearButton(): string;
6
6
  suggestionsLabel(): string;
7
- ariaLiveSuggestions(resultCount: number): string;
7
+ ariaLiveSuggestions(recentQueries: number, suggestions: number): string;
8
+ removeRecentQuery(query: string): string;
8
9
  }
9
10
  export declare const searchInputContext: I18nMessageContext<SearchInputMessages>;
@@ -1,2 +1,2 @@
1
- import { I18nMessageContext } from "@fluid-topics/ft-i18n";
1
+ import { I18nMessageContext, } from "@fluid-topics/ft-i18n";
2
2
  export const searchInputContext = I18nMessageContext.build("designedSearchInput");
@@ -10,16 +10,21 @@ export declare class FtSearchInputSuggestionManager {
10
10
  constructor(ftSearchInput: FtSearchInput);
11
11
  listboxId: string;
12
12
  render(): import("lit-html").TemplateResult<1>;
13
+ private renderRecentQuery;
13
14
  private renderSuggestion;
14
- private onSuggestSelected;
15
- private getIcon;
16
- get selectedSuggestOption(): HTMLElement | null;
17
- private getLastSuggestionElement;
18
- private getFirstSuggestionElement;
19
- private nextSuggestionElement;
20
- private previousSuggestionElement;
21
- private selectSuggestion;
22
- selectNextSuggestion(): void;
23
- selectPreviousSuggestion(): void;
24
- clearSelection(): void;
15
+ private renderOption;
16
+ private onRemoveRecentQuery;
17
+ private onSelectOption;
18
+ get activeElementId(): string | undefined;
19
+ get activeElement(): HTMLElement | undefined;
20
+ recentQueriesCount(): number;
21
+ suggestionsCount(): number;
22
+ optionsCount(): number;
23
+ private clearSelection;
24
+ selectNext(): void;
25
+ selectPrevious(): void;
26
+ selectLeft(): void;
27
+ private isRemovable;
28
+ selectRight(): void;
29
+ closeBox(): void;
25
30
  }
@@ -1,12 +1,13 @@
1
- import { wordWrap } from "@fluid-topics/ft-wc-utils";
2
- import { html } from "lit";
1
+ import { wordWrap, } from "@fluid-topics/ft-wc-utils";
2
+ import { html, nothing, } from "lit";
3
3
  import { suggestionStyles } from "./ft-search-input-suggestion.styles";
4
4
  import { repeat } from "lit/directives/repeat.js";
5
- import { FtIcon, FtIcons, FtIconVariants, resolveFileFormatIcon } from "@fluid-topics/ft-icon";
5
+ import { FtIcon, FtIcons, FtIconVariants, resolveFileFormatIcon, } from "@fluid-topics/ft-icon";
6
6
  import { FtRipple } from "@fluid-topics/ft-ripple";
7
- import { FtTypography } from "@fluid-topics/ft-typography";
7
+ import { FtTypography, FtTypographyVariants, } from "@fluid-topics/ft-typography";
8
8
  import { SearchPlaceConverterProvider } from "@fluid-topics/ft-app-context";
9
9
  import { searchInputContext } from "./SearchInputMessages";
10
+ import { FtButton } from "@fluid-topics/ft-button";
10
11
  export class SuggestionSelectedEvent extends CustomEvent {
11
12
  constructor(value) {
12
13
  super("suggestion-selected", { detail: value });
@@ -18,59 +19,28 @@ export class FtSearchInputSuggestionManager {
18
19
  this.listboxId = "ft-search-input-suggestion";
19
20
  }
20
21
  render() {
22
+ var _a;
21
23
  const searchPlaceConverter = SearchPlaceConverterProvider.get();
24
+ const { recentQuerySuggestions } = this.ftSearchInput;
22
25
  return html `
23
26
  <div class="ft-search-input-suggestion ft-word-wrap"
24
27
  id="${this.listboxId}"
25
28
  role="listbox"
26
29
  aria-label="${searchInputContext.messages.suggestionsLabel()}"
27
30
  part="container">
28
- ${repeat(this.ftSearchInput.suggestResults || [], (suggest) => suggest.value, (suggest, index) => {
29
- return this.renderSuggestion(index, suggest, searchPlaceConverter);
31
+ ${repeat(recentQuerySuggestions, (query) => query, (query, index) => {
32
+ return this.renderRecentQuery(index, query, searchPlaceConverter);
33
+ })}
34
+ ${repeat((_a = this.ftSearchInput.suggestResults) !== null && _a !== void 0 ? _a : [], (suggest) => suggest.value, (suggest, index) => {
35
+ return this.renderSuggestion(recentQuerySuggestions.length + index, suggest, searchPlaceConverter);
30
36
  })}
31
37
  </div>
32
38
  `;
33
39
  }
34
- renderSuggestion(index, suggest, searchPlaceConverter) {
35
- const id = `suggestion-${index}`;
36
- const content = html `
37
- <ft-ripple ?activated=${this.ftSearchInput.selectedSuggestOptionId === id}></ft-ripple>
38
- ${this.getIcon(suggest)}
39
- <ft-typography variant="body1">${suggest.value}</ft-typography>
40
- `;
41
- if (this.ftSearchInput.launchSearchPath) {
42
- const suggestRequest = { ...this.ftSearchInput.request, query: suggest.value };
43
- const launchSuggestUrl = searchPlaceConverter.serializeToCurrentPageIfPossible(suggestRequest, this.ftSearchInput.launchSearchPath);
44
- return html `
45
- <a href="${launchSuggestUrl}"
46
- part="suggestion"
47
- class="ft-search-input-suggestion--suggestion"
48
- role="option"
49
- id="${id}"
50
- aria-selected="${this.ftSearchInput.selectedSuggestOptionId === id}"
51
- tabindex="-1"
52
- aria-label="${content}"
53
- @click=${() => this.onSuggestSelected(suggest.value)}>
54
- ${content}
55
- </a>
56
- `;
57
- }
58
- return html `
59
- <div part="suggestion"
60
- class="ft-search-input-suggestion--suggestion"
61
- role="option"
62
- id="${id}"
63
- aria-selected="${this.ftSearchInput.selectedSuggestOptionId === id}"
64
- tabindex="-1"
65
- @click=${() => this.onSuggestSelected(suggest.value)}>
66
- ${content}
67
- </div>
68
- `;
69
- }
70
- onSuggestSelected(suggest) {
71
- this.ftSearchInput.onSuggestionSelected(suggest);
40
+ renderRecentQuery(index, query, searchPlaceConverter) {
41
+ return this.renderOption(index, query, "history", FtIconVariants.material, true, searchPlaceConverter);
72
42
  }
73
- getIcon(suggest) {
43
+ renderSuggestion(index, suggest, searchPlaceConverter) {
74
44
  const iconVariant = suggest.type === "DOCUMENT" ? FtIconVariants.file_format : FtIconVariants.fluid_topics;
75
45
  let icon;
76
46
  switch (suggest.type) {
@@ -90,56 +60,139 @@ export class FtSearchInputSuggestionManager {
90
60
  icon = FtIcons.FILE_CODE;
91
61
  break;
92
62
  }
63
+ return this.renderOption(index, suggest.value, icon, iconVariant, false, searchPlaceConverter);
64
+ }
65
+ renderOption(index, query, icon, iconVariant, removable, searchPlaceConverter) {
66
+ const indexSelected = this.ftSearchInput.selectedSuggestOptionIndex === index;
67
+ const removeButtonSelected = this.ftSearchInput.selectedSuggestOptionRemove;
68
+ const href = this.ftSearchInput.launchSearchPath ? searchPlaceConverter.serializeToCurrentPageIfPossible({ ...this.ftSearchInput.request, query }, this.ftSearchInput.launchSearchPath) : nothing;
93
69
  return html `
94
- <ft-icon .variant=${iconVariant} .value=${icon} part="suggestion-icon"></ft-icon>
70
+ <div part="suggestion-container"
71
+ class="ft-search-input-suggestion--container">
72
+ <a href="${href}"
73
+ part="suggestion"
74
+ class="ft-search-input-suggestion--suggestion"
75
+ role="option"
76
+ id="suggestion-${index}"
77
+ aria-selected="${indexSelected}"
78
+ tabindex="-1"
79
+ @click=${() => this.onSelectOption(query)}
80
+ >
81
+ <ft-ripple ?activated=${indexSelected && !removeButtonSelected}></ft-ripple>
82
+ <ft-icon variant="${iconVariant}" value="${icon}" part="suggestion-icon"></ft-icon>
83
+ <ft-typography variant="${FtTypographyVariants.body1}">${query}</ft-typography>
84
+ </a>
85
+ ${removable ? html `
86
+ <ft-button icon="${FtIcons.CLOSE}"
87
+ round
88
+ part="remove-suggestion"
89
+ class="ft-search-input-suggestion--remove"
90
+ id="remove-suggestion-${index}"
91
+ label="${searchInputContext.messages.removeRecentQuery(query)}"
92
+ tooltipPosition="left"
93
+ tabindex="-1"
94
+ ?activated=${indexSelected && removeButtonSelected}
95
+ @click=${() => this.onRemoveRecentQuery(query)}>
96
+ </ft-button>
97
+ ` : nothing}
98
+ </div>
95
99
  `;
96
100
  }
97
- get selectedSuggestOption() {
98
- return this.ftSearchInput.shadowRoot.querySelector(`#${this.ftSearchInput.selectedSuggestOptionId}`);
101
+ onRemoveRecentQuery(query) {
102
+ var _a;
103
+ (_a = this.ftSearchInput.stateManager) === null || _a === void 0 ? void 0 : _a.removeRecentQuery(query);
99
104
  }
100
- getLastSuggestionElement() {
101
- const suggestions = this.ftSearchInput.shadowRoot.querySelectorAll("[role=option]");
102
- return suggestions.length > 0 ? suggestions[suggestions.length - 1] : null;
105
+ onSelectOption(query) {
106
+ this.ftSearchInput.onSuggestionSelected(query);
103
107
  }
104
- getFirstSuggestionElement() {
105
- return this.ftSearchInput.shadowRoot.querySelector("[role=option]");
108
+ get activeElementId() {
109
+ const { selectedSuggestOptionIndex, selectedSuggestOptionRemove } = this.ftSearchInput;
110
+ if (selectedSuggestOptionIndex === undefined) {
111
+ return undefined;
112
+ }
113
+ return (selectedSuggestOptionRemove ? "remove-suggestion-" : "suggestion-") + selectedSuggestOptionIndex;
106
114
  }
107
- nextSuggestionElement() {
108
- const divElement = this.selectedSuggestOption;
109
- if (!divElement) {
110
- return this.getFirstSuggestionElement();
115
+ get activeElement() {
116
+ var _a;
117
+ const id = this.activeElementId;
118
+ if (id === undefined) {
119
+ return undefined;
111
120
  }
112
- return divElement === null || divElement === void 0 ? void 0 : divElement.nextElementSibling;
121
+ return (_a = this.ftSearchInput.shadowRoot.getElementById(id)) !== null && _a !== void 0 ? _a : undefined;
122
+ }
123
+ recentQueriesCount() {
124
+ return this.ftSearchInput.recentQuerySuggestions.length;
125
+ }
126
+ suggestionsCount() {
127
+ var _a, _b;
128
+ return (_b = (_a = this.ftSearchInput.suggestResults) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
129
+ }
130
+ optionsCount() {
131
+ return this.recentQueriesCount() + this.suggestionsCount();
113
132
  }
114
- previousSuggestionElement() {
115
- const divElement = this.selectedSuggestOption;
116
- if (!divElement) {
117
- return this.getLastSuggestionElement();
133
+ clearSelection() {
134
+ this.ftSearchInput.selectedSuggestOptionIndex = undefined;
135
+ this.ftSearchInput.selectedSuggestOptionRemove = false;
136
+ }
137
+ selectNext() {
138
+ if (this.ftSearchInput.selectedSuggestOptionIndex === undefined) {
139
+ this.ftSearchInput.selectedSuggestOptionIndex = 0;
140
+ }
141
+ else {
142
+ this.ftSearchInput.selectedSuggestOptionIndex++;
143
+ if (this.ftSearchInput.selectedSuggestOptionIndex >= this.optionsCount()) {
144
+ this.clearSelection();
145
+ }
118
146
  }
119
- return divElement === null || divElement === void 0 ? void 0 : divElement.previousElementSibling;
120
147
  }
121
- selectSuggestion(element) {
122
- if (element) {
123
- this.ftSearchInput.selectedSuggestOptionId = element.id;
148
+ selectPrevious() {
149
+ if (this.ftSearchInput.selectedSuggestOptionIndex === undefined) {
150
+ this.ftSearchInput.selectedSuggestOptionIndex = this.optionsCount() - 1;
124
151
  }
125
152
  else {
126
- this.ftSearchInput.selectedSuggestOptionId = undefined;
153
+ this.ftSearchInput.selectedSuggestOptionIndex--;
154
+ if (this.ftSearchInput.selectedSuggestOptionIndex < 0) {
155
+ this.clearSelection();
156
+ }
127
157
  }
128
158
  }
129
- selectNextSuggestion() {
130
- this.selectSuggestion(this.nextSuggestionElement());
159
+ selectLeft() {
160
+ const index = this.ftSearchInput.selectedSuggestOptionIndex;
161
+ if (index === undefined) {
162
+ return;
163
+ }
164
+ if (this.ftSearchInput.selectedSuggestOptionRemove) {
165
+ this.ftSearchInput.selectedSuggestOptionRemove = false;
166
+ }
167
+ else {
168
+ this.clearSelection();
169
+ }
131
170
  }
132
- selectPreviousSuggestion() {
133
- this.selectSuggestion(this.previousSuggestionElement());
171
+ isRemovable(index) {
172
+ return index < this.recentQueriesCount();
134
173
  }
135
- clearSelection() {
136
- this.selectSuggestion(null);
174
+ selectRight() {
175
+ const index = this.ftSearchInput.selectedSuggestOptionIndex;
176
+ if (index === undefined) {
177
+ return;
178
+ }
179
+ if (!this.ftSearchInput.selectedSuggestOptionRemove && this.isRemovable(index)) {
180
+ this.ftSearchInput.selectedSuggestOptionRemove = true;
181
+ }
182
+ else {
183
+ this.clearSelection();
184
+ }
185
+ }
186
+ closeBox() {
187
+ this.ftSearchInput.displaySuggestListbox = false;
188
+ this.clearSelection();
137
189
  }
138
190
  }
139
191
  FtSearchInputSuggestionManager.elementDefinitions = {
140
192
  "ft-ripple": FtRipple,
141
193
  "ft-typography": FtTypography,
142
194
  "ft-icon": FtIcon,
195
+ "ft-button": FtButton,
143
196
  };
144
197
  FtSearchInputSuggestionManager.styles = [
145
198
  wordWrap,
@@ -21,6 +21,17 @@ export const suggestionStyles = css `
21
21
  box-shadow: ${FtSearchInputCssVariables.elevation02};
22
22
  }
23
23
 
24
+ .ft-search-input-suggestion--container {
25
+ display: flex;
26
+ align-items: center;
27
+ }
28
+
29
+ .ft-search-input-suggestion--container:has([aria-selected="true"]),
30
+ .ft-search-input-suggestion--container:hover {
31
+ outline: none;
32
+ box-shadow: inset 3px 0 0 ${FtSearchBarCssVariables.activeItemBorderColor};
33
+ }
34
+
24
35
  .ft-search-input-suggestion--suggestion {
25
36
  text-decoration: none;
26
37
  position: relative;
@@ -31,19 +42,13 @@ export const suggestionStyles = css `
31
42
  cursor: pointer;
32
43
  color: ${FtSearchInputCssVariables.colorOnSurface};
33
44
  min-height: 44px;
45
+ flex-grow: 1;
34
46
  }
35
47
 
36
48
  .ft-search-input-suggestion--suggestion > *:not(ft-ripple) {
37
49
  position: relative;
38
50
  }
39
51
 
40
- .ft-search-input-suggestion--suggestion[aria-selected="true"],
41
- .ft-search-input-suggestion--suggestion:hover {
42
- outline: none;
43
- box-shadow: inset 3px 0 0 ${FtSearchBarCssVariables.activeItemBorderColor};
44
-
45
- }
46
-
47
52
  .ft-search-input-suggestion--suggestion ft-typography {
48
53
  display: block;
49
54
  flex-grow: 1;
@@ -12,9 +12,11 @@ export declare class FtSearchInput extends FtSearchInput_base implements FtSearc
12
12
  request?: FtSearchRequest;
13
13
  liveQuery: string;
14
14
  suggestResults?: Array<FtSuggestResult>;
15
+ recentQuerySuggestions: string[];
15
16
  private forceCloseSuggestion;
16
17
  displaySuggestListbox: boolean;
17
- selectedSuggestOptionId?: string;
18
+ selectedSuggestOptionIndex?: number;
19
+ selectedSuggestOptionRemove: boolean;
18
20
  launchSearchPath?: string;
19
21
  placeholder?: I18nAttributeValue;
20
22
  useCustomSearchPath: boolean;
@@ -24,20 +26,18 @@ export declare class FtSearchInput extends FtSearchInput_base implements FtSearc
24
26
  private launchSearchButton;
25
27
  suggestions: FtSearchInputSuggestionManager;
26
28
  protected render(): import("lit-html").TemplateResult<1>;
27
- private renderLiveText;
28
29
  private onSearchBarKeyDown;
29
30
  private onSearchBarKeyUp;
30
31
  private onInput;
31
32
  private onClearInput;
32
33
  launchSearch(query: string): void;
33
34
  onSuggestionSelected(value: string): void;
34
- get shouldDisplaySuggestions(): boolean;
35
35
  private onClick;
36
36
  private showSuggestionsOnFocus;
37
37
  private onFocus;
38
38
  focusInput(showSuggestions?: boolean): Promise<void>;
39
39
  connectedCallback(): Promise<void>;
40
40
  disconnectedCallback(): void;
41
- private closeSuggestListbox;
41
+ private onPointerDown;
42
42
  }
43
43
  export {};
@@ -17,37 +17,45 @@ import { FtButton } from "@fluid-topics/ft-button";
17
17
  import { FtTypographyBody2 } from "@fluid-topics/ft-typography";
18
18
  import { SearchPlaceConverterProvider } from "@fluid-topics/ft-app-context";
19
19
  import { i18nAttribute, } from "@fluid-topics/ft-i18n/build/decorators/i18nAttribute";
20
+ const maxRecentQuerySuggestions = 5;
21
+ function selectRecentQuerySuggestions(state) {
22
+ const normalizedQuery = state.liveQuery.trim().toLowerCase();
23
+ return state.recentQueries.filter((q) => q.toLowerCase().includes(normalizedQuery)).slice(0, maxRecentQuerySuggestions);
24
+ }
20
25
  export class FtSearchInput extends withI18n(FtSearchComponent) {
21
26
  constructor() {
22
27
  super();
23
28
  this.liveQuery = "";
29
+ this.recentQuerySuggestions = [];
24
30
  this.forceCloseSuggestion = false;
25
31
  this.displaySuggestListbox = false;
32
+ this.selectedSuggestOptionRemove = false;
26
33
  this.useCustomSearchPath = false;
27
34
  this.triggerSearchOnClear = false;
28
35
  this.focusOnDisplay = false;
29
36
  this.suggestions = new FtSearchInputSuggestionManager(this);
30
37
  this.showSuggestionsOnFocus = true;
31
- this.closeSuggestListbox = (e) => {
38
+ this.onPointerDown = (e) => {
32
39
  const path = e.composedPath();
33
40
  const inside = path.some((n) => n === this || n === this.shadowRoot);
34
41
  if (!inside) {
35
- this.displaySuggestListbox = false;
36
- this.selectedSuggestOptionId = undefined;
42
+ this.suggestions.closeBox();
37
43
  }
38
44
  };
39
45
  this.addI18nContext(searchInputContext);
40
46
  }
41
47
  render() {
42
- var _a, _b, _c;
48
+ var _a;
49
+ const hasSuggestions = this.suggestions.optionsCount() > 0;
43
50
  const classes = {
44
51
  "ft-search-input": true,
45
- "ft-search-input--with-suggestions": (_b = (_a = this.suggestResults) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0 > 0,
52
+ "ft-search-input--with-suggestions": hasSuggestions,
46
53
  "ft-search-input--with-suggestions-closed": this.forceCloseSuggestion,
47
54
  };
48
55
  const liveRequest = { ...this.request, query: this.liveQuery };
49
56
  const searchPage = this.useCustomSearchPath ? this.launchSearchPath : undefined;
50
57
  const launchSearchUrl = SearchPlaceConverterProvider.get().serializeToCurrentPageIfPossible(liveRequest, searchPage);
58
+ const displaySuggestions = !this.forceCloseSuggestion && this.displaySuggestListbox && hasSuggestions;
51
59
  return html `
52
60
  <div class="${classMap(classes)}" @focusout=${() => this.forceCloseSuggestion = false} role="search">
53
61
  <div class="ft-search-input--input-container" part="input-container" tabindex="-1">
@@ -56,12 +64,12 @@ export class FtSearchInput extends withI18n(FtSearchComponent) {
56
64
  part="input"
57
65
  type="search"
58
66
  placeholder="${this.placeholder ? this.placeholder.message : searchInputContext.messages.placeholder()}"
59
- .value=${this.liveQuery}
67
+ value="${this.liveQuery}"
60
68
  role="combobox"
61
69
  aria-autocomplete="list"
62
70
  aria-controls="${this.suggestions.listboxId}"
63
71
  aria-expanded="${this.displaySuggestListbox}"
64
- aria-activedescendant="${(_c = this.selectedSuggestOptionId) !== null && _c !== void 0 ? _c : nothing}"
72
+ aria-activedescendant="${(_a = this.suggestions.activeElementId) !== null && _a !== void 0 ? _a : nothing}"
65
73
  @input=${this.onInput}
66
74
  @click=${this.onClick}
67
75
  @focus=${this.onFocus}
@@ -69,9 +77,9 @@ export class FtSearchInput extends withI18n(FtSearchComponent) {
69
77
  @keyup=${this.onSearchBarKeyUp}>
70
78
  </div>
71
79
  <div class="sr-only" aria-live="polite" aria-atomic="true">
72
- ${this.renderLiveText()}
80
+ ${displaySuggestions ? searchInputContext.messages.ariaLiveSuggestions(this.suggestions.recentQueriesCount(), this.suggestions.suggestionsCount()) : ""}
73
81
  </div>
74
- ${this.shouldDisplaySuggestions ? this.suggestions.render() : nothing}
82
+ ${displaySuggestions ? this.suggestions.render() : nothing}
75
83
  </div>
76
84
  <div class="ft-search-input--actions" part="actions">
77
85
  ${this.liveQuery ? html `
@@ -97,14 +105,6 @@ export class FtSearchInput extends withI18n(FtSearchComponent) {
97
105
  </div>
98
106
  `;
99
107
  }
100
- renderLiveText() {
101
- if (this.shouldDisplaySuggestions) {
102
- return searchInputContext.messages.ariaLiveSuggestions(this.suggestResults.length);
103
- }
104
- else {
105
- return "";
106
- }
107
- }
108
108
  onSearchBarKeyDown(e) {
109
109
  this.forceCloseSuggestion = false;
110
110
  if (!["Escape", "Tab", "Shift"].includes(e.key)) {
@@ -112,38 +112,42 @@ export class FtSearchInput extends withI18n(FtSearchComponent) {
112
112
  }
113
113
  switch (e.key) {
114
114
  case "Tab":
115
- this.displaySuggestListbox = false;
116
- this.selectedSuggestOptionId = undefined;
115
+ this.suggestions.closeBox();
117
116
  break;
118
117
  case "Escape":
119
- if (this.shouldDisplaySuggestions) {
118
+ if (this.displaySuggestListbox && this.suggestions.optionsCount() > 0) {
120
119
  e.stopPropagation();
121
120
  e.preventDefault();
122
- this.displaySuggestListbox = false;
123
- this.selectedSuggestOptionId = undefined;
121
+ this.suggestions.closeBox();
124
122
  }
125
123
  break;
126
124
  case "ArrowDown":
127
125
  e.stopPropagation();
128
126
  e.preventDefault();
129
- this.suggestions.selectNextSuggestion();
127
+ this.suggestions.selectNext();
130
128
  break;
131
129
  case "ArrowUp":
132
130
  e.stopPropagation();
133
131
  e.preventDefault();
134
- this.suggestions.selectPreviousSuggestion();
132
+ this.suggestions.selectPrevious();
135
133
  break;
136
134
  case "ArrowLeft":
135
+ if (this.selectedSuggestOptionIndex !== undefined) {
136
+ e.stopPropagation();
137
+ e.preventDefault();
138
+ this.suggestions.selectLeft();
139
+ }
140
+ break;
137
141
  case "ArrowRight":
138
- if (this.selectedSuggestOptionId) {
142
+ if (this.selectedSuggestOptionIndex !== undefined) {
139
143
  e.stopPropagation();
140
144
  e.preventDefault();
141
- this.suggestions.clearSelection();
145
+ this.suggestions.selectRight();
142
146
  }
143
147
  break;
144
148
  case "Enter": {
145
149
  // Select suggested option if applicable
146
- const elementToClick = this.suggestions.selectedSuggestOption;
150
+ const elementToClick = this.suggestions.activeElement;
147
151
  if (elementToClick) {
148
152
  elementToClick.click();
149
153
  break; // Clicking the selected suggestion will already launch a search
@@ -185,19 +189,15 @@ export class FtSearchInput extends withI18n(FtSearchComponent) {
185
189
  this.input.focus();
186
190
  }
187
191
  launchSearch(query) {
188
- var _a;
192
+ var _a, _b;
189
193
  this.forceCloseSuggestion = true;
190
194
  this.dispatchEvent(new CustomEvent("change", { detail: query }));
191
195
  (_a = this.stateManager) === null || _a === void 0 ? void 0 : _a.setQuery(query);
196
+ (_b = this.stateManager) === null || _b === void 0 ? void 0 : _b.addRecentQuery(query);
192
197
  }
193
198
  onSuggestionSelected(value) {
194
199
  this.launchSearch(value);
195
- this.displaySuggestListbox = false;
196
- this.suggestions.clearSelection();
197
- }
198
- get shouldDisplaySuggestions() {
199
- var _a;
200
- return !this.forceCloseSuggestion && this.displaySuggestListbox && ((_a = this.suggestResults) !== null && _a !== void 0 ? _a : []).length > 0;
200
+ this.suggestions.closeBox();
201
201
  }
202
202
  onClick() {
203
203
  this.onFocus();
@@ -216,14 +216,14 @@ export class FtSearchInput extends withI18n(FtSearchComponent) {
216
216
  }
217
217
  async connectedCallback() {
218
218
  super.connectedCallback();
219
- document.addEventListener("pointerdown", this.closeSuggestListbox);
219
+ document.addEventListener("pointerdown", this.onPointerDown);
220
220
  if (this.focusOnDisplay && !isTouchScreen) {
221
221
  this.focusInput(false);
222
222
  }
223
223
  }
224
224
  disconnectedCallback() {
225
225
  super.disconnectedCallback();
226
- document.removeEventListener("pointerdown", this.closeSuggestListbox);
226
+ document.removeEventListener("pointerdown", this.onPointerDown);
227
227
  }
228
228
  }
229
229
  FtSearchInput.elementDefinitions = {
@@ -246,6 +246,9 @@ __decorate([
246
246
  __decorate([
247
247
  redux()
248
248
  ], FtSearchInput.prototype, "suggestResults", void 0);
249
+ __decorate([
250
+ redux({ selector: selectRecentQuerySuggestions })
251
+ ], FtSearchInput.prototype, "recentQuerySuggestions", void 0);
249
252
  __decorate([
250
253
  state()
251
254
  ], FtSearchInput.prototype, "forceCloseSuggestion", void 0);
@@ -254,7 +257,10 @@ __decorate([
254
257
  ], FtSearchInput.prototype, "displaySuggestListbox", void 0);
255
258
  __decorate([
256
259
  state()
257
- ], FtSearchInput.prototype, "selectedSuggestOptionId", void 0);
260
+ ], FtSearchInput.prototype, "selectedSuggestOptionIndex", void 0);
261
+ __decorate([
262
+ state()
263
+ ], FtSearchInput.prototype, "selectedSuggestOptionRemove", void 0);
258
264
  __decorate([
259
265
  property()
260
266
  ], FtSearchInput.prototype, "launchSearchPath", void 0);