@fluid-topics/ft-text-field 1.2.27 → 1.2.29

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,2 @@
1
+ import { FtTextFieldSuggestionDefinition } from "./models";
2
+ export declare function basicSuggestionsProvider(values: Array<string | FtTextFieldSuggestionDefinition>, maxSuggest?: number): (query: string) => FtTextFieldSuggestionDefinition[];
@@ -0,0 +1,18 @@
1
+ export function basicSuggestionsProvider(values, maxSuggest = 20) {
2
+ return (query) => values.map(value => typeof value == "string" ? ({ value: value, label: value }) : value)
3
+ .filter(v => v.label.toLowerCase().includes(query.toLowerCase()))
4
+ .sort((a, b) => alphabeticalSortWithPriorToStartWithQuery(a.label, b.label, query))
5
+ .slice(0, maxSuggest);
6
+ }
7
+ function alphabeticalSortWithPriorToStartWithQuery(a, b, query) {
8
+ const lowerA = a.toLowerCase();
9
+ const lowerB = b.toLowerCase();
10
+ const lowerQuery = query.toLowerCase();
11
+ if (lowerA.startsWith(lowerQuery) && !lowerB.includes(lowerQuery)) {
12
+ return -1;
13
+ }
14
+ if (lowerB.startsWith(lowerQuery) && !lowerA.startsWith(lowerQuery)) {
15
+ return 1;
16
+ }
17
+ return lowerA.localeCompare(lowerB);
18
+ }
@@ -2,6 +2,7 @@ import { PropertyValues } from "lit";
2
2
  import { ElementDefinitionsMap, FtLitElement } from "@fluid-topics/ft-wc-utils";
3
3
  import { FtTextFieldProperties } from "./ft-text-field.properties";
4
4
  import { FtTextFieldSuggestion } from "./ft-text-field-suggestion";
5
+ import { FtTextFieldSuggestionDefinition } from "./models";
5
6
  declare const FtTextField_base: import("@fluid-topics/ft-wc-utils").FtFormComponentType<typeof FtLitElement>;
6
7
  export declare class FtTextField extends FtTextField_base implements FtTextFieldProperties {
7
8
  static elementDefinitions: ElementDefinitionsMap;
@@ -29,23 +30,33 @@ export declare class FtTextField extends FtTextField_base implements FtTextField
29
30
  maxLength?: number;
30
31
  password: boolean;
31
32
  autocomplete?: string;
33
+ suggestionsProvider?: (query: string) => FtTextFieldSuggestionDefinition[] | Promise<FtTextFieldSuggestionDefinition[]>;
32
34
  focused: boolean;
33
35
  hidePassword: boolean;
34
36
  hideSuggestions: boolean;
35
37
  visibleSuggestions: FtTextFieldSuggestion[];
38
+ providedSuggestions: FtTextFieldSuggestionDefinition[];
36
39
  mainPanel?: HTMLElement;
37
40
  input?: HTMLInputElement;
38
41
  newValueSuggestion?: FtTextFieldSuggestion;
39
42
  suggestionsContainer?: HTMLElement;
40
- suggestions: FtTextFieldSuggestion[];
43
+ providedSuggestionsInDom: FtTextFieldSuggestion[];
44
+ slottedSuggestions: FtTextFieldSuggestion[];
41
45
  focus(): void;
42
- constructor();
43
46
  protected render(): import("lit").TemplateResult<1>;
47
+ private resolveInputType;
48
+ private renderSuggestions;
44
49
  private renderPasswordIcon;
45
50
  private renderIcon;
46
51
  protected update(props: PropertyValues): void;
52
+ private updateSuggestions;
47
53
  private filterSuggestionsIfNeeded;
48
54
  protected contentAvailableCallback(props: PropertyValues): void;
55
+ private watchAutofillInterval?;
56
+ private setupWatchAutofill;
57
+ private clearWatchAutofill;
58
+ connectedCallback(): void;
59
+ disconnectedCallback(): void;
49
60
  setValue(newValue: string, fireChangeEvent?: boolean): void;
50
61
  private handleInput;
51
62
  private handleClick;
@@ -54,6 +65,8 @@ export declare class FtTextField extends FtTextField_base implements FtTextField
54
65
  private onFocus;
55
66
  private onMainPanelBlur;
56
67
  private togglePasswordVisibility;
68
+ private isPasswordField;
57
69
  private setSuggestionPosition;
70
+ private suggestionsShouldBeDisplayed;
58
71
  }
59
72
  export {};
@@ -5,7 +5,8 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  import { html, nothing } from "lit";
8
- import { property, query, queryAssignedElements, state } from "lit/decorators.js";
8
+ import { property, query, queryAll, queryAssignedElements, state } from "lit/decorators.js";
9
+ import { repeat } from "lit/directives/repeat.js";
9
10
  import { classMap } from "lit/directives/class-map.js";
10
11
  import { ifDefined } from "lit/directives/if-defined.js";
11
12
  import { computeFlipOffsetPosition, FtLitElement, noTextInputDefaultClearButton, toFtFormComponent } from "@fluid-topics/ft-wc-utils";
@@ -13,9 +14,28 @@ import { FtTypography, FtTypographyBody1 } from "@fluid-topics/ft-typography";
13
14
  import { FtInputLabel } from "@fluid-topics/ft-input-label";
14
15
  import { FtRipple } from "@fluid-topics/ft-ripple";
15
16
  import { FtIcon, FtIcons } from "@fluid-topics/ft-icon";
16
- import { styles } from "./ft-text-field.styles";
17
+ import { FtTextFieldCssVariables, styles } from "./ft-text-field.styles";
17
18
  import { FtTextFieldSuggestion } from "./ft-text-field-suggestion";
18
19
  class FtTextField extends toFtFormComponent(FtLitElement, "textbox") {
20
+ constructor() {
21
+ super(...arguments);
22
+ this._value = "";
23
+ this.dispatchedValue = "";
24
+ this.outlined = false;
25
+ this.disabled = false;
26
+ this.error = false;
27
+ this.fixedMenuPosition = false;
28
+ this.prefix = null;
29
+ this.passwordHiddenIcon = FtIcons.EYE_SLASH;
30
+ this.passwordRevealedIcon = FtIcons.EYE;
31
+ this.filterSuggestions = false;
32
+ this.password = false;
33
+ this.focused = false;
34
+ this.hidePassword = true;
35
+ this.hideSuggestions = false;
36
+ this.visibleSuggestions = [];
37
+ this.providedSuggestions = [];
38
+ }
19
39
  /*
20
40
  * We prevent lit from creating setter and getter to differentiate
21
41
  * when the value change comes from inside or outside the component
@@ -40,26 +60,7 @@ class FtTextField extends toFtFormComponent(FtLitElement, "textbox") {
40
60
  var _a;
41
61
  (_a = this.input) === null || _a === void 0 ? void 0 : _a.focus();
42
62
  }
43
- constructor() {
44
- super();
45
- this._value = "";
46
- this.dispatchedValue = "";
47
- this.outlined = false;
48
- this.disabled = false;
49
- this.error = false;
50
- this.fixedMenuPosition = false;
51
- this.prefix = null;
52
- this.passwordHiddenIcon = FtIcons.EYE_SLASH;
53
- this.passwordRevealedIcon = FtIcons.EYE;
54
- this.filterSuggestions = false;
55
- this.password = false;
56
- this.focused = false;
57
- this.hidePassword = true;
58
- this.hideSuggestions = false;
59
- this.visibleSuggestions = [];
60
- }
61
63
  render() {
62
- var _a;
63
64
  const classes = {
64
65
  "ft-text-field": true,
65
66
  "ft-text-field--filled": !this.outlined,
@@ -70,11 +71,14 @@ class FtTextField extends toFtFormComponent(FtLitElement, "textbox") {
70
71
  "ft-text-field--in-error": this.error,
71
72
  "ft-text-field--fixed": this.fixedMenuPosition,
72
73
  "ft-text-field--with-prefix": !!this.prefix,
73
- "ft-text-field--hide-suggestions": this.hideSuggestions || this.visibleSuggestions.length == 0,
74
+ "ft-text-field--hide-suggestions": !this.suggestionsShouldBeDisplayed(),
74
75
  "ft-text-field--raised-label": this.focused || this.value != "",
75
76
  "ft-text-field--with-icon": !!this.icon,
76
- "ft-text-field--with-password": this.password
77
+ "ft-text-field--with-password": this.isPasswordField()
77
78
  };
79
+ const suggestionsAreProvidedAndQueryNotInPossiblesValues = this.suggestionsProvider && this.value && this.value != "" && !this.providedSuggestions.map(p => p.label).includes(this.value);
80
+ const suggestionsFromSlotAndQueryNotEmpty = !this.suggestionsProvider && this.filterSuggestions && this.value && this.value != "";
81
+ const shouldDisplayQueryAsNewSuggestion = suggestionsFromSlotAndQueryNotEmpty || suggestionsAreProvidedAndQueryNotInPossiblesValues;
78
82
  return html `
79
83
  <div class="${classMap(classes)}">
80
84
  <div class="ft-text-field--main-panel"
@@ -94,7 +98,7 @@ class FtTextField extends toFtFormComponent(FtLitElement, "textbox") {
94
98
  ${this.prefix}
95
99
  </ft-typography>
96
100
  ` : nothing}
97
- <input type=${(_a = this.type) !== null && _a !== void 0 ? _a : ((this.password && this.hidePassword) ? "password" : "text")}
101
+ <input type=${this.resolveInputType()}
98
102
  name=${ifDefined(this.name)}
99
103
  maxlength=${ifDefined(this.maxLength || undefined)}
100
104
  aria-label="${this.label}"
@@ -110,12 +114,14 @@ class FtTextField extends toFtFormComponent(FtLitElement, "textbox") {
110
114
  </div>
111
115
  <div class="ft-text-field--suggestions"
112
116
  @suggestion-selected=${this.onSuggestionSelected}>
113
- <slot @slotchange=${() => this.filterSuggestionsIfNeeded()}></slot>
114
- ${this.filterSuggestions && (this.value && this.value != "") ? html `
115
- <ft-text-field-suggestion class="ft-text-field-suggestion--new-value" helper="${this.suggestionsHelper}">
116
- ${this.value}
117
- </ft-text-field-suggestion>
118
- ` : nothing}
117
+ ${this.renderSuggestions()}
118
+ ${shouldDisplayQueryAsNewSuggestion
119
+ ? html `
120
+ <ft-text-field-suggestion class="ft-text-field-suggestion--new-value" helper="${this.suggestionsHelper}">
121
+ ${this.value}
122
+ </ft-text-field-suggestion>
123
+ `
124
+ : nothing}
119
125
  </div>
120
126
  </div>
121
127
  ${this.helper ? html `
@@ -126,6 +132,32 @@ class FtTextField extends toFtFormComponent(FtLitElement, "textbox") {
126
132
  </div>
127
133
  `;
128
134
  }
135
+ resolveInputType() {
136
+ var _a;
137
+ if (this.isPasswordField()) {
138
+ return this.hidePassword ? "password" : "text";
139
+ }
140
+ return (_a = this.type) !== null && _a !== void 0 ? _a : "text";
141
+ }
142
+ renderSuggestions() {
143
+ if (this.suggestionsProvider) {
144
+ return html `
145
+ ${repeat(this.providedSuggestions, suggestion => html `
146
+ <ft-text-field-suggestion value=${suggestion.value} @click=${suggestion.clickHandler}>
147
+ ${suggestion.icon ? html `
148
+ <ft-icon .value=${suggestion.icon}></ft-icon>
149
+ `
150
+ : nothing}
151
+ ${suggestion.label}
152
+ </ft-text-field-suggestion>`)}
153
+ `;
154
+ }
155
+ else {
156
+ return html `
157
+ <slot @slotchange=${() => this.filterSuggestionsIfNeeded()}></slot>
158
+ `;
159
+ }
160
+ }
129
161
  renderPasswordIcon() {
130
162
  return html `
131
163
  <ft-icon class="ft-text-field--icon"
@@ -135,7 +167,7 @@ class FtTextField extends toFtFormComponent(FtLitElement, "textbox") {
135
167
  `;
136
168
  }
137
169
  renderIcon() {
138
- if (this.password) {
170
+ if (this.isPasswordField()) {
139
171
  return this.renderPasswordIcon();
140
172
  }
141
173
  return this.icon ? html `
@@ -147,8 +179,11 @@ class FtTextField extends toFtFormComponent(FtLitElement, "textbox") {
147
179
  }
148
180
  update(props) {
149
181
  super.update(props);
150
- if (props.has("value") || props.has("filterSuggestions")) {
151
- this.filterSuggestionsIfNeeded();
182
+ if (props.has("value")
183
+ || props.has("filterSuggestions")
184
+ || props.has("suggestionsProvider")
185
+ || props.has("hideSuggestions")) {
186
+ this.updateSuggestions();
152
187
  }
153
188
  if (props.has("value") && props.get("value") != null) {
154
189
  this.hideSuggestions = false;
@@ -156,17 +191,22 @@ class FtTextField extends toFtFormComponent(FtLitElement, "textbox") {
156
191
  if (props.has("dispatchedValue") && props.get("dispatchedValue") != null) {
157
192
  this.hideSuggestions = true;
158
193
  }
159
- if (props.has("visibleSuggestions") && this.visibleSuggestions.length) {
160
- this.setSuggestionPosition();
194
+ }
195
+ async updateSuggestions() {
196
+ if (this.suggestionsProvider) {
197
+ this.providedSuggestions = await this.suggestionsProvider(this.value);
198
+ }
199
+ else {
200
+ this.filterSuggestionsIfNeeded();
161
201
  }
162
202
  }
163
203
  filterSuggestionsIfNeeded() {
164
204
  if (this.filterSuggestions) {
165
- this.suggestions.forEach(s => s.hidden = !s.getValue().toLowerCase().includes(this.value.toLowerCase()));
166
- this.visibleSuggestions = this.suggestions.filter(s => !s.hidden);
205
+ this.slottedSuggestions.forEach(s => s.hidden = !s.getValue().toLowerCase().includes(this.value.toLowerCase()));
206
+ this.visibleSuggestions = this.slottedSuggestions.filter(s => !s.hidden);
167
207
  }
168
208
  else {
169
- this.visibleSuggestions = this.suggestions;
209
+ this.visibleSuggestions = this.slottedSuggestions;
170
210
  }
171
211
  if (this.newValueSuggestion) {
172
212
  this.visibleSuggestions = [...this.visibleSuggestions, this.newValueSuggestion];
@@ -178,6 +218,40 @@ class FtTextField extends toFtFormComponent(FtLitElement, "textbox") {
178
218
  if (props.has("focused") && !this.hideSuggestions && this.visibleSuggestions.length > 0) {
179
219
  this.setSuggestionPosition();
180
220
  }
221
+ if (props.has("value") && this.suggestionsProvider) {
222
+ this.visibleSuggestions = [...this.providedSuggestionsInDom];
223
+ }
224
+ if (props.has("autocomplete")) {
225
+ this.setupWatchAutofill();
226
+ }
227
+ if (this.suggestionsShouldBeDisplayed()) {
228
+ this.setSuggestionPosition();
229
+ }
230
+ }
231
+ setupWatchAutofill() {
232
+ var _a;
233
+ this.clearWatchAutofill();
234
+ if (((_a = this.autocomplete) !== null && _a !== void 0 ? _a : "off") !== "off") {
235
+ this.watchAutofillInterval = setInterval(() => {
236
+ var _a, _b, _c;
237
+ if (((_a = this.input) === null || _a === void 0 ? void 0 : _a.matches(":autofill")) && this.dispatchedValue !== ((_b = this.input) === null || _b === void 0 ? void 0 : _b.value)) {
238
+ this.setValue((_c = this.input) === null || _c === void 0 ? void 0 : _c.value, true);
239
+ }
240
+ }, 200);
241
+ }
242
+ }
243
+ clearWatchAutofill() {
244
+ if (this.watchAutofillInterval) {
245
+ clearInterval(this.watchAutofillInterval);
246
+ }
247
+ }
248
+ connectedCallback() {
249
+ super.connectedCallback();
250
+ this.setupWatchAutofill();
251
+ }
252
+ disconnectedCallback() {
253
+ super.disconnectedCallback();
254
+ this.clearWatchAutofill();
181
255
  }
182
256
  setValue(newValue, fireChangeEvent = false) {
183
257
  if (this.value !== newValue) {
@@ -237,15 +311,21 @@ class FtTextField extends toFtFormComponent(FtLitElement, "textbox") {
237
311
  togglePasswordVisibility() {
238
312
  this.hidePassword = !this.hidePassword;
239
313
  }
314
+ isPasswordField() {
315
+ return this.password || this.type === "password";
316
+ }
240
317
  setSuggestionPosition() {
241
318
  this.suggestionsContainer.style.width = this.mainPanel.getBoundingClientRect().width + "px";
242
319
  const fallbackPlacements = ["bottom", "top"];
243
- computeFlipOffsetPosition(this.mainPanel, this.suggestionsContainer, "bottom", fallbackPlacements, "fixed", 0)
320
+ computeFlipOffsetPosition(this.mainPanel, this.suggestionsContainer, "bottom", fallbackPlacements, "fixed", FtTextFieldCssVariables.suggestSize.name, 0)
244
321
  .then(({ x, y }) => {
245
322
  this.suggestionsContainer.style.left = `${x}px`;
246
323
  this.suggestionsContainer.style.top = `${y}px`;
247
324
  });
248
325
  }
326
+ suggestionsShouldBeDisplayed() {
327
+ return !this.hideSuggestions && (this.visibleSuggestions.length || this.providedSuggestions.length);
328
+ }
249
329
  }
250
330
  FtTextField.elementDefinitions = {
251
331
  "ft-input-label": FtInputLabel,
@@ -320,6 +400,9 @@ __decorate([
320
400
  __decorate([
321
401
  property({ type: String })
322
402
  ], FtTextField.prototype, "autocomplete", void 0);
403
+ __decorate([
404
+ property({ attribute: false })
405
+ ], FtTextField.prototype, "suggestionsProvider", void 0);
323
406
  __decorate([
324
407
  state()
325
408
  ], FtTextField.prototype, "focused", void 0);
@@ -332,6 +415,9 @@ __decorate([
332
415
  __decorate([
333
416
  state()
334
417
  ], FtTextField.prototype, "visibleSuggestions", void 0);
418
+ __decorate([
419
+ state()
420
+ ], FtTextField.prototype, "providedSuggestions", void 0);
335
421
  __decorate([
336
422
  query(".ft-text-field--main-panel")
337
423
  ], FtTextField.prototype, "mainPanel", void 0);
@@ -344,7 +430,10 @@ __decorate([
344
430
  __decorate([
345
431
  query(".ft-text-field--suggestions")
346
432
  ], FtTextField.prototype, "suggestionsContainer", void 0);
433
+ __decorate([
434
+ queryAll("ft-text-field-suggestion")
435
+ ], FtTextField.prototype, "providedSuggestionsInDom", void 0);
347
436
  __decorate([
348
437
  queryAssignedElements({ selector: "ft-text-field-suggestion" })
349
- ], FtTextField.prototype, "suggestions", void 0);
438
+ ], FtTextField.prototype, "slottedSuggestions", void 0);
350
439
  export { FtTextField };