@vaadin/field-base 22.0.1 → 22.0.5
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/LICENSE +1 -1
- package/package.json +3 -3
- package/src/checked-mixin.d.ts +1 -1
- package/src/checked-mixin.js +1 -1
- package/src/delegate-focus-mixin.d.ts +7 -2
- package/src/delegate-focus-mixin.js +54 -6
- package/src/delegate-state-mixin.d.ts +1 -1
- package/src/delegate-state-mixin.js +1 -1
- package/src/error-controller.d.ts +36 -0
- package/src/error-controller.js +134 -0
- package/src/field-aria-controller.d.ts +1 -1
- package/src/field-aria-controller.js +1 -1
- package/src/field-mixin.d.ts +3 -1
- package/src/field-mixin.js +57 -221
- package/src/helper-controller.d.ts +23 -0
- package/src/helper-controller.js +185 -0
- package/src/input-constraints-mixin.d.ts +1 -1
- package/src/input-constraints-mixin.js +1 -1
- package/src/input-control-mixin.d.ts +1 -1
- package/src/input-control-mixin.js +11 -8
- package/src/input-controller.d.ts +3 -3
- package/src/input-controller.js +5 -4
- package/src/input-field-mixin.d.ts +1 -1
- package/src/input-field-mixin.js +16 -1
- package/src/input-mixin.d.ts +1 -1
- package/src/input-mixin.js +1 -1
- package/src/label-controller.d.ts +26 -0
- package/src/label-controller.js +188 -0
- package/src/label-mixin.d.ts +4 -3
- package/src/label-mixin.js +10 -49
- package/src/labelled-input-controller.d.ts +1 -1
- package/src/labelled-input-controller.js +17 -4
- package/src/pattern-mixin.d.ts +1 -1
- package/src/pattern-mixin.js +1 -1
- package/src/shadow-focus-mixin.d.ts +1 -1
- package/src/shadow-focus-mixin.js +16 -4
- package/src/slot-styles-mixin.d.ts +1 -1
- package/src/slot-styles-mixin.js +1 -1
- package/src/slot-target-controller.d.ts +31 -0
- package/src/slot-target-controller.js +119 -0
- package/src/styles/clear-button-styles.d.ts +1 -1
- package/src/styles/clear-button-styles.js +1 -1
- package/src/styles/field-shared-styles.d.ts +1 -1
- package/src/styles/field-shared-styles.js +1 -1
- package/src/styles/input-field-container-styles.d.ts +1 -1
- package/src/styles/input-field-container-styles.js +1 -1
- package/src/styles/input-field-shared-styles.d.ts +1 -1
- package/src/styles/input-field-shared-styles.js +1 -1
- package/src/text-area-controller.d.ts +3 -3
- package/src/text-area-controller.js +5 -4
- package/src/validate-mixin.d.ts +1 -1
- package/src/validate-mixin.js +1 -1
- package/src/slot-controller.d.ts +0 -8
- package/src/slot-controller.js +0 -36
- package/src/slot-label-mixin.d.ts +0 -15
- package/src/slot-label-mixin.js +0 -38
- package/src/slot-target-mixin.d.ts +0 -25
- package/src/slot-target-mixin.js +0 -110
package/src/field-mixin.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c) 2021 Vaadin Ltd.
|
|
3
|
+
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
|
-
import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js';
|
|
7
6
|
import { animationFrame } from '@vaadin/component-base/src/async.js';
|
|
8
7
|
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
|
|
9
8
|
import { Debouncer } from '@vaadin/component-base/src/debounce.js';
|
|
9
|
+
import { ErrorController } from './error-controller.js';
|
|
10
10
|
import { FieldAriaController } from './field-aria-controller.js';
|
|
11
|
+
import { HelperController } from './helper-controller.js';
|
|
11
12
|
import { LabelMixin } from './label-mixin.js';
|
|
12
13
|
import { ValidateMixin } from './validate-mixin.js';
|
|
13
14
|
|
|
@@ -38,7 +39,8 @@ export const FieldMixin = (superclass) =>
|
|
|
38
39
|
* @attr {string} error-message
|
|
39
40
|
*/
|
|
40
41
|
errorMessage: {
|
|
41
|
-
type: String
|
|
42
|
+
type: String,
|
|
43
|
+
observer: '_errorMessageChanged'
|
|
42
44
|
},
|
|
43
45
|
|
|
44
46
|
/**
|
|
@@ -55,34 +57,30 @@ export const FieldMixin = (superclass) =>
|
|
|
55
57
|
};
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
/** @protected */
|
|
59
|
-
get slots() {
|
|
60
|
-
return {
|
|
61
|
-
...super.slots,
|
|
62
|
-
'error-message': () => {
|
|
63
|
-
const error = document.createElement('div');
|
|
64
|
-
error.textContent = this.errorMessage;
|
|
65
|
-
return error;
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
60
|
static get observers() {
|
|
71
61
|
return [
|
|
72
62
|
'__observeOffsetHeight(errorMessage, invalid, label, helperText)',
|
|
73
|
-
'_updateErrorMessage(invalid, errorMessage)',
|
|
74
63
|
'_invalidChanged(invalid)',
|
|
75
|
-
'_requiredChanged(required)'
|
|
76
|
-
'_helperIdChanged(_helperId)'
|
|
64
|
+
'_requiredChanged(required)'
|
|
77
65
|
];
|
|
78
66
|
}
|
|
79
67
|
|
|
68
|
+
/** @protected */
|
|
69
|
+
get _errorId() {
|
|
70
|
+
return this._errorController.errorId;
|
|
71
|
+
}
|
|
72
|
+
|
|
80
73
|
/**
|
|
81
74
|
* @protected
|
|
82
75
|
* @return {HTMLElement}
|
|
83
76
|
*/
|
|
84
77
|
get _errorNode() {
|
|
85
|
-
return this.
|
|
78
|
+
return this._errorController.node;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** @protected */
|
|
82
|
+
get _helperId() {
|
|
83
|
+
return this._helperController.helperId;
|
|
86
84
|
}
|
|
87
85
|
|
|
88
86
|
/**
|
|
@@ -90,157 +88,38 @@ export const FieldMixin = (superclass) =>
|
|
|
90
88
|
* @return {HTMLElement}
|
|
91
89
|
*/
|
|
92
90
|
get _helperNode() {
|
|
93
|
-
return this.
|
|
91
|
+
return this._helperController.node;
|
|
94
92
|
}
|
|
95
93
|
|
|
96
94
|
constructor() {
|
|
97
95
|
super();
|
|
98
96
|
|
|
99
|
-
// Ensure every instance has unique ID
|
|
100
|
-
const uniqueId = (FieldMixinClass._uniqueFieldId = 1 + FieldMixinClass._uniqueFieldId || 0);
|
|
101
|
-
this._errorId = `error-${this.localName}-${uniqueId}`;
|
|
102
|
-
this._helperId = `helper-${this.localName}-${uniqueId}`;
|
|
103
|
-
|
|
104
|
-
// Save generated ID to restore later
|
|
105
|
-
this.__savedHelperId = this._helperId;
|
|
106
|
-
|
|
107
97
|
this._fieldAriaController = new FieldAriaController(this);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
/** @protected */
|
|
111
|
-
ready() {
|
|
112
|
-
super.ready();
|
|
113
|
-
|
|
114
|
-
const error = this._errorNode;
|
|
115
|
-
if (error) {
|
|
116
|
-
error.id = this._errorId;
|
|
117
|
-
|
|
118
|
-
this.__applyCustomError();
|
|
119
|
-
|
|
120
|
-
this._updateErrorMessage(this.invalid, this.errorMessage);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const helper = this._helperNode;
|
|
124
|
-
if (helper) {
|
|
125
|
-
this.__applyCustomHelper(helper);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
this.__helperSlot = this.shadowRoot.querySelector('[name="helper"]');
|
|
129
|
-
|
|
130
|
-
this.__helperSlotObserver = new FlattenedNodesObserver(this.__helperSlot, (info) => {
|
|
131
|
-
const helper = this._currentHelper;
|
|
132
|
-
|
|
133
|
-
const newHelper = info.addedNodes.find((node) => node !== helper);
|
|
134
|
-
const oldHelper = info.removedNodes.find((node) => node === helper);
|
|
135
|
-
|
|
136
|
-
if (newHelper) {
|
|
137
|
-
// Custom helper is added, remove the previous one.
|
|
138
|
-
if (helper && helper.isConnected) {
|
|
139
|
-
this.removeChild(helper);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
this.__applyCustomHelper(newHelper);
|
|
143
|
-
|
|
144
|
-
this.__helperIdObserver = new MutationObserver((mutations) => {
|
|
145
|
-
mutations.forEach((mutation) => {
|
|
146
|
-
// only handle helper nodes
|
|
147
|
-
if (
|
|
148
|
-
mutation.type === 'attributes' &&
|
|
149
|
-
mutation.attributeName === 'id' &&
|
|
150
|
-
mutation.target === this._currentHelper &&
|
|
151
|
-
mutation.target.id !== this.__savedHelperId
|
|
152
|
-
) {
|
|
153
|
-
this.__updateHelperId(mutation.target);
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
this.__helperIdObserver.observe(newHelper, { attributes: true });
|
|
159
|
-
} else if (oldHelper) {
|
|
160
|
-
// The observer does not exist when default helper is removed.
|
|
161
|
-
if (this.__helperIdObserver) {
|
|
162
|
-
this.__helperIdObserver.disconnect();
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
this.__applyDefaultHelper(this.helperText);
|
|
166
|
-
}
|
|
167
|
-
});
|
|
98
|
+
this._helperController = new HelperController(this);
|
|
99
|
+
this._errorController = new ErrorController(this);
|
|
168
100
|
|
|
169
101
|
this.addController(this._fieldAriaController);
|
|
170
|
-
|
|
102
|
+
this.addController(this._helperController);
|
|
103
|
+
this.addController(this._errorController);
|
|
171
104
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
this.errorMessage = error;
|
|
177
|
-
delete this.__errorMessage;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* @param {HTMLElement} helper
|
|
183
|
-
* @private
|
|
184
|
-
*/
|
|
185
|
-
__applyCustomHelper(helper) {
|
|
186
|
-
this.__updateHelperId(helper);
|
|
187
|
-
this._currentHelper = helper;
|
|
188
|
-
this.__toggleHasHelper(helper.children.length > 0 || this.__isNotEmpty(helper.textContent));
|
|
189
|
-
}
|
|
105
|
+
this._labelController.addEventListener('label-changed', (event) => {
|
|
106
|
+
const { hasLabel, node } = event.detail;
|
|
107
|
+
this.__labelChanged(hasLabel, node);
|
|
108
|
+
});
|
|
190
109
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
__isNotEmpty(helperText) {
|
|
196
|
-
return helperText && helperText.trim() !== '';
|
|
110
|
+
this._helperController.addEventListener('helper-changed', (event) => {
|
|
111
|
+
const { hasHelper, node } = event.detail;
|
|
112
|
+
this.__helperChanged(hasHelper, node);
|
|
113
|
+
});
|
|
197
114
|
}
|
|
198
115
|
|
|
199
116
|
/** @private */
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
helper.setAttribute('slot', 'helper');
|
|
206
|
-
this.__defaultHelper = helper;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
helper.id = this.__savedHelperId;
|
|
210
|
-
this._helperId = helper.id;
|
|
211
|
-
this.appendChild(helper);
|
|
212
|
-
this._currentHelper = helper;
|
|
213
|
-
|
|
214
|
-
return helper;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* @param {string} helperText
|
|
219
|
-
* @private
|
|
220
|
-
*/
|
|
221
|
-
__applyDefaultHelper(helperText) {
|
|
222
|
-
let helper = this._helperNode;
|
|
223
|
-
|
|
224
|
-
const hasHelperText = this.__isNotEmpty(helperText);
|
|
225
|
-
if (hasHelperText && !helper) {
|
|
226
|
-
// Create helper lazily
|
|
227
|
-
helper = this.__attachDefaultHelper();
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Only set text content for default helper
|
|
231
|
-
if (helper && helper === this.__defaultHelper) {
|
|
232
|
-
helper.textContent = helperText;
|
|
117
|
+
__helperChanged(hasHelper, helperNode) {
|
|
118
|
+
if (hasHelper) {
|
|
119
|
+
this._fieldAriaController.setHelperId(helperNode.id);
|
|
120
|
+
} else {
|
|
121
|
+
this._fieldAriaController.setHelperId(null);
|
|
233
122
|
}
|
|
234
|
-
|
|
235
|
-
this.__toggleHasHelper(hasHelperText);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* @param {boolean} hasHelper
|
|
240
|
-
* @private
|
|
241
|
-
*/
|
|
242
|
-
__toggleHasHelper(hasHelper) {
|
|
243
|
-
this.toggleAttribute('has-helper', hasHelper);
|
|
244
123
|
}
|
|
245
124
|
|
|
246
125
|
/**
|
|
@@ -268,66 +147,23 @@ export const FieldMixin = (superclass) =>
|
|
|
268
147
|
);
|
|
269
148
|
}
|
|
270
149
|
|
|
271
|
-
/**
|
|
272
|
-
|
|
273
|
-
* @override
|
|
274
|
-
*/
|
|
275
|
-
_toggleHasLabelAttribute() {
|
|
276
|
-
super._toggleHasLabelAttribute();
|
|
277
|
-
|
|
150
|
+
/** @private */
|
|
151
|
+
__labelChanged(hasLabel, labelNode) {
|
|
278
152
|
// Label ID should be only added when the label content is present.
|
|
279
153
|
// Otherwise, it may conflict with an `aria-label` attribute possibly added by the user.
|
|
280
|
-
if (
|
|
281
|
-
this._fieldAriaController.setLabelId(
|
|
154
|
+
if (hasLabel) {
|
|
155
|
+
this._fieldAriaController.setLabelId(labelNode.id);
|
|
282
156
|
} else {
|
|
283
157
|
this._fieldAriaController.setLabelId(null);
|
|
284
158
|
}
|
|
285
159
|
}
|
|
286
160
|
|
|
287
161
|
/**
|
|
288
|
-
* @param {boolean} invalid
|
|
289
162
|
* @param {string | null | undefined} errorMessage
|
|
290
163
|
* @protected
|
|
291
164
|
*/
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (!error) {
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// save the custom error message content
|
|
299
|
-
if (error.textContent && !errorMessage) {
|
|
300
|
-
this.__errorMessage = error.textContent.trim();
|
|
301
|
-
}
|
|
302
|
-
const hasError = Boolean(invalid && errorMessage);
|
|
303
|
-
error.textContent = hasError ? errorMessage : '';
|
|
304
|
-
error.hidden = !hasError;
|
|
305
|
-
this.toggleAttribute('has-error-message', hasError);
|
|
306
|
-
|
|
307
|
-
// Role alert will make the error message announce immediately
|
|
308
|
-
// as the field becomes invalid
|
|
309
|
-
if (hasError) {
|
|
310
|
-
error.setAttribute('role', 'alert');
|
|
311
|
-
} else {
|
|
312
|
-
error.removeAttribute('role');
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* @param {HTMLElement} customHelper
|
|
318
|
-
* @private
|
|
319
|
-
*/
|
|
320
|
-
__updateHelperId(customHelper) {
|
|
321
|
-
let newId;
|
|
322
|
-
|
|
323
|
-
if (customHelper.id) {
|
|
324
|
-
newId = customHelper.id;
|
|
325
|
-
} else {
|
|
326
|
-
newId = this.__savedHelperId;
|
|
327
|
-
customHelper.id = newId;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
this._helperId = newId;
|
|
165
|
+
_errorMessageChanged(errorMessage) {
|
|
166
|
+
this._errorController.setErrorMessage(errorMessage);
|
|
331
167
|
}
|
|
332
168
|
|
|
333
169
|
/**
|
|
@@ -335,7 +171,7 @@ export const FieldMixin = (superclass) =>
|
|
|
335
171
|
* @protected
|
|
336
172
|
*/
|
|
337
173
|
_helperTextChanged(helperText) {
|
|
338
|
-
this.
|
|
174
|
+
this._helperController.setHelperText(helperText);
|
|
339
175
|
}
|
|
340
176
|
|
|
341
177
|
/**
|
|
@@ -356,25 +192,25 @@ export const FieldMixin = (superclass) =>
|
|
|
356
192
|
this._fieldAriaController.setRequired(required);
|
|
357
193
|
}
|
|
358
194
|
|
|
359
|
-
/**
|
|
360
|
-
* @param {string} helperId
|
|
361
|
-
* @protected
|
|
362
|
-
*/
|
|
363
|
-
_helperIdChanged(helperId) {
|
|
364
|
-
this._fieldAriaController.setHelperId(helperId);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
195
|
/**
|
|
368
196
|
* @param {boolean} required
|
|
369
197
|
* @protected
|
|
370
198
|
*/
|
|
371
199
|
_invalidChanged(invalid) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
200
|
+
this._errorController.setInvalid(invalid);
|
|
201
|
+
|
|
202
|
+
// This timeout is needed to prevent NVDA from announcing the error message twice:
|
|
203
|
+
// 1. Once adding the `[role=alert]` attribute by the `_updateErrorMessage` method (OK).
|
|
204
|
+
// 2. Once linking the error ID with the ARIA target here (unwanted).
|
|
205
|
+
// Related issue: https://github.com/vaadin/web-components/issues/3061.
|
|
206
|
+
setTimeout(() => {
|
|
207
|
+
// Error message ID needs to be dynamically added / removed based on the validity
|
|
208
|
+
// Otherwise assistive technologies would announce the error, even if we hide it.
|
|
209
|
+
if (invalid) {
|
|
210
|
+
this._fieldAriaController.setErrorId(this._errorController.errorId);
|
|
211
|
+
} else {
|
|
212
|
+
this._fieldAriaController.setErrorId(null);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
379
215
|
}
|
|
380
216
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2021 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A controller that manages the helper node content.
|
|
10
|
+
*/
|
|
11
|
+
export class HelperController extends SlotController {
|
|
12
|
+
/**
|
|
13
|
+
* String used for the helper text.
|
|
14
|
+
*/
|
|
15
|
+
helperText: string | null | undefined;
|
|
16
|
+
|
|
17
|
+
helperId: string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Set helper text based on corresponding host property.
|
|
21
|
+
*/
|
|
22
|
+
setHelperText(helperText: string): void;
|
|
23
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2021 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A controller that manages the helper node content.
|
|
10
|
+
*/
|
|
11
|
+
export class HelperController extends SlotController {
|
|
12
|
+
constructor(host) {
|
|
13
|
+
// Do not provide slot factory, as only create helper lazily.
|
|
14
|
+
super(host, 'helper');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get helperId() {
|
|
18
|
+
return this.node && this.node.id;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Override to initialize the newly added custom helper.
|
|
23
|
+
*
|
|
24
|
+
* @param {Node} helperNode
|
|
25
|
+
* @protected
|
|
26
|
+
* @override
|
|
27
|
+
*/
|
|
28
|
+
initCustomNode(helperNode) {
|
|
29
|
+
this.__updateHelperId(helperNode);
|
|
30
|
+
|
|
31
|
+
this.__observeHelper(helperNode);
|
|
32
|
+
|
|
33
|
+
const hasHelper = this.__hasHelper(helperNode);
|
|
34
|
+
this.__toggleHasHelper(hasHelper);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Override to cleanup helper node when it's removed.
|
|
39
|
+
*
|
|
40
|
+
* @param {Node} _node
|
|
41
|
+
* @protected
|
|
42
|
+
* @override
|
|
43
|
+
*/
|
|
44
|
+
teardownNode(_node) {
|
|
45
|
+
// The observer does not exist when the default helper is removed.
|
|
46
|
+
if (this.__helperIdObserver) {
|
|
47
|
+
this.__helperIdObserver.disconnect();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const helperNode = this.getSlotChild();
|
|
51
|
+
|
|
52
|
+
// Custom node is added to helper slot
|
|
53
|
+
if (helperNode && helperNode !== this.defaultNode) {
|
|
54
|
+
const hasHelper = this.__hasHelper(helperNode);
|
|
55
|
+
this.__toggleHasHelper(hasHelper);
|
|
56
|
+
} else {
|
|
57
|
+
// Restore default helper if needed
|
|
58
|
+
this.__applyDefaultHelper(this.helperText, helperNode);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Set helper text based on corresponding host property.
|
|
64
|
+
* @param {string} helperText
|
|
65
|
+
*/
|
|
66
|
+
setHelperText(helperText) {
|
|
67
|
+
this.helperText = helperText;
|
|
68
|
+
|
|
69
|
+
const helperNode = this.getSlotChild();
|
|
70
|
+
if (!helperNode || helperNode === this.defaultNode) {
|
|
71
|
+
this.__applyDefaultHelper(helperText, helperNode);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {HTMLElement} helperNode
|
|
77
|
+
* @return {boolean}
|
|
78
|
+
* @private
|
|
79
|
+
*/
|
|
80
|
+
__hasHelper(helperNode) {
|
|
81
|
+
if (!helperNode) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return helperNode.children.length > 0 || this.__isNotEmpty(helperNode.textContent);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {string} helperText
|
|
90
|
+
* @private
|
|
91
|
+
*/
|
|
92
|
+
__isNotEmpty(helperText) {
|
|
93
|
+
return helperText && helperText.trim() !== '';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {string} helperText
|
|
98
|
+
* @param {Node} helperNode
|
|
99
|
+
* @private
|
|
100
|
+
*/
|
|
101
|
+
__applyDefaultHelper(helperText, helperNode) {
|
|
102
|
+
const hasHelperText = this.__isNotEmpty(helperText);
|
|
103
|
+
|
|
104
|
+
if (hasHelperText && !helperNode) {
|
|
105
|
+
// Set slot factory lazily to only create helper node when needed.
|
|
106
|
+
this.slotFactory = () => document.createElement('div');
|
|
107
|
+
|
|
108
|
+
helperNode = this.attachDefaultNode();
|
|
109
|
+
|
|
110
|
+
this.__updateHelperId(helperNode);
|
|
111
|
+
this.__observeHelper(helperNode);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (helperNode) {
|
|
115
|
+
helperNode.textContent = helperText;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.__toggleHasHelper(hasHelperText);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @param {HTMLElement} helperNode
|
|
123
|
+
* @private
|
|
124
|
+
*/
|
|
125
|
+
__observeHelper(helperNode) {
|
|
126
|
+
this.__helperObserver = new MutationObserver((mutations) => {
|
|
127
|
+
mutations.forEach((mutation) => {
|
|
128
|
+
const target = mutation.target;
|
|
129
|
+
|
|
130
|
+
// Ensure the mutation target is the currently connected helper
|
|
131
|
+
// to ignore async mutations dispatched for removed element.
|
|
132
|
+
const isHelperMutation = target === this.node;
|
|
133
|
+
|
|
134
|
+
if (mutation.type === 'attributes') {
|
|
135
|
+
// We use attributeFilter to only observe ID mutation,
|
|
136
|
+
// no need to check for attribute name separately.
|
|
137
|
+
if (isHelperMutation && target.id !== this.defaultId) {
|
|
138
|
+
this.__updateHelperId(target);
|
|
139
|
+
}
|
|
140
|
+
} else if (isHelperMutation || target.parentElement === this.node) {
|
|
141
|
+
// Update has-helper when textContent changes
|
|
142
|
+
const hasHelper = this.__hasHelper(this.node);
|
|
143
|
+
this.__toggleHasHelper(hasHelper);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Observe changes to helper ID attribute, text content and children.
|
|
149
|
+
this.__helperObserver.observe(helperNode, {
|
|
150
|
+
attributes: true,
|
|
151
|
+
attributeFilter: ['id'],
|
|
152
|
+
childList: true,
|
|
153
|
+
subtree: true,
|
|
154
|
+
characterData: true
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @param {boolean} hasHelper
|
|
160
|
+
* @private
|
|
161
|
+
*/
|
|
162
|
+
__toggleHasHelper(hasHelper) {
|
|
163
|
+
this.host.toggleAttribute('has-helper', hasHelper);
|
|
164
|
+
|
|
165
|
+
// Make it possible for other mixins to observe change
|
|
166
|
+
this.dispatchEvent(
|
|
167
|
+
new CustomEvent('helper-changed', {
|
|
168
|
+
detail: {
|
|
169
|
+
hasHelper,
|
|
170
|
+
node: this.node
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @param {HTMLElement} helperNode
|
|
178
|
+
* @private
|
|
179
|
+
*/
|
|
180
|
+
__updateHelperId(helperNode) {
|
|
181
|
+
if (!helperNode.id) {
|
|
182
|
+
helperNode.id = this.defaultId;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c) 2021 Vaadin Ltd.
|
|
3
|
+
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c) 2021 Vaadin Ltd.
|
|
3
|
+
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
import { KeyboardMixin } from '@vaadin/component-base/src/keyboard-mixin.js';
|
|
@@ -107,9 +107,7 @@ export const InputControlMixin = (superclass) =>
|
|
|
107
107
|
_onClearButtonClick(event) {
|
|
108
108
|
event.preventDefault();
|
|
109
109
|
this.inputElement.focus();
|
|
110
|
-
this.
|
|
111
|
-
this.inputElement.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
|
112
|
-
this.inputElement.dispatchEvent(new Event('change', { bubbles: true }));
|
|
110
|
+
this.__clear();
|
|
113
111
|
}
|
|
114
112
|
|
|
115
113
|
/**
|
|
@@ -137,10 +135,8 @@ export const InputControlMixin = (superclass) =>
|
|
|
137
135
|
_onKeyDown(event) {
|
|
138
136
|
super._onKeyDown(event);
|
|
139
137
|
|
|
140
|
-
if (event.key === 'Escape' && this.clearButtonVisible) {
|
|
141
|
-
|
|
142
|
-
this.clear();
|
|
143
|
-
dispatchChange && this.inputElement.dispatchEvent(new Event('change', { bubbles: true }));
|
|
138
|
+
if (event.key === 'Escape' && this.clearButtonVisible && !!this.value) {
|
|
139
|
+
this.__clear();
|
|
144
140
|
}
|
|
145
141
|
}
|
|
146
142
|
|
|
@@ -167,4 +163,11 @@ export const InputControlMixin = (superclass) =>
|
|
|
167
163
|
})
|
|
168
164
|
);
|
|
169
165
|
}
|
|
166
|
+
|
|
167
|
+
/** @private */
|
|
168
|
+
__clear() {
|
|
169
|
+
this.clear();
|
|
170
|
+
this.inputElement.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
|
171
|
+
this.inputElement.dispatchEvent(new Event('change', { bubbles: true }));
|
|
172
|
+
}
|
|
170
173
|
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c) 2021 Vaadin Ltd.
|
|
3
|
+
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
|
-
import { SlotController } from '
|
|
6
|
+
import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* A controller to create and initialize slotted `<input>` element.
|
|
10
10
|
*/
|
|
11
|
-
export class InputController
|
|
11
|
+
export class InputController extends SlotController {}
|
package/src/input-controller.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c) 2021 Vaadin Ltd.
|
|
3
|
+
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
|
-
import { SlotController } from '
|
|
6
|
+
import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* A controller to create and initialize slotted `<input>` element.
|
|
10
10
|
*/
|
|
11
11
|
export class InputController extends SlotController {
|
|
12
12
|
constructor(host, callback) {
|
|
13
|
-
super(
|
|
13
|
+
super(
|
|
14
|
+
host,
|
|
14
15
|
'input',
|
|
15
16
|
() => document.createElement('input'),
|
|
16
17
|
(host, node) => {
|
|
@@ -30,6 +31,6 @@ export class InputController extends SlotController {
|
|
|
30
31
|
callback(node);
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
+
);
|
|
34
35
|
}
|
|
35
36
|
}
|