@vaadin/field-base 22.0.3 → 22.0.6
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/package.json +3 -3
- package/src/delegate-focus-mixin.d.ts +6 -1
- package/src/delegate-focus-mixin.js +53 -5
- package/src/error-controller.d.ts +36 -0
- package/src/error-controller.js +134 -0
- package/src/field-mixin.js +17 -69
- package/src/input-control-mixin.js +23 -7
- package/src/input-field-mixin.js +15 -0
- package/src/label-controller.js +2 -0
- package/src/shadow-focus-mixin.js +15 -3
- package/src/slot-target-controller.d.ts +31 -0
- package/src/slot-target-controller.js +119 -0
- package/src/slot-label-mixin.d.ts +0 -15
- package/src/slot-label-mixin.js +0 -25
- package/src/slot-target-mixin.d.ts +0 -25
- package/src/slot-target-mixin.js +0 -110
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/field-base",
|
|
3
|
-
"version": "22.0.
|
|
3
|
+
"version": "22.0.6",
|
|
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.6",
|
|
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": "18c55872d764e338e0f98e65826cbe895156251a"
|
|
43
43
|
}
|
|
@@ -6,13 +6,18 @@
|
|
|
6
6
|
import { Constructor } from '@open-wc/dedupe-mixin';
|
|
7
7
|
import { DisabledMixinClass } from '@vaadin/component-base/src/disabled-mixin.js';
|
|
8
8
|
import { FocusMixinClass } from '@vaadin/component-base/src/focus-mixin.js';
|
|
9
|
+
import { TabindexMixinClass } from '@vaadin/component-base/src/tabindex-mixin.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* A mixin to forward focus to an element in the light DOM.
|
|
12
13
|
*/
|
|
13
14
|
export declare function DelegateFocusMixin<T extends Constructor<HTMLElement>>(
|
|
14
15
|
base: T
|
|
15
|
-
): T &
|
|
16
|
+
): T &
|
|
17
|
+
Constructor<DelegateFocusMixinClass> &
|
|
18
|
+
Constructor<DisabledMixinClass> &
|
|
19
|
+
Constructor<FocusMixinClass> &
|
|
20
|
+
Constructor<TabindexMixinClass>;
|
|
16
21
|
|
|
17
22
|
export declare class DelegateFocusMixinClass {
|
|
18
23
|
/**
|
|
@@ -4,19 +4,19 @@
|
|
|
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';
|
|
7
|
-
import { DisabledMixin } from '@vaadin/component-base/src/disabled-mixin.js';
|
|
8
7
|
import { FocusMixin } from '@vaadin/component-base/src/focus-mixin.js';
|
|
8
|
+
import { TabindexMixin } from '@vaadin/component-base/src/tabindex-mixin.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* A mixin to forward focus to an element in the light DOM.
|
|
12
12
|
*
|
|
13
13
|
* @polymerMixin
|
|
14
|
-
* @mixes DisabledMixin
|
|
15
14
|
* @mixes FocusMixin
|
|
15
|
+
* @mixes TabindexMixin
|
|
16
16
|
*/
|
|
17
17
|
export const DelegateFocusMixin = dedupingMixin(
|
|
18
18
|
(superclass) =>
|
|
19
|
-
class DelegateFocusMixinClass extends FocusMixin(
|
|
19
|
+
class DelegateFocusMixinClass extends FocusMixin(TabindexMixin(superclass)) {
|
|
20
20
|
static get properties() {
|
|
21
21
|
return {
|
|
22
22
|
/**
|
|
@@ -40,6 +40,19 @@ export const DelegateFocusMixin = dedupingMixin(
|
|
|
40
40
|
type: Object,
|
|
41
41
|
readOnly: true,
|
|
42
42
|
observer: '_focusElementChanged'
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Indicates whether the element can be focused and where it participates in sequential keyboard navigation.
|
|
47
|
+
*
|
|
48
|
+
* By default, the host element does not have tabindex attribute. Instead, `focusElement` should have it.
|
|
49
|
+
* Toggling `tabindex` attribute on the host element propagates its value to `focusElement`.
|
|
50
|
+
*
|
|
51
|
+
* @protected
|
|
52
|
+
*/
|
|
53
|
+
tabindex: {
|
|
54
|
+
type: Number,
|
|
55
|
+
value: undefined
|
|
43
56
|
}
|
|
44
57
|
};
|
|
45
58
|
}
|
|
@@ -103,6 +116,7 @@ export const DelegateFocusMixin = dedupingMixin(
|
|
|
103
116
|
if (element) {
|
|
104
117
|
element.disabled = this.disabled;
|
|
105
118
|
this._addFocusListeners(element);
|
|
119
|
+
this.__forwardTabIndex(this.tabindex);
|
|
106
120
|
} else if (oldElement) {
|
|
107
121
|
this._removeFocusListeners(oldElement);
|
|
108
122
|
}
|
|
@@ -162,10 +176,12 @@ export const DelegateFocusMixin = dedupingMixin(
|
|
|
162
176
|
|
|
163
177
|
/**
|
|
164
178
|
* @param {boolean} disabled
|
|
179
|
+
* @param {boolean} oldDisabled
|
|
165
180
|
* @protected
|
|
181
|
+
* @override
|
|
166
182
|
*/
|
|
167
|
-
_disabledChanged(disabled) {
|
|
168
|
-
super._disabledChanged(disabled);
|
|
183
|
+
_disabledChanged(disabled, oldDisabled) {
|
|
184
|
+
super._disabledChanged(disabled, oldDisabled);
|
|
169
185
|
|
|
170
186
|
if (this.focusElement) {
|
|
171
187
|
this.focusElement.disabled = disabled;
|
|
@@ -175,5 +191,37 @@ export const DelegateFocusMixin = dedupingMixin(
|
|
|
175
191
|
this.blur();
|
|
176
192
|
}
|
|
177
193
|
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Override an observer from `TabindexMixin`.
|
|
197
|
+
* Do not call super to remove tabindex attribute
|
|
198
|
+
* from the host after it has been forwarded.
|
|
199
|
+
* @param {string} tabindex
|
|
200
|
+
* @protected
|
|
201
|
+
* @override
|
|
202
|
+
*/
|
|
203
|
+
_tabindexChanged(tabindex) {
|
|
204
|
+
this.__forwardTabIndex(tabindex);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** @private */
|
|
208
|
+
__forwardTabIndex(tabindex) {
|
|
209
|
+
if (tabindex !== undefined && this.focusElement) {
|
|
210
|
+
this.focusElement.tabIndex = tabindex;
|
|
211
|
+
|
|
212
|
+
// Preserve tabindex="-1" on the host element
|
|
213
|
+
if (tabindex !== -1) {
|
|
214
|
+
this.tabindex = undefined;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (this.disabled && tabindex) {
|
|
219
|
+
// If tabindex attribute was changed while component was disabled
|
|
220
|
+
if (tabindex !== -1) {
|
|
221
|
+
this.__lastTabIndex = tabindex;
|
|
222
|
+
}
|
|
223
|
+
this.tabindex = undefined;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
178
226
|
}
|
|
179
227
|
);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2021 - 2022 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 error message node content.
|
|
10
|
+
*/
|
|
11
|
+
export class ErrorController extends SlotController {
|
|
12
|
+
/**
|
|
13
|
+
* ID attribute value set on the error message element.
|
|
14
|
+
*/
|
|
15
|
+
readonly errorId: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* String used for the error message text content.
|
|
19
|
+
*/
|
|
20
|
+
protected errorMessage: string | null | undefined;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Set to true when the host element is invalid.
|
|
24
|
+
*/
|
|
25
|
+
protected invalid: boolean;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Set the error message element text content.
|
|
29
|
+
*/
|
|
30
|
+
setErrorMessage(errorMessage: string | null | undefined): void;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Set invalid state for detecting whether to show error message.
|
|
34
|
+
*/
|
|
35
|
+
setInvalid(invalid: boolean): void;
|
|
36
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2021 - 2022 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 error message node content.
|
|
10
|
+
*/
|
|
11
|
+
export class ErrorController extends SlotController {
|
|
12
|
+
constructor(host) {
|
|
13
|
+
super(
|
|
14
|
+
host,
|
|
15
|
+
'error-message',
|
|
16
|
+
() => document.createElement('div'),
|
|
17
|
+
(_host, node) => {
|
|
18
|
+
this.__updateErrorId(node);
|
|
19
|
+
|
|
20
|
+
this.__updateHasError();
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* ID attribute value set on the error message element.
|
|
27
|
+
*
|
|
28
|
+
* @return {string}
|
|
29
|
+
*/
|
|
30
|
+
get errorId() {
|
|
31
|
+
return this.node && this.node.id;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Set the error message element text content.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} errorMessage
|
|
38
|
+
*/
|
|
39
|
+
setErrorMessage(errorMessage) {
|
|
40
|
+
this.errorMessage = errorMessage;
|
|
41
|
+
|
|
42
|
+
this.__updateHasError();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Set invalid state for detecting whether to show error message.
|
|
47
|
+
*
|
|
48
|
+
* @param {boolean} invalid
|
|
49
|
+
*/
|
|
50
|
+
setInvalid(invalid) {
|
|
51
|
+
this.invalid = invalid;
|
|
52
|
+
|
|
53
|
+
this.__updateHasError();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Override to initialize the newly added custom label.
|
|
58
|
+
*
|
|
59
|
+
* @param {Node} errorNode
|
|
60
|
+
* @protected
|
|
61
|
+
* @override
|
|
62
|
+
*/
|
|
63
|
+
initCustomNode(errorNode) {
|
|
64
|
+
this.__updateErrorId(errorNode);
|
|
65
|
+
|
|
66
|
+
// Save the custom error message content on the host.
|
|
67
|
+
if (errorNode.textContent && !this.errorMessage) {
|
|
68
|
+
this.errorMessage = errorNode.textContent.trim();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.__updateHasError();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Override to cleanup label node when it's removed.
|
|
76
|
+
*
|
|
77
|
+
* @param {Node} node
|
|
78
|
+
* @protected
|
|
79
|
+
* @override
|
|
80
|
+
*/
|
|
81
|
+
teardownNode(node) {
|
|
82
|
+
let errorNode = this.getSlotChild();
|
|
83
|
+
|
|
84
|
+
// If custom error was removed, restore the default one.
|
|
85
|
+
if (!errorNode && node !== this.defaultNode) {
|
|
86
|
+
errorNode = this.attachDefaultNode();
|
|
87
|
+
|
|
88
|
+
// Run initializer to update default label and ID.
|
|
89
|
+
this.initNode(errorNode);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.__updateHasError();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @param {string} error
|
|
97
|
+
* @private
|
|
98
|
+
*/
|
|
99
|
+
__isNotEmpty(error) {
|
|
100
|
+
return Boolean(error && error.trim() !== '');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** @private */
|
|
104
|
+
__updateHasError() {
|
|
105
|
+
const errorNode = this.node;
|
|
106
|
+
const hasError = Boolean(this.invalid && this.__isNotEmpty(this.errorMessage));
|
|
107
|
+
|
|
108
|
+
// Update both default and custom error message node.
|
|
109
|
+
if (errorNode) {
|
|
110
|
+
errorNode.textContent = hasError ? this.errorMessage : '';
|
|
111
|
+
errorNode.hidden = !hasError;
|
|
112
|
+
|
|
113
|
+
// Role alert will make the error message announce immediately
|
|
114
|
+
// as the field becomes invalid
|
|
115
|
+
if (hasError) {
|
|
116
|
+
errorNode.setAttribute('role', 'alert');
|
|
117
|
+
} else {
|
|
118
|
+
errorNode.removeAttribute('role');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.host.toggleAttribute('has-error-message', hasError);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {HTMLElement} errorNode
|
|
127
|
+
* @private
|
|
128
|
+
*/
|
|
129
|
+
__updateErrorId(errorNode) {
|
|
130
|
+
if (!errorNode.id) {
|
|
131
|
+
errorNode.id = this.defaultId;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
package/src/field-mixin.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { animationFrame } from '@vaadin/component-base/src/async.js';
|
|
7
7
|
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
|
|
8
8
|
import { Debouncer } from '@vaadin/component-base/src/debounce.js';
|
|
9
|
-
import {
|
|
9
|
+
import { ErrorController } from './error-controller.js';
|
|
10
10
|
import { FieldAriaController } from './field-aria-controller.js';
|
|
11
11
|
import { HelperController } from './helper-controller.js';
|
|
12
12
|
import { LabelMixin } from './label-mixin.js';
|
|
@@ -18,11 +18,10 @@ import { ValidateMixin } from './validate-mixin.js';
|
|
|
18
18
|
* @polymerMixin
|
|
19
19
|
* @mixes ControllerMixin
|
|
20
20
|
* @mixes LabelMixin
|
|
21
|
-
* @mixes SlotMixin
|
|
22
21
|
* @mixes ValidateMixin
|
|
23
22
|
*/
|
|
24
23
|
export const FieldMixin = (superclass) =>
|
|
25
|
-
class FieldMixinClass extends ValidateMixin(LabelMixin(ControllerMixin(
|
|
24
|
+
class FieldMixinClass extends ValidateMixin(LabelMixin(ControllerMixin(superclass))) {
|
|
26
25
|
static get properties() {
|
|
27
26
|
return {
|
|
28
27
|
/**
|
|
@@ -40,7 +39,8 @@ export const FieldMixin = (superclass) =>
|
|
|
40
39
|
* @attr {string} error-message
|
|
41
40
|
*/
|
|
42
41
|
errorMessage: {
|
|
43
|
-
type: String
|
|
42
|
+
type: String,
|
|
43
|
+
observer: '_errorMessageChanged'
|
|
44
44
|
},
|
|
45
45
|
|
|
46
46
|
/**
|
|
@@ -57,33 +57,25 @@ export const FieldMixin = (superclass) =>
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
/** @protected */
|
|
61
|
-
get slots() {
|
|
62
|
-
return {
|
|
63
|
-
...super.slots,
|
|
64
|
-
'error-message': () => {
|
|
65
|
-
const error = document.createElement('div');
|
|
66
|
-
error.textContent = this.errorMessage;
|
|
67
|
-
return error;
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
60
|
static get observers() {
|
|
73
61
|
return [
|
|
74
62
|
'__observeOffsetHeight(errorMessage, invalid, label, helperText)',
|
|
75
|
-
'_updateErrorMessage(invalid, errorMessage)',
|
|
76
63
|
'_invalidChanged(invalid)',
|
|
77
64
|
'_requiredChanged(required)'
|
|
78
65
|
];
|
|
79
66
|
}
|
|
80
67
|
|
|
68
|
+
/** @protected */
|
|
69
|
+
get _errorId() {
|
|
70
|
+
return this._errorController.errorId;
|
|
71
|
+
}
|
|
72
|
+
|
|
81
73
|
/**
|
|
82
74
|
* @protected
|
|
83
75
|
* @return {HTMLElement}
|
|
84
76
|
*/
|
|
85
77
|
get _errorNode() {
|
|
86
|
-
return this.
|
|
78
|
+
return this._errorController.node;
|
|
87
79
|
}
|
|
88
80
|
|
|
89
81
|
/** @protected */
|
|
@@ -102,15 +94,13 @@ export const FieldMixin = (superclass) =>
|
|
|
102
94
|
constructor() {
|
|
103
95
|
super();
|
|
104
96
|
|
|
105
|
-
// Ensure every instance has unique ID
|
|
106
|
-
const uniqueId = (FieldMixinClass._uniqueFieldId = 1 + FieldMixinClass._uniqueFieldId || 0);
|
|
107
|
-
this._errorId = `error-${this.localName}-${uniqueId}`;
|
|
108
|
-
|
|
109
97
|
this._fieldAriaController = new FieldAriaController(this);
|
|
110
98
|
this._helperController = new HelperController(this);
|
|
99
|
+
this._errorController = new ErrorController(this);
|
|
111
100
|
|
|
112
101
|
this.addController(this._fieldAriaController);
|
|
113
102
|
this.addController(this._helperController);
|
|
103
|
+
this.addController(this._errorController);
|
|
114
104
|
|
|
115
105
|
this._labelController.addEventListener('label-changed', (event) => {
|
|
116
106
|
const { hasLabel, node } = event.detail;
|
|
@@ -123,29 +113,6 @@ export const FieldMixin = (superclass) =>
|
|
|
123
113
|
});
|
|
124
114
|
}
|
|
125
115
|
|
|
126
|
-
/** @protected */
|
|
127
|
-
ready() {
|
|
128
|
-
super.ready();
|
|
129
|
-
|
|
130
|
-
const error = this._errorNode;
|
|
131
|
-
if (error) {
|
|
132
|
-
error.id = this._errorId;
|
|
133
|
-
|
|
134
|
-
this.__applyCustomError();
|
|
135
|
-
|
|
136
|
-
this._updateErrorMessage(this.invalid, this.errorMessage);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** @private */
|
|
141
|
-
__applyCustomError() {
|
|
142
|
-
const error = this.__errorMessage;
|
|
143
|
-
if (error && error !== this.errorMessage) {
|
|
144
|
-
this.errorMessage = error;
|
|
145
|
-
delete this.__errorMessage;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
116
|
/** @private */
|
|
150
117
|
__helperChanged(hasHelper, helperNode) {
|
|
151
118
|
if (hasHelper) {
|
|
@@ -192,32 +159,11 @@ export const FieldMixin = (superclass) =>
|
|
|
192
159
|
}
|
|
193
160
|
|
|
194
161
|
/**
|
|
195
|
-
* @param {boolean} invalid
|
|
196
162
|
* @param {string | null | undefined} errorMessage
|
|
197
163
|
* @protected
|
|
198
164
|
*/
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (!error) {
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// save the custom error message content
|
|
206
|
-
if (error.textContent && !errorMessage) {
|
|
207
|
-
this.__errorMessage = error.textContent.trim();
|
|
208
|
-
}
|
|
209
|
-
const hasError = Boolean(invalid && errorMessage);
|
|
210
|
-
error.textContent = hasError ? errorMessage : '';
|
|
211
|
-
error.hidden = !hasError;
|
|
212
|
-
this.toggleAttribute('has-error-message', hasError);
|
|
213
|
-
|
|
214
|
-
// Role alert will make the error message announce immediately
|
|
215
|
-
// as the field becomes invalid
|
|
216
|
-
if (hasError) {
|
|
217
|
-
error.setAttribute('role', 'alert');
|
|
218
|
-
} else {
|
|
219
|
-
error.removeAttribute('role');
|
|
220
|
-
}
|
|
165
|
+
_errorMessageChanged(errorMessage) {
|
|
166
|
+
this._errorController.setErrorMessage(errorMessage);
|
|
221
167
|
}
|
|
222
168
|
|
|
223
169
|
/**
|
|
@@ -251,6 +197,8 @@ export const FieldMixin = (superclass) =>
|
|
|
251
197
|
* @protected
|
|
252
198
|
*/
|
|
253
199
|
_invalidChanged(invalid) {
|
|
200
|
+
this._errorController.setInvalid(invalid);
|
|
201
|
+
|
|
254
202
|
// This timeout is needed to prevent NVDA from announcing the error message twice:
|
|
255
203
|
// 1. Once adding the `[role=alert]` attribute by the `_updateErrorMessage` method (OK).
|
|
256
204
|
// 2. Once linking the error ID with the ARIA target here (unwanted).
|
|
@@ -259,7 +207,7 @@ export const FieldMixin = (superclass) =>
|
|
|
259
207
|
// Error message ID needs to be dynamically added / removed based on the validity
|
|
260
208
|
// Otherwise assistive technologies would announce the error, even if we hide it.
|
|
261
209
|
if (invalid) {
|
|
262
|
-
this._fieldAriaController.setErrorId(this.
|
|
210
|
+
this._fieldAriaController.setErrorId(this._errorController.errorId);
|
|
263
211
|
} else {
|
|
264
212
|
this._fieldAriaController.setErrorId(null);
|
|
265
213
|
}
|
|
@@ -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,24 @@ 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
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Fired when the user commits a value change.
|
|
176
|
+
*
|
|
177
|
+
* @event change
|
|
178
|
+
*/
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Fired when the value is changed by the user: on every typing keystroke,
|
|
182
|
+
* and the value is cleared using the clear button.
|
|
183
|
+
*
|
|
184
|
+
* @event input
|
|
185
|
+
*/
|
|
170
186
|
};
|
package/src/input-field-mixin.js
CHANGED
|
@@ -120,6 +120,21 @@ export const InputFieldMixin = (superclass) =>
|
|
|
120
120
|
this.validate();
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Override an event listener from `InputMixin`
|
|
125
|
+
* to mark as valid after user started typing.
|
|
126
|
+
* @param {Event} event
|
|
127
|
+
* @protected
|
|
128
|
+
* @override
|
|
129
|
+
*/
|
|
130
|
+
_onInput(event) {
|
|
131
|
+
super._onInput(event);
|
|
132
|
+
|
|
133
|
+
if (this.invalid) {
|
|
134
|
+
this.validate();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
123
138
|
/**
|
|
124
139
|
* Override a method from `InputMixin` to validate the field
|
|
125
140
|
* when a new value is set programmatically.
|
package/src/label-controller.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
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';
|
|
7
|
-
import { TabindexMixin } from '@vaadin/component-base/src/tabindex-mixin.js';
|
|
8
7
|
import { DelegateFocusMixin } from './delegate-focus-mixin.js';
|
|
9
8
|
|
|
10
9
|
/**
|
|
@@ -13,10 +12,23 @@ import { DelegateFocusMixin } from './delegate-focus-mixin.js';
|
|
|
13
12
|
* @polymerMixin
|
|
14
13
|
* @mixes DelegateFocusMixin
|
|
15
14
|
* @mixes KeyboardMixin
|
|
16
|
-
* @mixes TabindexMixin
|
|
17
15
|
*/
|
|
18
16
|
export const ShadowFocusMixin = (superClass) =>
|
|
19
|
-
class ShadowFocusMixinClass extends
|
|
17
|
+
class ShadowFocusMixinClass extends DelegateFocusMixin(KeyboardMixin(superClass)) {
|
|
18
|
+
static get properties() {
|
|
19
|
+
return {
|
|
20
|
+
/**
|
|
21
|
+
* Indicates whether the element can be focused and where it participates in sequential keyboard navigation.
|
|
22
|
+
*
|
|
23
|
+
* @protected
|
|
24
|
+
*/
|
|
25
|
+
tabindex: {
|
|
26
|
+
type: Number,
|
|
27
|
+
value: 0
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
20
32
|
/**
|
|
21
33
|
* Override an event listener from `KeyboardMixin`
|
|
22
34
|
* to prevent setting `focused` on Shift Tab.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
import { ReactiveController } from 'lit';
|
|
7
|
+
|
|
8
|
+
export class SlotTargetController implements ReactiveController {
|
|
9
|
+
constructor(
|
|
10
|
+
sourceSlot: HTMLSlotElement,
|
|
11
|
+
targetFactory: () => HTMLElement,
|
|
12
|
+
copyCallback?: (nodes: HTMLElement[]) => void
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
hostConnected(): void;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The source `<slot>` element to copy nodes from.
|
|
19
|
+
*/
|
|
20
|
+
protected sourceSlot: HTMLSlotElement;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Function used to get a reference to slot target.
|
|
24
|
+
*/
|
|
25
|
+
protected targetFactory: () => HTMLElement;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Function called after copying nodes to target.
|
|
29
|
+
*/
|
|
30
|
+
protected copyCallback?: (nodes: HTMLElement[]) => void;
|
|
31
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A controller to copy the content from a source slot to a target element.
|
|
9
|
+
*/
|
|
10
|
+
export class SlotTargetController {
|
|
11
|
+
constructor(sourceSlot, targetFactory, callback) {
|
|
12
|
+
/**
|
|
13
|
+
* The source `<slot>` element to copy nodes from.
|
|
14
|
+
*/
|
|
15
|
+
this.sourceSlot = sourceSlot;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Function used to get a reference to slot target.
|
|
19
|
+
*/
|
|
20
|
+
this.targetFactory = targetFactory;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Function called after copying nodes to target.
|
|
24
|
+
*/
|
|
25
|
+
this.copyCallback = callback;
|
|
26
|
+
|
|
27
|
+
if (sourceSlot) {
|
|
28
|
+
sourceSlot.addEventListener('slotchange', () => {
|
|
29
|
+
// Copy in progress, ignore this event.
|
|
30
|
+
if (this.__copying) {
|
|
31
|
+
this.__copying = false;
|
|
32
|
+
} else {
|
|
33
|
+
this.__checkAndCopyNodesToSlotTarget();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
hostConnected() {
|
|
40
|
+
this.__sourceSlotObserver = new MutationObserver(() => this.__checkAndCopyNodesToSlotTarget());
|
|
41
|
+
|
|
42
|
+
// Ensure the content is up to date when host is connected
|
|
43
|
+
// to handle e.g. mutating text content while disconnected.
|
|
44
|
+
this.__checkAndCopyNodesToSlotTarget();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Copies every node from the source slot to the target element
|
|
49
|
+
* once the source slot' content is changed.
|
|
50
|
+
*
|
|
51
|
+
* @private
|
|
52
|
+
*/
|
|
53
|
+
__checkAndCopyNodesToSlotTarget() {
|
|
54
|
+
this.__sourceSlotObserver.disconnect();
|
|
55
|
+
|
|
56
|
+
// Ensure slot target element is up to date.
|
|
57
|
+
const slotTarget = this.targetFactory();
|
|
58
|
+
|
|
59
|
+
if (!slotTarget) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Remove any existing clones from the slot target
|
|
64
|
+
if (this.__slotTargetClones) {
|
|
65
|
+
this.__slotTargetClones.forEach((node) => {
|
|
66
|
+
if (node.parentElement === slotTarget) {
|
|
67
|
+
slotTarget.removeChild(node);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
delete this.__slotTargetClones;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Exclude whitespace text nodes
|
|
74
|
+
const nodes = this.sourceSlot
|
|
75
|
+
.assignedNodes({ flatten: true })
|
|
76
|
+
.filter((node) => !(node.nodeType == Node.TEXT_NODE && node.textContent.trim() === ''));
|
|
77
|
+
|
|
78
|
+
if (nodes.length > 0) {
|
|
79
|
+
slotTarget.innerHTML = '';
|
|
80
|
+
|
|
81
|
+
// Ignore next slotchange
|
|
82
|
+
this.__copying = true;
|
|
83
|
+
|
|
84
|
+
this.__copyNodesToSlotTarget(nodes, slotTarget);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Copies the nodes to the target element.
|
|
90
|
+
*
|
|
91
|
+
* @param {!Array<!Node>} nodes
|
|
92
|
+
* @param {HTMLElement} slotTarget
|
|
93
|
+
* @private
|
|
94
|
+
*/
|
|
95
|
+
__copyNodesToSlotTarget(nodes, slotTarget) {
|
|
96
|
+
this.__slotTargetClones = this.__slotTargetClones || [];
|
|
97
|
+
|
|
98
|
+
nodes.forEach((node) => {
|
|
99
|
+
// Clone the nodes and append the clones to the target
|
|
100
|
+
const clone = node.cloneNode(true);
|
|
101
|
+
this.__slotTargetClones.push(clone);
|
|
102
|
+
|
|
103
|
+
slotTarget.appendChild(clone);
|
|
104
|
+
|
|
105
|
+
// Observe all changes to the source node to have the clones updated
|
|
106
|
+
this.__sourceSlotObserver.observe(node, {
|
|
107
|
+
attributes: true,
|
|
108
|
+
childList: true,
|
|
109
|
+
subtree: true,
|
|
110
|
+
characterData: true
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Run callback e.g. to show a deprecation warning
|
|
115
|
+
if (typeof this.copyCallback === 'function') {
|
|
116
|
+
this.copyCallback(nodes);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
|
-
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
-
*/
|
|
6
|
-
import { Constructor } from '@open-wc/dedupe-mixin';
|
|
7
|
-
import { LabelMixinClass } from './label-mixin.js';
|
|
8
|
-
import { SlotTargetMixinClass } from './slot-target-mixin.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* A mixin to forward any content from the default slot to the label node.
|
|
12
|
-
*/
|
|
13
|
-
export declare function SlotLabelMixin<T extends Constructor<HTMLElement>>(
|
|
14
|
-
base: T
|
|
15
|
-
): T & Constructor<LabelMixinClass> & Constructor<SlotTargetMixinClass>;
|
package/src/slot-label-mixin.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
|
-
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
-
*/
|
|
6
|
-
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
|
|
7
|
-
import { LabelMixin } from './label-mixin.js';
|
|
8
|
-
import { SlotTargetMixin } from './slot-target-mixin.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* A mixin to forward any content from the default slot to the label node.
|
|
12
|
-
*
|
|
13
|
-
* @polymerMixin
|
|
14
|
-
* @mixes LabelMixin
|
|
15
|
-
* @mixes SlotTargetMixin
|
|
16
|
-
*/
|
|
17
|
-
export const SlotLabelMixin = dedupingMixin(
|
|
18
|
-
(superclass) =>
|
|
19
|
-
class SlotLabelMixinClass extends SlotTargetMixin(LabelMixin(superclass)) {
|
|
20
|
-
/** @protected */
|
|
21
|
-
get _slotTarget() {
|
|
22
|
-
return this._labelNode;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
);
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
|
-
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
-
*/
|
|
6
|
-
import { Constructor } from '@open-wc/dedupe-mixin';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* A mixin to copy the content from a source slot to a target element.
|
|
10
|
-
*/
|
|
11
|
-
export declare function SlotTargetMixin<T extends Constructor<HTMLElement>>(
|
|
12
|
-
base: T
|
|
13
|
-
): T & Constructor<SlotTargetMixinClass>;
|
|
14
|
-
|
|
15
|
-
export declare class SlotTargetMixinClass {
|
|
16
|
-
/**
|
|
17
|
-
* A reference to the source slot from which the content is copied to the target element.
|
|
18
|
-
*/
|
|
19
|
-
protected readonly _sourceSlot: HTMLSlotElement;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* A reference to the target element to which the content is copied from the source slot.
|
|
23
|
-
*/
|
|
24
|
-
protected readonly _slotTarget: HTMLElement;
|
|
25
|
-
}
|
package/src/slot-target-mixin.js
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
|
-
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
-
*/
|
|
6
|
-
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* A mixin to copy the content from a source slot to a target element.
|
|
10
|
-
*
|
|
11
|
-
* @polymerMixin
|
|
12
|
-
*/
|
|
13
|
-
export const SlotTargetMixin = dedupingMixin(
|
|
14
|
-
(superclass) =>
|
|
15
|
-
class SlotTargetMixinClass extends superclass {
|
|
16
|
-
/** @protected */
|
|
17
|
-
ready() {
|
|
18
|
-
super.ready();
|
|
19
|
-
|
|
20
|
-
if (this._sourceSlot) {
|
|
21
|
-
this.__sourceSlotObserver = new MutationObserver(() => this.__checkAndCopyNodesToSlotTarget());
|
|
22
|
-
|
|
23
|
-
this.__checkAndCopyNodesToSlotTarget();
|
|
24
|
-
|
|
25
|
-
this._sourceSlot.addEventListener('slotchange', () => {
|
|
26
|
-
this.__checkAndCopyNodesToSlotTarget();
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* A reference to the source slot from which the nodes are copied to the target element.
|
|
33
|
-
*
|
|
34
|
-
* @type {HTMLSlotElement | null}
|
|
35
|
-
* @protected
|
|
36
|
-
*/
|
|
37
|
-
get _sourceSlot() {
|
|
38
|
-
console.warn(`Please implement the '_sourceSlot' property in <${this.localName}>`);
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* A reference to the target element to which the nodes are copied from the source slot.
|
|
44
|
-
*
|
|
45
|
-
* @type {HTMLElement | null}
|
|
46
|
-
* @protected
|
|
47
|
-
*/
|
|
48
|
-
get _slotTarget() {
|
|
49
|
-
console.warn(`Please implement the '_slotTarget' property in <${this.localName}>`);
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Copies every node from the source slot to the target element
|
|
55
|
-
* once the source slot' content is changed.
|
|
56
|
-
*
|
|
57
|
-
* @private
|
|
58
|
-
*/
|
|
59
|
-
__checkAndCopyNodesToSlotTarget() {
|
|
60
|
-
this.__sourceSlotObserver.disconnect();
|
|
61
|
-
|
|
62
|
-
if (!this._slotTarget) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Remove any existing clones from the slot target
|
|
67
|
-
if (this.__slotTargetClones) {
|
|
68
|
-
this.__slotTargetClones.forEach((node) => {
|
|
69
|
-
if (node.parentElement === this._slotTarget) {
|
|
70
|
-
this._slotTarget.removeChild(node);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
delete this.__slotTargetClones;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Exclude whitespace text nodes
|
|
77
|
-
const nodes = this._sourceSlot
|
|
78
|
-
.assignedNodes({ flatten: true })
|
|
79
|
-
.filter((node) => !(node.nodeType == Node.TEXT_NODE && node.textContent.trim() === ''));
|
|
80
|
-
|
|
81
|
-
if (nodes.length > 0) {
|
|
82
|
-
this._slotTarget.innerHTML = '';
|
|
83
|
-
this.__copyNodesToSlotTarget(nodes);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Copies the nodes to the target element.
|
|
89
|
-
*
|
|
90
|
-
* @protected
|
|
91
|
-
* @param {!Array<!Node>} nodes
|
|
92
|
-
*/
|
|
93
|
-
__copyNodesToSlotTarget(nodes) {
|
|
94
|
-
this.__slotTargetClones = this.__slotTargetClones || [];
|
|
95
|
-
nodes.forEach((node) => {
|
|
96
|
-
// Clone the nodes and append the clones to the target slot
|
|
97
|
-
const clone = node.cloneNode(true);
|
|
98
|
-
this.__slotTargetClones.push(clone);
|
|
99
|
-
this._slotTarget.appendChild(clone);
|
|
100
|
-
// Observe all changes to the source node to have the clones updated
|
|
101
|
-
this.__sourceSlotObserver.observe(node, {
|
|
102
|
-
attributes: true,
|
|
103
|
-
childList: true,
|
|
104
|
-
subtree: true,
|
|
105
|
-
characterData: true
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
);
|