@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.
Files changed (58) hide show
  1. package/LICENSE +1 -1
  2. package/package.json +3 -3
  3. package/src/checked-mixin.d.ts +1 -1
  4. package/src/checked-mixin.js +1 -1
  5. package/src/delegate-focus-mixin.d.ts +7 -2
  6. package/src/delegate-focus-mixin.js +54 -6
  7. package/src/delegate-state-mixin.d.ts +1 -1
  8. package/src/delegate-state-mixin.js +1 -1
  9. package/src/error-controller.d.ts +36 -0
  10. package/src/error-controller.js +134 -0
  11. package/src/field-aria-controller.d.ts +1 -1
  12. package/src/field-aria-controller.js +1 -1
  13. package/src/field-mixin.d.ts +3 -1
  14. package/src/field-mixin.js +57 -221
  15. package/src/helper-controller.d.ts +23 -0
  16. package/src/helper-controller.js +185 -0
  17. package/src/input-constraints-mixin.d.ts +1 -1
  18. package/src/input-constraints-mixin.js +1 -1
  19. package/src/input-control-mixin.d.ts +1 -1
  20. package/src/input-control-mixin.js +11 -8
  21. package/src/input-controller.d.ts +3 -3
  22. package/src/input-controller.js +5 -4
  23. package/src/input-field-mixin.d.ts +1 -1
  24. package/src/input-field-mixin.js +16 -1
  25. package/src/input-mixin.d.ts +1 -1
  26. package/src/input-mixin.js +1 -1
  27. package/src/label-controller.d.ts +26 -0
  28. package/src/label-controller.js +188 -0
  29. package/src/label-mixin.d.ts +4 -3
  30. package/src/label-mixin.js +10 -49
  31. package/src/labelled-input-controller.d.ts +1 -1
  32. package/src/labelled-input-controller.js +17 -4
  33. package/src/pattern-mixin.d.ts +1 -1
  34. package/src/pattern-mixin.js +1 -1
  35. package/src/shadow-focus-mixin.d.ts +1 -1
  36. package/src/shadow-focus-mixin.js +16 -4
  37. package/src/slot-styles-mixin.d.ts +1 -1
  38. package/src/slot-styles-mixin.js +1 -1
  39. package/src/slot-target-controller.d.ts +31 -0
  40. package/src/slot-target-controller.js +119 -0
  41. package/src/styles/clear-button-styles.d.ts +1 -1
  42. package/src/styles/clear-button-styles.js +1 -1
  43. package/src/styles/field-shared-styles.d.ts +1 -1
  44. package/src/styles/field-shared-styles.js +1 -1
  45. package/src/styles/input-field-container-styles.d.ts +1 -1
  46. package/src/styles/input-field-container-styles.js +1 -1
  47. package/src/styles/input-field-shared-styles.d.ts +1 -1
  48. package/src/styles/input-field-shared-styles.js +1 -1
  49. package/src/text-area-controller.d.ts +3 -3
  50. package/src/text-area-controller.js +5 -4
  51. package/src/validate-mixin.d.ts +1 -1
  52. package/src/validate-mixin.js +1 -1
  53. package/src/slot-controller.d.ts +0 -8
  54. package/src/slot-controller.js +0 -36
  55. package/src/slot-label-mixin.d.ts +0 -15
  56. package/src/slot-label-mixin.js +0 -38
  57. package/src/slot-target-mixin.d.ts +0 -25
  58. package/src/slot-target-mixin.js +0 -110
@@ -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._getDirectSlotChild('error-message');
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._getDirectSlotChild('helper');
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
- /** @private */
173
- __applyCustomError() {
174
- const error = this.__errorMessage;
175
- if (error && error !== this.errorMessage) {
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
- * @param {string} helperText
193
- * @private
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
- __attachDefaultHelper() {
201
- let helper = this.__defaultHelper;
202
-
203
- if (!helper) {
204
- helper = document.createElement('div');
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
- * @protected
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 (this.hasAttribute('has-label')) {
281
- this._fieldAriaController.setLabelId(this._labelId);
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
- _updateErrorMessage(invalid, errorMessage) {
293
- const error = this._errorNode;
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.__applyDefaultHelper(helperText);
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
- // Error message ID needs to be dynamically added / removed based on the validity
373
- // Otherwise assistive technologies would announce the error, even if we hide it.
374
- if (invalid) {
375
- this._fieldAriaController.setErrorId(this._errorId);
376
- } else {
377
- this._fieldAriaController.setErrorId(null);
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 { Constructor } from '@open-wc/dedupe-mixin';
@@ -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 { Constructor } from '@open-wc/dedupe-mixin';
@@ -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.clear();
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
- const dispatchChange = !!this.value;
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 './slot-controller.js';
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 implements SlotController {}
11
+ export class InputController extends SlotController {}
@@ -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 './slot-controller.js';
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(host, [
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
  }