@vaadin/field-base 24.0.0-alpha4 → 24.0.0-alpha6

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/index.d.ts CHANGED
@@ -10,7 +10,6 @@ export { InputMixin } from './src/input-mixin.js';
10
10
  export { LabelledInputController } from './src/labelled-input-controller.js';
11
11
  export { LabelMixin } from './src/label-mixin.js';
12
12
  export { PatternMixin } from './src/pattern-mixin.js';
13
- export { ShadowFocusMixin } from './src/shadow-focus-mixin.js';
14
13
  export { SlotStylesMixin } from './src/slot-styles-mixin.js';
15
14
  export { TextAreaController } from './src/text-area-controller.js';
16
15
  export { ValidateMixin } from './src/validate-mixin.js';
package/index.js CHANGED
@@ -10,7 +10,6 @@ export { InputMixin } from './src/input-mixin.js';
10
10
  export { LabelledInputController } from './src/labelled-input-controller.js';
11
11
  export { LabelMixin } from './src/label-mixin.js';
12
12
  export { PatternMixin } from './src/pattern-mixin.js';
13
- export { ShadowFocusMixin } from './src/shadow-focus-mixin.js';
14
13
  export { SlotStylesMixin } from './src/slot-styles-mixin.js';
15
14
  export { TextAreaController } from './src/text-area-controller.js';
16
15
  export { ValidateMixin } from './src/validate-mixin.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/field-base",
3
- "version": "24.0.0-alpha4",
3
+ "version": "24.0.0-alpha6",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -32,7 +32,7 @@
32
32
  "dependencies": {
33
33
  "@open-wc/dedupe-mixin": "^1.3.0",
34
34
  "@polymer/polymer": "^3.0.0",
35
- "@vaadin/component-base": "24.0.0-alpha4",
35
+ "@vaadin/component-base": "24.0.0-alpha6",
36
36
  "lit": "^2.0.0"
37
37
  },
38
38
  "devDependencies": {
@@ -40,5 +40,5 @@
40
40
  "@vaadin/testing-helpers": "^0.3.2",
41
41
  "sinon": "^13.0.2"
42
42
  },
43
- "gitHead": "66be46e82c4d0a673859fbc9bdb1581dd89f360c"
43
+ "gitHead": "0004ac92b6e5f415b5fa949e0582d1d11e527b1f"
44
44
  }
@@ -9,11 +9,6 @@ import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
9
9
  * A controller that manages the error message node content.
10
10
  */
11
11
  export class ErrorController extends SlotController {
12
- /**
13
- * ID attribute value set on the error message element.
14
- */
15
- readonly errorId: string;
16
-
17
12
  /**
18
13
  * String used for the error message text content.
19
14
  */
@@ -3,30 +3,14 @@
3
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 '@vaadin/component-base/src/slot-controller.js';
6
+ import { SlotObserveController } from '@vaadin/component-base/src/slot-observe-controller.js';
7
7
 
8
8
  /**
9
9
  * A controller that manages the error message node content.
10
10
  */
11
- export class ErrorController extends SlotController {
11
+ export class ErrorController extends SlotObserveController {
12
12
  constructor(host) {
13
- super(host, 'error-message', 'div', {
14
- initializer: (node) => {
15
- this.__updateErrorId(node);
16
-
17
- this.__updateHasError();
18
- },
19
- useUniqueId: true,
20
- });
21
- }
22
-
23
- /**
24
- * ID attribute value set on the error message element.
25
- *
26
- * @return {string}
27
- */
28
- get errorId() {
29
- return this.node && this.node.id;
13
+ super(host, 'error-message', 'div');
30
14
  }
31
15
 
32
16
  /**
@@ -37,7 +21,7 @@ export class ErrorController extends SlotController {
37
21
  setErrorMessage(errorMessage) {
38
22
  this.errorMessage = errorMessage;
39
23
 
40
- this.__updateHasError();
24
+ this.updateDefaultNode(this.node);
41
25
  }
42
26
 
43
27
  /**
@@ -48,7 +32,33 @@ export class ErrorController extends SlotController {
48
32
  setInvalid(invalid) {
49
33
  this.invalid = invalid;
50
34
 
51
- this.__updateHasError();
35
+ this.updateDefaultNode(this.node);
36
+ }
37
+
38
+ /**
39
+ * Override method inherited from `SlotController` to not run
40
+ * initializer on the custom slotted node unnecessarily.
41
+ *
42
+ * @param {Node} node
43
+ * @protected
44
+ * @override
45
+ */
46
+ initAddedNode(node) {
47
+ if (node !== this.defaultNode) {
48
+ // There is no need to run `initNode`.
49
+ this.initCustomNode(node);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Override to initialize the newly added default error message.
55
+ *
56
+ * @param {Node} errorNode
57
+ * @protected
58
+ * @override
59
+ */
60
+ initNode(errorNode) {
61
+ this.updateDefaultNode(errorNode);
52
62
  }
53
63
 
54
64
  /**
@@ -59,53 +69,43 @@ export class ErrorController extends SlotController {
59
69
  * @override
60
70
  */
61
71
  initCustomNode(errorNode) {
62
- this.__updateErrorId(errorNode);
63
-
64
72
  // Save the custom error message content on the host.
65
73
  if (errorNode.textContent && !this.errorMessage) {
66
74
  this.errorMessage = errorNode.textContent.trim();
67
75
  }
68
76
 
69
- this.__updateHasError();
77
+ // Notify the host about custom node.
78
+ super.initCustomNode(errorNode);
70
79
  }
71
80
 
72
81
  /**
73
- * Override to cleanup error message node when it's removed.
82
+ * Override method inherited from `SlotObserveController`
83
+ * to restore and the default error message element.
74
84
  *
75
- * @param {Node} node
76
85
  * @protected
77
86
  * @override
78
87
  */
79
- teardownNode(node) {
80
- let errorNode = this.getSlotChild();
81
-
82
- // If custom error was removed, restore the default one.
83
- if (!errorNode && node !== this.defaultNode) {
84
- errorNode = this.attachDefaultNode();
85
-
86
- // Run initializer to update default error message ID.
87
- this.initNode(errorNode);
88
- }
89
-
90
- this.__updateHasError();
88
+ restoreDefaultNode() {
89
+ this.attachDefaultNode();
91
90
  }
92
91
 
93
92
  /**
94
- * @param {string} error
95
- * @private
93
+ * Override method inherited from `SlotObserveController`
94
+ * to update the error message text and hidden state.
95
+ *
96
+ * Note: unlike with other controllers, this method is
97
+ * called for both default and custom error message.
98
+ *
99
+ * @param {Node | undefined} node
100
+ * @protected
101
+ * @override
96
102
  */
97
- __isNotEmpty(error) {
98
- return Boolean(error && error.trim() !== '');
99
- }
100
-
101
- /** @private */
102
- __updateHasError() {
103
- const errorNode = this.node;
104
- const hasError = Boolean(this.invalid && this.__isNotEmpty(this.errorMessage));
103
+ updateDefaultNode(errorNode) {
104
+ const { errorMessage, invalid } = this;
105
+ const hasError = Boolean(invalid && errorMessage && errorMessage.trim() !== '');
105
106
 
106
- // Update both default and custom error message node.
107
107
  if (errorNode) {
108
- errorNode.textContent = hasError ? this.errorMessage : '';
108
+ errorNode.textContent = hasError ? errorMessage : '';
109
109
  errorNode.hidden = !hasError;
110
110
 
111
111
  // Role alert will make the error message announce immediately
@@ -117,16 +117,7 @@ export class ErrorController extends SlotController {
117
117
  }
118
118
  }
119
119
 
120
- this.host.toggleAttribute('has-error-message', hasError);
121
- }
122
-
123
- /**
124
- * @param {HTMLElement} errorNode
125
- * @private
126
- */
127
- __updateErrorId(errorNode) {
128
- if (!errorNode.id) {
129
- errorNode.id = this.defaultId;
130
- }
120
+ // Notify the host after update.
121
+ super.updateDefaultNode(errorNode);
131
122
  }
132
123
  }
@@ -56,11 +56,6 @@ export const FieldMixin = (superclass) =>
56
56
  return ['_invalidChanged(invalid)', '_requiredChanged(required)'];
57
57
  }
58
58
 
59
- /** @protected */
60
- get _errorId() {
61
- return this._errorController.errorId;
62
- }
63
-
64
59
  /**
65
60
  * @protected
66
61
  * @return {HTMLElement}
@@ -69,11 +64,6 @@ export const FieldMixin = (superclass) =>
69
64
  return this._errorController.node;
70
65
  }
71
66
 
72
- /** @protected */
73
- get _helperId() {
74
- return this._helperController.helperId;
75
- }
76
-
77
67
  /**
78
68
  * @protected
79
69
  * @return {HTMLElement}
@@ -89,14 +79,19 @@ export const FieldMixin = (superclass) =>
89
79
  this._helperController = new HelperController(this);
90
80
  this._errorController = new ErrorController(this);
91
81
 
92
- this._labelController.addEventListener('label-changed', (event) => {
93
- const { hasLabel, node } = event.detail;
94
- this.__labelChanged(hasLabel, node);
82
+ this._errorController.addEventListener('slot-content-changed', (event) => {
83
+ this.toggleAttribute('has-error-message', event.detail.hasContent);
84
+ });
85
+
86
+ this._labelController.addEventListener('slot-content-changed', (event) => {
87
+ const { hasContent, node } = event.detail;
88
+ this.__labelChanged(hasContent, node);
95
89
  });
96
90
 
97
- this._helperController.addEventListener('helper-changed', (event) => {
98
- const { hasHelper, node } = event.detail;
99
- this.__helperChanged(hasHelper, node);
91
+ this._helperController.addEventListener('slot-content-changed', (event) => {
92
+ const { hasContent, node } = event.detail;
93
+ this.toggleAttribute('has-helper', hasContent);
94
+ this.__helperChanged(hasContent, node);
100
95
  });
101
96
  }
102
97
 
@@ -178,7 +173,8 @@ export const FieldMixin = (superclass) =>
178
173
  // Error message ID needs to be dynamically added / removed based on the validity
179
174
  // Otherwise assistive technologies would announce the error, even if we hide it.
180
175
  if (invalid) {
181
- this._fieldAriaController.setErrorId(this._errorController.errorId);
176
+ const node = this._errorNode;
177
+ this._fieldAriaController.setErrorId(node && node.id);
182
178
  } else {
183
179
  this._fieldAriaController.setErrorId(null);
184
180
  }
@@ -14,8 +14,6 @@ export class HelperController extends SlotController {
14
14
  */
15
15
  helperText: string | null | undefined;
16
16
 
17
- helperId: string;
18
-
19
17
  /**
20
18
  * Set helper text based on corresponding host property.
21
19
  */
@@ -3,189 +3,87 @@
3
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 '@vaadin/component-base/src/slot-controller.js';
6
+ import { SlotObserveController } from '@vaadin/component-base/src/slot-observe-controller.js';
7
7
 
8
8
  /**
9
9
  * A controller that manages the helper node content.
10
10
  */
11
- export class HelperController extends SlotController {
11
+ export class HelperController extends SlotObserveController {
12
12
  constructor(host) {
13
13
  // Do not provide tag name, as we create helper lazily.
14
- super(host, 'helper', null, {
15
- useUniqueId: true,
16
- });
17
- }
18
-
19
- get helperId() {
20
- return this.node && this.node.id;
21
- }
22
-
23
- /**
24
- * Override to initialize the newly added custom helper.
25
- *
26
- * @param {Node} helperNode
27
- * @protected
28
- * @override
29
- */
30
- initCustomNode(helperNode) {
31
- this.__updateHelperId(helperNode);
32
-
33
- this.__observeHelper(helperNode);
34
-
35
- const hasHelper = this.__hasHelper(helperNode);
36
- this.__toggleHasHelper(hasHelper);
37
- }
38
-
39
- /**
40
- * Override to cleanup helper node when it's removed.
41
- *
42
- * @param {Node} _node
43
- * @protected
44
- * @override
45
- */
46
- teardownNode(_node) {
47
- // The observer does not exist when the default helper is removed.
48
- if (this.__helperIdObserver) {
49
- this.__helperIdObserver.disconnect();
50
- }
51
-
52
- const helperNode = this.getSlotChild();
53
-
54
- // Custom node is added to helper slot
55
- if (helperNode && helperNode !== this.defaultNode) {
56
- const hasHelper = this.__hasHelper(helperNode);
57
- this.__toggleHasHelper(hasHelper);
58
- } else {
59
- // Restore default helper if needed
60
- this.__applyDefaultHelper(this.helperText, helperNode);
61
- }
14
+ super(host, 'helper', null);
62
15
  }
63
16
 
64
17
  /**
65
18
  * Set helper text based on corresponding host property.
19
+ *
66
20
  * @param {string} helperText
67
21
  */
68
22
  setHelperText(helperText) {
69
23
  this.helperText = helperText;
70
24
 
25
+ // Restore the default helper, if needed.
71
26
  const helperNode = this.getSlotChild();
72
- if (!helperNode || helperNode === this.defaultNode) {
73
- this.__applyDefaultHelper(helperText, helperNode);
74
- }
75
- }
76
-
77
- /**
78
- * @param {HTMLElement} helperNode
79
- * @return {boolean}
80
- * @private
81
- */
82
- __hasHelper(helperNode) {
83
27
  if (!helperNode) {
84
- return false;
28
+ this.restoreDefaultNode();
85
29
  }
86
30
 
87
- return (
88
- helperNode.children.length > 0 ||
89
- (helperNode.nodeType === Node.ELEMENT_NODE && customElements.get(helperNode.localName)) ||
90
- this.__isNotEmpty(helperNode.textContent)
91
- );
92
- }
93
-
94
- /**
95
- * @param {string} helperText
96
- * @private
97
- */
98
- __isNotEmpty(helperText) {
99
- return helperText && helperText.trim() !== '';
31
+ // When default helper is used, update it.
32
+ if (this.node === this.defaultNode) {
33
+ this.updateDefaultNode(this.node);
34
+ }
100
35
  }
101
36
 
102
37
  /**
103
- * @param {string} helperText
104
- * @param {Node} helperNode
105
- * @private
38
+ * Override method inherited from `SlotObserveController`
39
+ * to create the default helper element lazily as needed.
40
+ *
41
+ * @param {Node | undefined} node
42
+ * @protected
43
+ * @override
106
44
  */
107
- __applyDefaultHelper(helperText, helperNode) {
108
- const hasHelperText = this.__isNotEmpty(helperText);
45
+ restoreDefaultNode() {
46
+ const { helperText } = this;
109
47
 
110
- if (hasHelperText && !helperNode) {
111
- // Set tag name lazily to only create helper node when needed.
48
+ // No helper yet, create one.
49
+ if (helperText && helperText.trim() !== '') {
112
50
  this.tagName = 'div';
113
51
 
114
- helperNode = this.attachDefaultNode();
52
+ const helperNode = this.attachDefaultNode();
115
53
 
116
- this.__updateHelperId(helperNode);
117
- this.__observeHelper(helperNode);
54
+ // Observe the default node.
55
+ this.observeNode(helperNode);
118
56
  }
119
-
120
- if (helperNode) {
121
- helperNode.textContent = helperText;
122
- }
123
-
124
- this.__toggleHasHelper(hasHelperText);
125
57
  }
126
58
 
127
59
  /**
128
- * @param {HTMLElement} helperNode
129
- * @private
60
+ * Override method inherited from `SlotObserveController`
61
+ * to update the default helper element text content.
62
+ *
63
+ * @param {Node | undefined} node
64
+ * @protected
65
+ * @override
130
66
  */
131
- __observeHelper(helperNode) {
132
- this.__helperObserver = new MutationObserver((mutations) => {
133
- mutations.forEach((mutation) => {
134
- const target = mutation.target;
135
-
136
- // Ensure the mutation target is the currently connected helper
137
- // to ignore async mutations dispatched for removed element.
138
- const isHelperMutation = target === this.node;
139
-
140
- if (mutation.type === 'attributes') {
141
- // We use attributeFilter to only observe ID mutation,
142
- // no need to check for attribute name separately.
143
- if (isHelperMutation && target.id !== this.defaultId) {
144
- this.__updateHelperId(target);
145
- }
146
- } else if (isHelperMutation || target.parentElement === this.node) {
147
- // Update has-helper when textContent changes
148
- const hasHelper = this.__hasHelper(this.node);
149
- this.__toggleHasHelper(hasHelper);
150
- }
151
- });
152
- });
67
+ updateDefaultNode(node) {
68
+ if (node) {
69
+ node.textContent = this.helperText;
70
+ }
153
71
 
154
- // Observe changes to helper ID attribute, text content and children.
155
- this.__helperObserver.observe(helperNode, {
156
- attributes: true,
157
- attributeFilter: ['id'],
158
- childList: true,
159
- subtree: true,
160
- characterData: true,
161
- });
72
+ // Notify the host after update.
73
+ super.updateDefaultNode(node);
162
74
  }
163
75
 
164
76
  /**
165
- * @param {boolean} hasHelper
166
- * @private
77
+ * Override to observe the newly added custom node.
78
+ *
79
+ * @param {Node} node
80
+ * @protected
81
+ * @override
167
82
  */
168
- __toggleHasHelper(hasHelper) {
169
- this.host.toggleAttribute('has-helper', hasHelper);
170
-
171
- // Make it possible for other mixins to observe change
172
- this.dispatchEvent(
173
- new CustomEvent('helper-changed', {
174
- detail: {
175
- hasHelper,
176
- node: this.node,
177
- },
178
- }),
179
- );
180
- }
83
+ initCustomNode(node) {
84
+ // Notify the host about a custom slotted helper.
85
+ super.initCustomNode(node);
181
86
 
182
- /**
183
- * @param {HTMLElement} helperNode
184
- * @private
185
- */
186
- __updateHelperId(helperNode) {
187
- if (!helperNode.id) {
188
- helperNode.id = this.defaultId;
189
- }
87
+ this.observeNode(node);
190
88
  }
191
89
  }
@@ -56,4 +56,6 @@ export declare class InputMixinClass {
56
56
  protected _toggleHasValue(hasValue: boolean): void;
57
57
 
58
58
  protected _valueChanged(value?: string, oldValue?: string): void;
59
+
60
+ protected _setHasInputValue(event: InputEvent): void;
59
61
  }
@@ -161,11 +161,7 @@ export const InputMixin = dedupingMixin(
161
161
  * @private
162
162
  */
163
163
  __onInput(event) {
164
- // In the case a custom web component is passed as `inputElement`,
165
- // the actual native input element, on which the event occurred,
166
- // can be inside shadow trees.
167
- const target = event.composedPath()[0];
168
- this._hasInputValue = target.value.length > 0;
164
+ this._setHasInputValue(event);
169
165
  this._onInput(event);
170
166
  }
171
167
 
@@ -236,5 +232,19 @@ export const InputMixin = dedupingMixin(
236
232
  get _hasValue() {
237
233
  return this.value != null && this.value !== '';
238
234
  }
235
+
236
+ /**
237
+ * Sets the `_hasInputValue` property based on the `input` event.
238
+ *
239
+ * @param {InputEvent} event
240
+ * @protected
241
+ */
242
+ _setHasInputValue(event) {
243
+ // In the case a custom web component is passed as `inputElement`,
244
+ // the actual native input element, on which the event occurred,
245
+ // can be inside shadow trees.
246
+ const target = event.composedPath()[0];
247
+ this._hasInputValue = target.value.length > 0;
248
+ }
239
249
  },
240
250
  );
@@ -9,11 +9,6 @@ import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
9
9
  * A controller to manage the label element.
10
10
  */
11
11
  export class LabelController extends SlotController {
12
- /**
13
- * ID attribute value set on the label element.
14
- */
15
- labelId: string;
16
-
17
12
  /**
18
13
  * String used for the label.
19
14
  */
@@ -3,72 +3,14 @@
3
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 '@vaadin/component-base/src/slot-controller.js';
6
+ import { SlotObserveController } from '@vaadin/component-base/src/slot-observe-controller.js';
7
7
 
8
8
  /**
9
9
  * A controller to manage the label element.
10
10
  */
11
- export class LabelController extends SlotController {
11
+ export class LabelController extends SlotObserveController {
12
12
  constructor(host) {
13
- super(host, 'label', 'label', {
14
- initializer: (node) => {
15
- // Set ID attribute or use the existing one.
16
- this.__updateLabelId(node);
17
-
18
- // Set text content for the default label.
19
- this.__updateDefaultLabel(this.label);
20
-
21
- this.__observeLabel(node);
22
- },
23
- useUniqueId: true,
24
- });
25
- }
26
-
27
- /**
28
- * @return {string}
29
- */
30
- get labelId() {
31
- return this.node.id;
32
- }
33
-
34
- /**
35
- * Override to initialize the newly added custom label.
36
- *
37
- * @param {Node} labelNode
38
- * @protected
39
- * @override
40
- */
41
- initCustomNode(labelNode) {
42
- this.__updateLabelId(labelNode);
43
-
44
- const hasLabel = this.__hasLabel(labelNode);
45
- this.__toggleHasLabel(hasLabel);
46
- }
47
-
48
- /**
49
- * Override to cleanup label node when it's removed.
50
- *
51
- * @param {Node} node
52
- * @protected
53
- * @override
54
- */
55
- teardownNode(node) {
56
- if (this.__labelObserver) {
57
- this.__labelObserver.disconnect();
58
- }
59
-
60
- let labelNode = this.getSlotChild();
61
-
62
- // If custom label was removed, restore the default one.
63
- if (!labelNode && node !== this.defaultNode) {
64
- labelNode = this.attachDefaultNode();
65
-
66
- // Run initializer to update default label and ID.
67
- this.initNode(labelNode);
68
- }
69
-
70
- const hasLabel = this.__hasLabel(labelNode);
71
- this.__toggleHasLabel(hasLabel);
13
+ super(host, 'label', 'label');
72
14
  }
73
15
 
74
16
  /**
@@ -79,108 +21,80 @@ export class LabelController extends SlotController {
79
21
  setLabel(label) {
80
22
  this.label = label;
81
23
 
82
- this.__updateDefaultLabel(label);
83
- }
84
-
85
- /**
86
- * @param {HTMLElement} labelNode
87
- * @return {boolean}
88
- * @private
89
- */
90
- __hasLabel(labelNode) {
24
+ // Restore the default label, if needed.
25
+ const labelNode = this.getSlotChild();
91
26
  if (!labelNode) {
92
- return false;
27
+ this.restoreDefaultNode();
93
28
  }
94
29
 
95
- return labelNode.children.length > 0 || this.__isNotEmpty(labelNode.textContent);
96
- }
97
-
98
- /**
99
- * @param {string} label
100
- * @private
101
- */
102
- __isNotEmpty(label) {
103
- return Boolean(label && label.trim() !== '');
30
+ // When default label is used, update it.
31
+ if (this.node === this.defaultNode) {
32
+ this.updateDefaultNode(this.node);
33
+ }
104
34
  }
105
35
 
106
36
  /**
107
- * @param {HTMLElement} labelNode
108
- * @private
37
+ * Override method inherited from `SlotObserveController`
38
+ * to restore and observe the default label element.
39
+ *
40
+ * @protected
41
+ * @override
109
42
  */
110
- __observeLabel(labelNode) {
111
- this.__labelObserver = new MutationObserver((mutations) => {
112
- mutations.forEach((mutation) => {
113
- const target = mutation.target;
114
-
115
- // Ensure the mutation target is the currently connected label
116
- // to ignore async mutations dispatched for removed element.
117
- const isLabelMutation = target === this.node;
43
+ restoreDefaultNode() {
44
+ const { label } = this;
118
45
 
119
- if (mutation.type === 'attributes') {
120
- // We use attributeFilter to only observe ID mutation,
121
- // no need to check for attribute name separately.
122
- if (isLabelMutation && target.id !== this.defaultId) {
123
- this.__updateLabelId(target);
124
- }
125
- } else if (isLabelMutation || target.parentElement === this.node) {
126
- // Update has-label when textContent changes
127
- const hasLabel = this.__hasLabel(this.node);
128
- this.__toggleHasLabel(hasLabel);
129
- }
130
- });
131
- });
46
+ // Restore the default label.
47
+ if (label && label.trim() !== '') {
48
+ const labelNode = this.attachDefaultNode();
132
49
 
133
- // Observe changes to label ID attribute, text content and children.
134
- this.__labelObserver.observe(labelNode, {
135
- attributes: true,
136
- attributeFilter: ['id'],
137
- childList: true,
138
- subtree: true,
139
- characterData: true,
140
- });
50
+ // Observe the default label.
51
+ this.observeNode(labelNode);
52
+ }
141
53
  }
142
54
 
143
55
  /**
144
- * @param {boolean} hasLabel
145
- * @private
56
+ * Override method inherited from `SlotObserveController`
57
+ * to update the default label element text content.
58
+ *
59
+ * @param {Node | undefined} node
60
+ * @protected
61
+ * @override
146
62
  */
147
- __toggleHasLabel(hasLabel) {
148
- this.host.toggleAttribute('has-label', hasLabel);
63
+ updateDefaultNode(node) {
64
+ if (node) {
65
+ node.textContent = this.label;
66
+ }
149
67
 
150
- // Make it possible for other mixins to observe change
151
- this.dispatchEvent(
152
- new CustomEvent('label-changed', {
153
- detail: {
154
- hasLabel,
155
- node: this.node,
156
- },
157
- }),
158
- );
68
+ // Notify the host after update.
69
+ super.updateDefaultNode(node);
159
70
  }
160
71
 
161
72
  /**
162
- * @param {string} label
163
- * @private
73
+ * Override method inherited from `SlotMixin` to observe
74
+ * the default label node, but not the custom one.
75
+ *
76
+ * @param {Node} node
77
+ * @protected
78
+ * @override
164
79
  */
165
- __updateDefaultLabel(label) {
166
- if (this.defaultNode) {
167
- this.defaultNode.textContent = label;
168
-
169
- // Update has-label if default label is used
170
- if (this.defaultNode === this.node) {
171
- const hasLabel = this.__isNotEmpty(label);
172
- this.__toggleHasLabel(hasLabel);
173
- }
80
+ initNode(node) {
81
+ if (node === this.defaultNode) {
82
+ this.updateDefaultNode(node);
83
+ this.observeNode(node);
174
84
  }
175
85
  }
176
86
 
177
87
  /**
178
- * @param {HTMLElement} labelNode
179
- * @private
88
+ * Override to observe the newly added custom node.
89
+ *
90
+ * @param {Node} node
91
+ * @protected
92
+ * @override
180
93
  */
181
- __updateLabelId(labelNode) {
182
- if (!labelNode.id) {
183
- labelNode.id = this.defaultId;
184
- }
94
+ initCustomNode(node) {
95
+ // Notify the host about adding a custom node.
96
+ super.initCustomNode(node);
97
+
98
+ this.observeNode(node);
185
99
  }
186
100
  }
@@ -31,7 +31,8 @@ export const LabelMixin = dedupingMixin(
31
31
 
32
32
  /** @protected */
33
33
  get _labelId() {
34
- return this._labelController.labelId;
34
+ const node = this._labelNode;
35
+ return node && node.id;
35
36
  }
36
37
 
37
38
  /** @protected */
@@ -43,6 +44,10 @@ export const LabelMixin = dedupingMixin(
43
44
  super();
44
45
 
45
46
  this._labelController = new LabelController(this);
47
+
48
+ this._labelController.addEventListener('slot-content-changed', (event) => {
49
+ this.toggleAttribute('has-label', event.detail.hasContent);
50
+ });
46
51
  }
47
52
 
48
53
  /** @protected */
@@ -12,7 +12,7 @@ export class LabelledInputController {
12
12
  this.input = input;
13
13
  this.__preventDuplicateLabelClick = this.__preventDuplicateLabelClick.bind(this);
14
14
 
15
- labelController.addEventListener('label-changed', (event) => {
15
+ labelController.addEventListener('slot-content-changed', (event) => {
16
16
  this.__initLabel(event.detail.node);
17
17
  });
18
18
 
@@ -1,23 +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 type { Constructor } from '@open-wc/dedupe-mixin';
7
- import type { DisabledMixinClass } from '@vaadin/component-base/src/disabled-mixin.js';
8
- import type { FocusMixinClass } from '@vaadin/component-base/src/focus-mixin.js';
9
- import type { KeyboardMixinClass } from '@vaadin/component-base/src/keyboard-mixin.js';
10
- import type { TabindexMixinClass } from '@vaadin/component-base/src/tabindex-mixin.js';
11
- import type { DelegateFocusMixinClass } from './delegate-focus-mixin.js';
12
-
13
- /**
14
- * A mixin to forward focus to an element in the shadow DOM.
15
- */
16
- export declare function ShadowFocusMixin<T extends Constructor<HTMLElement>>(
17
- base: T,
18
- ): Constructor<DelegateFocusMixinClass> &
19
- Constructor<DisabledMixinClass> &
20
- Constructor<FocusMixinClass> &
21
- Constructor<KeyboardMixinClass> &
22
- Constructor<TabindexMixinClass> &
23
- T;
@@ -1,97 +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 { KeyboardMixin } from '@vaadin/component-base/src/keyboard-mixin.js';
7
- import { DelegateFocusMixin } from './delegate-focus-mixin.js';
8
-
9
- /**
10
- * A mixin to forward focus to an element in the shadow DOM.
11
- *
12
- * @polymerMixin
13
- * @mixes DelegateFocusMixin
14
- * @mixes KeyboardMixin
15
- */
16
- export const ShadowFocusMixin = (superClass) =>
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
- * @override
24
- * @protected
25
- */
26
- tabindex: {
27
- type: Number,
28
- value: 0,
29
- },
30
- };
31
- }
32
-
33
- /**
34
- * Override an event listener from `KeyboardMixin`
35
- * to prevent focusing the host element on Shift Tab.
36
- * @param {KeyboardEvent} event
37
- * @protected
38
- * @override
39
- */
40
- _onKeyDown(event) {
41
- super._onKeyDown(event);
42
-
43
- // When focus moves with Shift + Tab, skip focusing the host element
44
- // by focusing it before the default browser focus handling runs
45
- if (!event.defaultPrevented && event.keyCode === 9 && event.shiftKey) {
46
- HTMLElement.prototype.focus.apply(this);
47
- }
48
- }
49
-
50
- /**
51
- * Override method inherited from `FocusMixin`
52
- * to support focusElement in Shadow DOM.
53
- * @param {Event} event
54
- * @return {boolean}
55
- * @protected
56
- * @override
57
- */
58
- _shouldSetFocus(event) {
59
- if (!this.disabled && this.focusElement) {
60
- const path = event.composedPath();
61
-
62
- // When focus moves to the host element itself, then delegate it to the focusElement
63
- // This should only move focus when using keyboard navigation, for clicks we don't want to interfere,
64
- // for example when the user tries to select some text
65
- if (path[0] === this && this._keyboardActive) {
66
- this.focusElement.focus();
67
- }
68
- if (path[0] === this || path.includes(this.focusElement)) {
69
- return true;
70
- }
71
- }
72
-
73
- return false;
74
- }
75
-
76
- /**
77
- * Override an observer from `TabindexMixin`.
78
- * Do not call super to remove tabindex attribute
79
- * from host when disabled by setting undefined.
80
- * @param {string} tabindex
81
- * @protected
82
- * @override
83
- */
84
- _tabindexChanged(tabindex) {
85
- if (tabindex !== undefined) {
86
- this.focusElement.tabIndex = tabindex;
87
- }
88
-
89
- if (this.disabled && tabindex) {
90
- // If tabindex attribute was changed while component was disabled
91
- if (tabindex !== -1) {
92
- this._lastTabIndex = tabindex;
93
- }
94
- this.tabindex = undefined;
95
- }
96
- }
97
- };