@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.
- package/build/TextFieldSuggestionProvider.d.ts +2 -0
- package/build/TextFieldSuggestionProvider.js +18 -0
- package/build/ft-text-field.d.ts +15 -2
- package/build/ft-text-field.js +129 -40
- package/build/ft-text-field.light.js +153 -136
- package/build/ft-text-field.min.js +173 -156
- package/build/ft-text-field.properties.d.ts +1 -0
- package/build/ft-text-field.styles.js +7 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/models.d.ts +7 -0
- package/build/models.js +1 -0
- package/package.json +7 -7
|
@@ -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
|
+
}
|
package/build/ft-text-field.d.ts
CHANGED
|
@@ -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
|
-
|
|
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 {};
|
package/build/ft-text-field.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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=${
|
|
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
|
-
|
|
114
|
-
${
|
|
115
|
-
|
|
116
|
-
${this.
|
|
117
|
-
|
|
118
|
-
|
|
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.
|
|
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")
|
|
151
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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.
|
|
166
|
-
this.visibleSuggestions = this.
|
|
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.
|
|
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, "
|
|
438
|
+
], FtTextField.prototype, "slottedSuggestions", void 0);
|
|
350
439
|
export { FtTextField };
|