@vaadin/field-base 22.0.0-rc1 → 22.0.3
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 +1 -1
- package/src/delegate-focus-mixin.js +1 -1
- package/src/delegate-state-mixin.d.ts +1 -1
- package/src/delegate-state-mixin.js +1 -1
- 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 +50 -161
- 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 +1 -1
- 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 +1 -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 +186 -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 +1 -1
- package/src/slot-label-mixin.d.ts +1 -1
- package/src/slot-label-mixin.js +1 -14
- package/src/slot-styles-mixin.d.ts +1 -1
- package/src/slot-styles-mixin.js +1 -1
- package/src/slot-target-mixin.d.ts +1 -1
- package/src/slot-target-mixin.js +1 -1
- 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/LICENSE
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/field-base",
|
|
3
|
-
"version": "22.0.
|
|
3
|
+
"version": "22.0.3",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@open-wc/dedupe-mixin": "^1.3.0",
|
|
33
33
|
"@polymer/polymer": "^3.0.0",
|
|
34
|
-
"@vaadin/component-base": "22.0.
|
|
34
|
+
"@vaadin/component-base": "^22.0.3",
|
|
35
35
|
"lit": "^2.0.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
@@ -39,5 +39,5 @@
|
|
|
39
39
|
"@vaadin/testing-helpers": "^0.3.2",
|
|
40
40
|
"sinon": "^9.2.1"
|
|
41
41
|
},
|
|
42
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "935ad1ea65a79b0f9ecb10d767689479b36c4e07"
|
|
43
43
|
}
|
package/src/checked-mixin.d.ts
CHANGED
package/src/checked-mixin.js
CHANGED
|
@@ -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 { 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 { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
|
package/src/field-mixin.d.ts
CHANGED
|
@@ -1,10 +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
6
|
import { Constructor } from '@open-wc/dedupe-mixin';
|
|
7
7
|
import { ControllerMixinClass } from '@vaadin/component-base/src/controller-mixin.js';
|
|
8
|
+
import { SlotMixinClass } from '@vaadin/component-base/src/slot-mixin.js';
|
|
8
9
|
import { LabelMixinClass } from './label-mixin.js';
|
|
9
10
|
import { ValidateMixinClass } from './validate-mixin.js';
|
|
10
11
|
|
|
@@ -17,6 +18,7 @@ export declare function FieldMixin<T extends Constructor<HTMLElement>>(
|
|
|
17
18
|
Constructor<ControllerMixinClass> &
|
|
18
19
|
Constructor<FieldMixinClass> &
|
|
19
20
|
Constructor<LabelMixinClass> &
|
|
21
|
+
Constructor<SlotMixinClass> &
|
|
20
22
|
Constructor<ValidateMixinClass>;
|
|
21
23
|
|
|
22
24
|
export declare class FieldMixinClass {
|
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 { SlotMixin } from '@vaadin/component-base/src/slot-mixin.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
|
|
|
@@ -17,10 +18,11 @@ import { ValidateMixin } from './validate-mixin.js';
|
|
|
17
18
|
* @polymerMixin
|
|
18
19
|
* @mixes ControllerMixin
|
|
19
20
|
* @mixes LabelMixin
|
|
21
|
+
* @mixes SlotMixin
|
|
20
22
|
* @mixes ValidateMixin
|
|
21
23
|
*/
|
|
22
24
|
export const FieldMixin = (superclass) =>
|
|
23
|
-
class FieldMixinClass extends ValidateMixin(LabelMixin(ControllerMixin(superclass))) {
|
|
25
|
+
class FieldMixinClass extends ValidateMixin(LabelMixin(ControllerMixin(SlotMixin(superclass)))) {
|
|
24
26
|
static get properties() {
|
|
25
27
|
return {
|
|
26
28
|
/**
|
|
@@ -72,8 +74,7 @@ export const FieldMixin = (superclass) =>
|
|
|
72
74
|
'__observeOffsetHeight(errorMessage, invalid, label, helperText)',
|
|
73
75
|
'_updateErrorMessage(invalid, errorMessage)',
|
|
74
76
|
'_invalidChanged(invalid)',
|
|
75
|
-
'_requiredChanged(required)'
|
|
76
|
-
'_helperIdChanged(_helperId)'
|
|
77
|
+
'_requiredChanged(required)'
|
|
77
78
|
];
|
|
78
79
|
}
|
|
79
80
|
|
|
@@ -85,12 +86,17 @@ export const FieldMixin = (superclass) =>
|
|
|
85
86
|
return this._getDirectSlotChild('error-message');
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
/** @protected */
|
|
90
|
+
get _helperId() {
|
|
91
|
+
return this._helperController.helperId;
|
|
92
|
+
}
|
|
93
|
+
|
|
88
94
|
/**
|
|
89
95
|
* @protected
|
|
90
96
|
* @return {HTMLElement}
|
|
91
97
|
*/
|
|
92
98
|
get _helperNode() {
|
|
93
|
-
return this.
|
|
99
|
+
return this._helperController.node;
|
|
94
100
|
}
|
|
95
101
|
|
|
96
102
|
constructor() {
|
|
@@ -99,12 +105,22 @@ export const FieldMixin = (superclass) =>
|
|
|
99
105
|
// Ensure every instance has unique ID
|
|
100
106
|
const uniqueId = (FieldMixinClass._uniqueFieldId = 1 + FieldMixinClass._uniqueFieldId || 0);
|
|
101
107
|
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
108
|
|
|
107
109
|
this._fieldAriaController = new FieldAriaController(this);
|
|
110
|
+
this._helperController = new HelperController(this);
|
|
111
|
+
|
|
112
|
+
this.addController(this._fieldAriaController);
|
|
113
|
+
this.addController(this._helperController);
|
|
114
|
+
|
|
115
|
+
this._labelController.addEventListener('label-changed', (event) => {
|
|
116
|
+
const { hasLabel, node } = event.detail;
|
|
117
|
+
this.__labelChanged(hasLabel, node);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
this._helperController.addEventListener('helper-changed', (event) => {
|
|
121
|
+
const { hasHelper, node } = event.detail;
|
|
122
|
+
this.__helperChanged(hasHelper, node);
|
|
123
|
+
});
|
|
108
124
|
}
|
|
109
125
|
|
|
110
126
|
/** @protected */
|
|
@@ -119,54 +135,6 @@ export const FieldMixin = (superclass) =>
|
|
|
119
135
|
|
|
120
136
|
this._updateErrorMessage(this.invalid, this.errorMessage);
|
|
121
137
|
}
|
|
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
|
-
});
|
|
168
|
-
|
|
169
|
-
this.addController(this._fieldAriaController);
|
|
170
138
|
}
|
|
171
139
|
|
|
172
140
|
/** @private */
|
|
@@ -178,69 +146,13 @@ export const FieldMixin = (superclass) =>
|
|
|
178
146
|
}
|
|
179
147
|
}
|
|
180
148
|
|
|
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
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* @param {string} helperText
|
|
193
|
-
* @private
|
|
194
|
-
*/
|
|
195
|
-
__isNotEmpty(helperText) {
|
|
196
|
-
return helperText && helperText.trim() !== '';
|
|
197
|
-
}
|
|
198
|
-
|
|
199
149
|
/** @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;
|
|
150
|
+
__helperChanged(hasHelper, helperNode) {
|
|
151
|
+
if (hasHelper) {
|
|
152
|
+
this._fieldAriaController.setHelperId(helperNode.id);
|
|
153
|
+
} else {
|
|
154
|
+
this._fieldAriaController.setHelperId(null);
|
|
233
155
|
}
|
|
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
156
|
}
|
|
245
157
|
|
|
246
158
|
/**
|
|
@@ -268,17 +180,12 @@ export const FieldMixin = (superclass) =>
|
|
|
268
180
|
);
|
|
269
181
|
}
|
|
270
182
|
|
|
271
|
-
/**
|
|
272
|
-
|
|
273
|
-
* @override
|
|
274
|
-
*/
|
|
275
|
-
_toggleHasLabelAttribute() {
|
|
276
|
-
super._toggleHasLabelAttribute();
|
|
277
|
-
|
|
183
|
+
/** @private */
|
|
184
|
+
__labelChanged(hasLabel, labelNode) {
|
|
278
185
|
// Label ID should be only added when the label content is present.
|
|
279
186
|
// Otherwise, it may conflict with an `aria-label` attribute possibly added by the user.
|
|
280
|
-
if (
|
|
281
|
-
this._fieldAriaController.setLabelId(
|
|
187
|
+
if (hasLabel) {
|
|
188
|
+
this._fieldAriaController.setLabelId(labelNode.id);
|
|
282
189
|
} else {
|
|
283
190
|
this._fieldAriaController.setLabelId(null);
|
|
284
191
|
}
|
|
@@ -301,6 +208,7 @@ export const FieldMixin = (superclass) =>
|
|
|
301
208
|
}
|
|
302
209
|
const hasError = Boolean(invalid && errorMessage);
|
|
303
210
|
error.textContent = hasError ? errorMessage : '';
|
|
211
|
+
error.hidden = !hasError;
|
|
304
212
|
this.toggleAttribute('has-error-message', hasError);
|
|
305
213
|
|
|
306
214
|
// Role alert will make the error message announce immediately
|
|
@@ -312,29 +220,12 @@ export const FieldMixin = (superclass) =>
|
|
|
312
220
|
}
|
|
313
221
|
}
|
|
314
222
|
|
|
315
|
-
/**
|
|
316
|
-
* @param {HTMLElement} customHelper
|
|
317
|
-
* @private
|
|
318
|
-
*/
|
|
319
|
-
__updateHelperId(customHelper) {
|
|
320
|
-
let newId;
|
|
321
|
-
|
|
322
|
-
if (customHelper.id) {
|
|
323
|
-
newId = customHelper.id;
|
|
324
|
-
} else {
|
|
325
|
-
newId = this.__savedHelperId;
|
|
326
|
-
customHelper.id = newId;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
this._helperId = newId;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
223
|
/**
|
|
333
224
|
* @param {string} helperText
|
|
334
225
|
* @protected
|
|
335
226
|
*/
|
|
336
227
|
_helperTextChanged(helperText) {
|
|
337
|
-
this.
|
|
228
|
+
this._helperController.setHelperText(helperText);
|
|
338
229
|
}
|
|
339
230
|
|
|
340
231
|
/**
|
|
@@ -355,25 +246,23 @@ export const FieldMixin = (superclass) =>
|
|
|
355
246
|
this._fieldAriaController.setRequired(required);
|
|
356
247
|
}
|
|
357
248
|
|
|
358
|
-
/**
|
|
359
|
-
* @param {string} helperId
|
|
360
|
-
* @protected
|
|
361
|
-
*/
|
|
362
|
-
_helperIdChanged(helperId) {
|
|
363
|
-
this._fieldAriaController.setHelperId(helperId);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
249
|
/**
|
|
367
250
|
* @param {boolean} required
|
|
368
251
|
* @protected
|
|
369
252
|
*/
|
|
370
253
|
_invalidChanged(invalid) {
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
254
|
+
// This timeout is needed to prevent NVDA from announcing the error message twice:
|
|
255
|
+
// 1. Once adding the `[role=alert]` attribute by the `_updateErrorMessage` method (OK).
|
|
256
|
+
// 2. Once linking the error ID with the ARIA target here (unwanted).
|
|
257
|
+
// Related issue: https://github.com/vaadin/web-components/issues/3061.
|
|
258
|
+
setTimeout(() => {
|
|
259
|
+
// Error message ID needs to be dynamically added / removed based on the validity
|
|
260
|
+
// Otherwise assistive technologies would announce the error, even if we hide it.
|
|
261
|
+
if (invalid) {
|
|
262
|
+
this._fieldAriaController.setErrorId(this._errorId);
|
|
263
|
+
} else {
|
|
264
|
+
this._fieldAriaController.setErrorId(null);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
378
267
|
}
|
|
379
268
|
};
|
|
@@ -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';
|