@vaadin/field-base 22.0.0-alpha6 → 22.0.0-alpha7

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 (63) hide show
  1. package/index.d.ts +6 -9
  2. package/index.js +6 -9
  3. package/package.json +22 -17
  4. package/src/aria-label-controller.d.ts +11 -0
  5. package/src/aria-label-controller.js +53 -0
  6. package/src/checked-mixin.js +37 -67
  7. package/src/delegate-focus-mixin.js +147 -131
  8. package/src/delegate-state-mixin.d.ts +1 -1
  9. package/src/delegate-state-mixin.js +100 -88
  10. package/src/field-mixin.d.ts +39 -0
  11. package/src/field-mixin.js +317 -0
  12. package/src/input-constraints-mixin.d.ts +4 -2
  13. package/src/input-constraints-mixin.js +105 -86
  14. package/src/input-control-mixin.d.ts +52 -0
  15. package/src/input-control-mixin.js +170 -0
  16. package/src/input-controller.d.ts +11 -0
  17. package/src/input-controller.js +35 -0
  18. package/src/input-field-mixin.d.ts +2 -16
  19. package/src/input-field-mixin.js +17 -89
  20. package/src/input-mixin.d.ts +1 -1
  21. package/src/input-mixin.js +156 -145
  22. package/src/label-mixin.d.ts +1 -1
  23. package/src/label-mixin.js +72 -59
  24. package/src/pattern-mixin.js +9 -9
  25. package/src/shadow-focus-mixin.d.ts +21 -0
  26. package/src/shadow-focus-mixin.js +87 -0
  27. package/src/slot-controller.d.ts +8 -0
  28. package/src/slot-controller.js +36 -0
  29. package/src/slot-label-mixin.d.ts +20 -0
  30. package/src/slot-label-mixin.js +38 -0
  31. package/src/slot-styles-mixin.js +38 -31
  32. package/src/slot-target-mixin.d.ts +8 -19
  33. package/src/slot-target-mixin.js +93 -73
  34. package/src/styles/clear-button-styles.d.ts +8 -0
  35. package/src/styles/clear-button-styles.js +21 -0
  36. package/src/styles/field-shared-styles.d.ts +8 -0
  37. package/src/styles/field-shared-styles.js +29 -0
  38. package/src/styles/input-field-container-styles.d.ts +8 -0
  39. package/src/styles/input-field-container-styles.js +16 -0
  40. package/src/styles/input-field-shared-styles.d.ts +8 -0
  41. package/src/styles/input-field-shared-styles.js +10 -0
  42. package/src/text-area-controller.d.ts +11 -0
  43. package/src/text-area-controller.js +38 -0
  44. package/src/validate-mixin.d.ts +1 -9
  45. package/src/validate-mixin.js +43 -120
  46. package/src/aria-label-mixin.d.ts +0 -20
  47. package/src/aria-label-mixin.js +0 -71
  48. package/src/char-length-mixin.d.ts +0 -30
  49. package/src/char-length-mixin.js +0 -42
  50. package/src/clear-button-mixin.d.ts +0 -28
  51. package/src/clear-button-mixin.js +0 -82
  52. package/src/delegate-input-state-mixin.d.ts +0 -43
  53. package/src/delegate-input-state-mixin.js +0 -63
  54. package/src/field-aria-mixin.d.ts +0 -24
  55. package/src/field-aria-mixin.js +0 -61
  56. package/src/helper-text-mixin.d.ts +0 -24
  57. package/src/helper-text-mixin.js +0 -144
  58. package/src/input-slot-mixin.d.ts +0 -26
  59. package/src/input-slot-mixin.js +0 -71
  60. package/src/text-area-slot-mixin.d.ts +0 -21
  61. package/src/text-area-slot-mixin.js +0 -56
  62. package/src/text-field-mixin.d.ts +0 -21
  63. package/src/text-field-mixin.js +0 -17
@@ -5,109 +5,121 @@
5
5
  */
6
6
  import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
7
7
 
8
- const DelegateStateMixinImplementation = (superclass) =>
9
- class DelegateStateMixinClass extends superclass {
10
- /**
11
- * An array of the host attributes to delegate to the target element.
12
- */
13
- static get delegateAttrs() {
14
- return [];
15
- }
16
-
17
- /**
18
- * An array of the host properties to delegate to the target element.
19
- */
20
- static get delegateProps() {
21
- return [];
22
- }
23
-
24
- /**
25
- * A target element to which attributes and properties are delegated.
26
- */
27
- get _delegateStateTarget() {
28
- console.warn(`Please implement the '_delegateStateTarget' property in <${this.localName}>`);
29
- return null;
30
- }
31
-
32
- /** @protected */
33
- ready() {
34
- super.ready();
8
+ /**
9
+ * A mixin to delegate properties and attributes to a target element.
10
+ *
11
+ * @polymerMixin
12
+ */
13
+ export const DelegateStateMixin = dedupingMixin(
14
+ (superclass) =>
15
+ class DelegateStateMixinClass extends superclass {
16
+ static get properties() {
17
+ return {
18
+ /**
19
+ * A target element to which attributes and properties are delegated.
20
+ * @protected
21
+ */
22
+ stateTarget: {
23
+ type: Object,
24
+ observer: '_stateTargetChanged'
25
+ }
26
+ };
27
+ }
35
28
 
36
- this._createDelegateAttrsObserver();
37
- this._ensureAttrsDelegated();
29
+ /**
30
+ * An array of the host attributes to delegate to the target element.
31
+ */
32
+ static get delegateAttrs() {
33
+ return [];
34
+ }
38
35
 
39
- this._createDelegatePropsObserver();
40
- this._ensurePropsDelegated();
41
- }
36
+ /**
37
+ * An array of the host properties to delegate to the target element.
38
+ */
39
+ static get delegateProps() {
40
+ return [];
41
+ }
42
42
 
43
- /** @protected */
44
- _createDelegateAttrsObserver() {
45
- this._createMethodObserver(`_delegateAttrsChanged(${this.constructor.delegateAttrs.join(', ')})`);
46
- }
43
+ /** @protected */
44
+ ready() {
45
+ super.ready();
47
46
 
48
- /** @protected */
49
- _createDelegatePropsObserver() {
50
- this._createMethodObserver(`_delegatePropsChanged(${this.constructor.delegateProps.join(', ')})`);
51
- }
47
+ this._createDelegateAttrsObserver();
48
+ this._createDelegatePropsObserver();
49
+ }
52
50
 
53
- /** @protected */
54
- _ensureAttrsDelegated() {
55
- this.constructor.delegateAttrs.forEach((name) => {
56
- this._delegateAttribute(name, this[name]);
57
- });
58
- }
51
+ /** @protected */
52
+ _stateTargetChanged(target) {
53
+ if (target) {
54
+ this._ensureAttrsDelegated();
55
+ this._ensurePropsDelegated();
56
+ }
57
+ }
59
58
 
60
- /** @protected */
61
- _ensurePropsDelegated() {
62
- this.constructor.delegateProps.forEach((name) => {
63
- this._delegateProperty(name, this[name]);
64
- });
65
- }
59
+ /** @protected */
60
+ _createDelegateAttrsObserver() {
61
+ this._createMethodObserver(`_delegateAttrsChanged(${this.constructor.delegateAttrs.join(', ')})`);
62
+ }
66
63
 
67
- /** @protected */
68
- _delegateAttrsChanged(...values) {
69
- this.constructor.delegateAttrs.forEach((name, index) => {
70
- this._delegateAttribute(name, values[index]);
71
- });
72
- }
64
+ /** @protected */
65
+ _createDelegatePropsObserver() {
66
+ this._createMethodObserver(`_delegatePropsChanged(${this.constructor.delegateProps.join(', ')})`);
67
+ }
73
68
 
74
- /** @protected */
75
- _delegatePropsChanged(...values) {
76
- this.constructor.delegateProps.forEach((name, index) => {
77
- this._delegateProperty(name, values[index]);
78
- });
79
- }
69
+ /** @protected */
70
+ _ensureAttrsDelegated() {
71
+ this.constructor.delegateAttrs.forEach((name) => {
72
+ this._delegateAttribute(name, this[name]);
73
+ });
74
+ }
80
75
 
81
- /** @protected */
82
- _delegateAttribute(name, value) {
83
- if (!this._delegateStateTarget) {
84
- return;
76
+ /** @protected */
77
+ _ensurePropsDelegated() {
78
+ this.constructor.delegateProps.forEach((name) => {
79
+ this._delegateProperty(name, this[name]);
80
+ });
85
81
  }
86
82
 
87
- if (name === 'invalid') {
88
- this._delegateAttribute('aria-invalid', value ? 'true' : false);
83
+ /** @protected */
84
+ _delegateAttrsChanged(...values) {
85
+ this.constructor.delegateAttrs.forEach((name, index) => {
86
+ this._delegateAttribute(name, values[index]);
87
+ });
89
88
  }
90
89
 
91
- if (typeof value === 'boolean') {
92
- this._delegateStateTarget.toggleAttribute(name, value);
93
- } else if (value) {
94
- this._delegateStateTarget.setAttribute(name, value);
95
- } else {
96
- this._delegateStateTarget.removeAttribute(name);
90
+ /** @protected */
91
+ _delegatePropsChanged(...values) {
92
+ this.constructor.delegateProps.forEach((name, index) => {
93
+ this._delegateProperty(name, values[index]);
94
+ });
97
95
  }
98
- }
99
96
 
100
- /** @protected */
101
- _delegateProperty(name, value) {
102
- if (!this._delegateStateTarget) {
103
- return;
97
+ /** @protected */
98
+ _delegateAttribute(name, value) {
99
+ if (!this.stateTarget) {
100
+ return;
101
+ }
102
+
103
+ if (name === 'invalid') {
104
+ this._delegateAttribute('aria-invalid', value ? 'true' : false);
105
+ }
106
+
107
+ if (typeof value === 'boolean') {
108
+ this.stateTarget.toggleAttribute(name, value);
109
+ } else if (value) {
110
+ this.stateTarget.setAttribute(name, value);
111
+ } else {
112
+ this.stateTarget.removeAttribute(name);
113
+ }
104
114
  }
105
115
 
106
- this._delegateStateTarget[name] = value;
107
- }
108
- };
116
+ /** @protected */
117
+ _delegateProperty(name, value) {
118
+ if (!this.stateTarget) {
119
+ return;
120
+ }
109
121
 
110
- /**
111
- * A mixin to delegate properties and attributes to a target element.
112
- */
113
- export const DelegateStateMixin = dedupingMixin(DelegateStateMixinImplementation);
122
+ this.stateTarget[name] = value;
123
+ }
124
+ }
125
+ );
@@ -0,0 +1,39 @@
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 { LabelMixin } from './label-mixin.js';
7
+ import { ValidateMixin } from './validate-mixin.js';
8
+
9
+ /**
10
+ * A mixin to provide common field logic: label, error message and helper text.
11
+ */
12
+ declare function FieldMixin<T extends new (...args: any[]) => {}>(base: T): T & FieldMixinConstructor;
13
+
14
+ interface FieldMixinConstructor {
15
+ new (...args: any[]): FieldMixin;
16
+ }
17
+
18
+ interface FieldMixin extends LabelMixin, ValidateMixin {
19
+ /**
20
+ * A target element to which ARIA attributes are set.
21
+ */
22
+ ariaTarget: HTMLElement;
23
+
24
+ /**
25
+ * String used for the helper text.
26
+ *
27
+ * @attr {string} helper-text
28
+ */
29
+ helperText: string | null | undefined;
30
+
31
+ /**
32
+ * Error to show when the field is invalid.
33
+ *
34
+ * @attr {string} error-message
35
+ */
36
+ errorMessage: string;
37
+ }
38
+
39
+ export { FieldMixin, FieldMixinConstructor };
@@ -0,0 +1,317 @@
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 { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js';
7
+ import { Debouncer } from '@vaadin/component-base/src/debounce.js';
8
+ import { animationFrame } from '@vaadin/component-base/src/async.js';
9
+ import { LabelMixin } from './label-mixin.js';
10
+ import { ValidateMixin } from './validate-mixin.js';
11
+
12
+ /**
13
+ * A mixin to provide common field logic: label, error message and helper text.
14
+ *
15
+ * @polymerMixin
16
+ * @mixes LabelMixin
17
+ * @mixes ValidateMixin
18
+ */
19
+ export const FieldMixin = (superclass) =>
20
+ class FieldMixinClass extends ValidateMixin(LabelMixin(superclass)) {
21
+ static get properties() {
22
+ return {
23
+ /**
24
+ * A target element to which ARIA attributes are set.
25
+ * @protected
26
+ */
27
+ ariaTarget: {
28
+ type: Object,
29
+ observer: '_ariaTargetChanged'
30
+ },
31
+
32
+ /**
33
+ * Error to show when the field is invalid.
34
+ *
35
+ * @attr {string} error-message
36
+ */
37
+ errorMessage: {
38
+ type: String
39
+ },
40
+
41
+ /**
42
+ * String used for the helper text.
43
+ * @attr {string} helper-text
44
+ */
45
+ helperText: {
46
+ type: String,
47
+ observer: '_helperTextChanged'
48
+ },
49
+
50
+ /** @protected */
51
+ _helperId: String
52
+ };
53
+ }
54
+
55
+ /** @protected */
56
+ get slots() {
57
+ return {
58
+ ...super.slots,
59
+ 'error-message': () => {
60
+ const error = document.createElement('div');
61
+ error.textContent = this.errorMessage;
62
+ error.setAttribute('aria-live', 'assertive');
63
+ return error;
64
+ }
65
+ };
66
+ }
67
+
68
+ static get observers() {
69
+ return [
70
+ '__ariaChanged(invalid, _helperId)',
71
+ '__observeOffsetHeight(errorMessage, invalid, label, helperText)',
72
+ '_updateErrorMessage(invalid, errorMessage)'
73
+ ];
74
+ }
75
+
76
+ /** @protected */
77
+ get _errorNode() {
78
+ return this._getDirectSlotChild('error-message');
79
+ }
80
+
81
+ /** @protected */
82
+ get _helperNode() {
83
+ return this._getDirectSlotChild('helper');
84
+ }
85
+
86
+ /** @protected */
87
+ get _ariaAttr() {
88
+ return 'aria-describedby';
89
+ }
90
+
91
+ constructor() {
92
+ super();
93
+
94
+ // Ensure every instance has unique ID
95
+ const uniqueId = (FieldMixinClass._uniqueFieldId = 1 + FieldMixinClass._uniqueFieldId || 0);
96
+ this._errorId = `error-${this.localName}-${uniqueId}`;
97
+ this._helperId = `helper-${this.localName}-${uniqueId}`;
98
+
99
+ // Save generated ID to restore later
100
+ this.__savedHelperId = this._helperId;
101
+ }
102
+
103
+ /** @protected */
104
+ ready() {
105
+ super.ready();
106
+
107
+ const error = this._errorNode;
108
+ if (error) {
109
+ error.id = this._errorId;
110
+
111
+ this.__applyCustomError();
112
+
113
+ this._updateErrorMessage(this.invalid, this.errorMessage);
114
+ }
115
+
116
+ const helper = this._helperNode;
117
+ if (helper) {
118
+ this.__applyCustomHelper(helper);
119
+ }
120
+
121
+ this.__helperSlot = this.shadowRoot.querySelector('[name="helper"]');
122
+
123
+ this.__helperSlotObserver = new FlattenedNodesObserver(this.__helperSlot, (info) => {
124
+ const helper = this._currentHelper;
125
+
126
+ const newHelper = info.addedNodes.find((node) => node !== helper);
127
+ const oldHelper = info.removedNodes.find((node) => node === helper);
128
+
129
+ if (newHelper) {
130
+ // Custom helper is added, remove the previous one.
131
+ if (helper && helper.isConnected) {
132
+ this.removeChild(helper);
133
+ }
134
+
135
+ this.__applyCustomHelper(newHelper);
136
+
137
+ this.__helperIdObserver = new MutationObserver((mutations) => {
138
+ mutations.forEach((mutation) => {
139
+ // only handle helper nodes
140
+ if (
141
+ mutation.type === 'attributes' &&
142
+ mutation.attributeName === 'id' &&
143
+ mutation.target === this._currentHelper &&
144
+ mutation.target.id !== this.__savedHelperId
145
+ ) {
146
+ this.__updateHelperId(mutation.target);
147
+ }
148
+ });
149
+ });
150
+
151
+ this.__helperIdObserver.observe(newHelper, { attributes: true });
152
+ } else if (oldHelper) {
153
+ // The observer does not exist when default helper is removed.
154
+ if (this.__helperIdObserver) {
155
+ this.__helperIdObserver.disconnect();
156
+ }
157
+
158
+ this.__applyDefaultHelper(this.helperText);
159
+ }
160
+ });
161
+ }
162
+
163
+ /** @private */
164
+ __applyCustomError() {
165
+ const error = this.__errorMessage;
166
+ if (error && error !== this.errorMessage) {
167
+ this.errorMessage = error;
168
+ delete this.__errorMessage;
169
+ }
170
+ }
171
+
172
+ /** @private */
173
+ __applyCustomHelper(helper) {
174
+ this.__updateHelperId(helper);
175
+ this._currentHelper = helper;
176
+ this.__toggleHasHelper(helper.children.length > 0 || this.__isNotEmpty(helper.textContent));
177
+ }
178
+
179
+ /** @private */
180
+ __isNotEmpty(helperText) {
181
+ return helperText && helperText.trim() !== '';
182
+ }
183
+
184
+ /** @private */
185
+ __attachDefaultHelper() {
186
+ let helper = this.__defaultHelper;
187
+
188
+ if (!helper) {
189
+ helper = document.createElement('div');
190
+ helper.setAttribute('slot', 'helper');
191
+ this.__defaultHelper = helper;
192
+ }
193
+
194
+ helper.id = this.__savedHelperId;
195
+ this.appendChild(helper);
196
+ this._currentHelper = helper;
197
+
198
+ return helper;
199
+ }
200
+
201
+ /** @private */
202
+ __applyDefaultHelper(helperText) {
203
+ let helper = this._helperNode;
204
+
205
+ const hasHelperText = this.__isNotEmpty(helperText);
206
+ if (hasHelperText && !helper) {
207
+ // Create helper lazily
208
+ helper = this.__attachDefaultHelper();
209
+ }
210
+
211
+ // Only set text content for default helper
212
+ if (helper && helper === this.__defaultHelper) {
213
+ helper.textContent = helperText;
214
+ }
215
+
216
+ this.__toggleHasHelper(hasHelperText);
217
+ }
218
+
219
+ /** @private */
220
+ __toggleHasHelper(hasHelper) {
221
+ this.toggleAttribute('has-helper', hasHelper);
222
+ }
223
+
224
+ /**
225
+ * Dispatch an event if a specific size measurement property has changed.
226
+ * Supporting multiple properties here is needed for `vaadin-text-area`.
227
+ * @protected
228
+ */
229
+ _dispatchIronResizeEventIfNeeded(prop, value) {
230
+ const oldSize = '__old' + prop;
231
+ if (this[oldSize] !== undefined && this[oldSize] !== value) {
232
+ this.dispatchEvent(new CustomEvent('iron-resize', { bubbles: true, composed: true }));
233
+ }
234
+
235
+ this[oldSize] = value;
236
+ }
237
+
238
+ /** @private */
239
+ __observeOffsetHeight() {
240
+ this.__observeOffsetHeightDebouncer = Debouncer.debounce(
241
+ this.__observeOffsetHeightDebouncer,
242
+ animationFrame,
243
+ () => {
244
+ this._dispatchIronResizeEventIfNeeded('Height', this.offsetHeight);
245
+ }
246
+ );
247
+ }
248
+
249
+ /**
250
+ * @param {boolean} invalid
251
+ * @protected
252
+ */
253
+ _updateErrorMessage(invalid, errorMessage) {
254
+ const error = this._errorNode;
255
+ if (!error) {
256
+ return;
257
+ }
258
+
259
+ // save the custom error message content
260
+ if (error.textContent && !errorMessage) {
261
+ this.__errorMessage = error.textContent.trim();
262
+ }
263
+ const hasError = Boolean(invalid && errorMessage);
264
+ error.textContent = hasError ? errorMessage : '';
265
+ this.toggleAttribute('has-error-message', hasError);
266
+ }
267
+
268
+ /** @private */
269
+ __updateHelperId(customHelper) {
270
+ let newId;
271
+
272
+ if (customHelper.id) {
273
+ newId = customHelper.id;
274
+ } else {
275
+ newId = this.__savedHelperId;
276
+ customHelper.id = newId;
277
+ }
278
+
279
+ this._helperId = newId;
280
+ }
281
+
282
+ /** @protected */
283
+ _helperTextChanged(helperText) {
284
+ this.__applyDefaultHelper(helperText);
285
+ }
286
+
287
+ /** @protected */
288
+ _ariaTargetChanged(target) {
289
+ if (target) {
290
+ this._updateAriaAttribute(this.invalid, this._helperId);
291
+ }
292
+ }
293
+
294
+ /** @protected */
295
+ _updateAriaAttribute(invalid, helperId) {
296
+ const attr = this._ariaAttr;
297
+
298
+ if (this.ariaTarget && attr) {
299
+ // For groups, add all IDs to aria-labelledby rather than aria-describedby -
300
+ // that should guarantee that it's announced when the group is entered.
301
+ const ariaIds = attr === 'aria-describedby' ? [helperId] : [this._labelId, helperId];
302
+
303
+ // Error message ID needs to be dynamically added / removed based on the validity
304
+ // Otherwise assistive technologies would announce the error, even if we hide it.
305
+ if (invalid) {
306
+ ariaIds.push(this._errorId);
307
+ }
308
+
309
+ this.ariaTarget.setAttribute(attr, ariaIds.join(' '));
310
+ }
311
+ }
312
+
313
+ /** @private */
314
+ __ariaChanged(invalid, helperId) {
315
+ this._updateAriaAttribute(invalid, helperId);
316
+ }
317
+ };
@@ -3,7 +3,9 @@
3
3
  * Copyright (c) 2021 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
- import { DelegateInputStateMixin } from './delegate-input-state-mixin.js';
6
+ import { DelegateStateMixin } from './delegate-state-mixin.js';
7
+ import { InputMixin } from './input-mixin.js';
8
+ import { ValidateMixin } from './validate-mixin.js';
7
9
 
8
10
  /**
9
11
  * A mixin to combine multiple input validation constraints.
@@ -16,7 +18,7 @@ interface InputConstraintsMixinConstructor {
16
18
  new (...args: any[]): InputConstraintsMixin;
17
19
  }
18
20
 
19
- interface InputConstraintsMixin extends DelegateInputStateMixin {
21
+ interface InputConstraintsMixin extends DelegateStateMixin, InputMixin, ValidateMixin {
20
22
  /**
21
23
  * Returns true if the current input value satisfies all constraints (if any).
22
24
  */