@vaadin/field-base 25.3.0-alpha1 → 25.3.0-alpha11

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.
@@ -150,7 +150,7 @@
150
150
  "type": {
151
151
  "text": "boolean"
152
152
  },
153
- "description": "Set to true to display the clear icon which clears the input.\n\nIt is up to the component to choose where to place the clear icon:\nin the Shadow DOM or in the light DOM. In any way, a reference to\nthe clear icon element should be provided via the `clearElement` getter.",
153
+ "description": "Set to true to display the clear icon which clears the input.\nThis also enables clearing the input when pressing Esc key.",
154
154
  "attribute": "clear-button-visible"
155
155
  },
156
156
  {
@@ -174,7 +174,7 @@
174
174
  "type": {
175
175
  "text": "boolean"
176
176
  },
177
- "description": "Set to true to display the clear icon which clears the input.\n\nIt is up to the component to choose where to place the clear icon:\nin the Shadow DOM or in the light DOM. In any way, a reference to\nthe clear icon element should be provided via the `clearElement` getter.",
177
+ "description": "Set to true to display the clear icon which clears the input.\nThis also enables clearing the input when pressing Esc key.",
178
178
  "fieldName": "clearButtonVisible"
179
179
  },
180
180
  {
@@ -227,6 +227,16 @@
227
227
  "description": "A mixin to provide common field logic: label, error message and helper text.",
228
228
  "name": "FieldMixin",
229
229
  "members": [
230
+ {
231
+ "kind": "field",
232
+ "name": "accessibleDescriptionRef",
233
+ "privacy": "public",
234
+ "type": {
235
+ "text": "string"
236
+ },
237
+ "description": "A space-separated list of IDs referencing the elements that\ndescribe the component for screen reader users. The referenced\nelements are announced in addition to the helper text and\nthe error message.",
238
+ "attribute": "accessible-description-ref"
239
+ },
230
240
  {
231
241
  "kind": "field",
232
242
  "name": "accessibleName",
@@ -234,7 +244,7 @@
234
244
  "type": {
235
245
  "text": "string"
236
246
  },
237
- "description": "String used to label the component to screen reader users.",
247
+ "description": "String used to label the component for screen reader users.",
238
248
  "attribute": "accessible-name"
239
249
  },
240
250
  {
@@ -244,7 +254,7 @@
244
254
  "type": {
245
255
  "text": "string"
246
256
  },
247
- "description": "Id of the element used as label of the component to screen reader users.",
257
+ "description": "A space-separated list of IDs referencing the elements that\nlabel the component for screen reader users.",
248
258
  "attribute": "accessible-name-ref"
249
259
  },
250
260
  {
@@ -353,12 +363,20 @@
353
363
  }
354
364
  ],
355
365
  "attributes": [
366
+ {
367
+ "name": "accessible-description-ref",
368
+ "type": {
369
+ "text": "string"
370
+ },
371
+ "description": "A space-separated list of IDs referencing the elements that\ndescribe the component for screen reader users. The referenced\nelements are announced in addition to the helper text and\nthe error message.",
372
+ "fieldName": "accessibleDescriptionRef"
373
+ },
356
374
  {
357
375
  "name": "accessible-name",
358
376
  "type": {
359
377
  "text": "string"
360
378
  },
361
- "description": "String used to label the component to screen reader users.",
379
+ "description": "String used to label the component for screen reader users.",
362
380
  "fieldName": "accessibleName"
363
381
  },
364
382
  {
@@ -366,7 +384,7 @@
366
384
  "type": {
367
385
  "text": "string"
368
386
  },
369
- "description": "Id of the element used as label of the component to screen reader users.",
387
+ "description": "A space-separated list of IDs referencing the elements that\nlabel the component for screen reader users.",
370
388
  "fieldName": "accessibleNameRef"
371
389
  },
372
390
  {
@@ -690,6 +708,20 @@
690
708
  "description": "A mixin to provide shared logic for the editable form input controls.",
691
709
  "name": "InputControlMixin",
692
710
  "members": [
711
+ {
712
+ "kind": "field",
713
+ "name": "accessibleDescriptionRef",
714
+ "privacy": "public",
715
+ "type": {
716
+ "text": "string"
717
+ },
718
+ "description": "A space-separated list of IDs referencing the elements that\ndescribe the component for screen reader users. The referenced\nelements are announced in addition to the helper text and\nthe error message.",
719
+ "attribute": "accessible-description-ref",
720
+ "inheritedFrom": {
721
+ "name": "FieldMixin",
722
+ "module": "src/field-mixin.js"
723
+ }
724
+ },
693
725
  {
694
726
  "kind": "field",
695
727
  "name": "accessibleName",
@@ -697,7 +729,7 @@
697
729
  "type": {
698
730
  "text": "string"
699
731
  },
700
- "description": "String used to label the component to screen reader users.",
732
+ "description": "String used to label the component for screen reader users.",
701
733
  "attribute": "accessible-name",
702
734
  "inheritedFrom": {
703
735
  "name": "FieldMixin",
@@ -711,7 +743,7 @@
711
743
  "type": {
712
744
  "text": "string"
713
745
  },
714
- "description": "Id of the element used as label of the component to screen reader users.",
746
+ "description": "A space-separated list of IDs referencing the elements that\nlabel the component for screen reader users.",
715
747
  "attribute": "accessible-name-ref",
716
748
  "inheritedFrom": {
717
749
  "name": "FieldMixin",
@@ -782,7 +814,7 @@
782
814
  "type": {
783
815
  "text": "boolean"
784
816
  },
785
- "description": "Set to true to display the clear icon which clears the input.\n\nIt is up to the component to choose where to place the clear icon:\nin the Shadow DOM or in the light DOM. In any way, a reference to\nthe clear icon element should be provided via the `clearElement` getter.",
817
+ "description": "Set to true to display the clear icon which clears the input.\nThis also enables clearing the input when pressing Esc key.",
786
818
  "attribute": "clear-button-visible",
787
819
  "inheritedFrom": {
788
820
  "name": "ClearButtonMixin",
@@ -957,12 +989,24 @@
957
989
  }
958
990
  ],
959
991
  "attributes": [
992
+ {
993
+ "name": "accessible-description-ref",
994
+ "type": {
995
+ "text": "string"
996
+ },
997
+ "description": "A space-separated list of IDs referencing the elements that\ndescribe the component for screen reader users. The referenced\nelements are announced in addition to the helper text and\nthe error message.",
998
+ "fieldName": "accessibleDescriptionRef",
999
+ "inheritedFrom": {
1000
+ "name": "FieldMixin",
1001
+ "module": "src/field-mixin.js"
1002
+ }
1003
+ },
960
1004
  {
961
1005
  "name": "accessible-name",
962
1006
  "type": {
963
1007
  "text": "string"
964
1008
  },
965
- "description": "String used to label the component to screen reader users.",
1009
+ "description": "String used to label the component for screen reader users.",
966
1010
  "fieldName": "accessibleName",
967
1011
  "inheritedFrom": {
968
1012
  "name": "FieldMixin",
@@ -974,7 +1018,7 @@
974
1018
  "type": {
975
1019
  "text": "string"
976
1020
  },
977
- "description": "Id of the element used as label of the component to screen reader users.",
1021
+ "description": "A space-separated list of IDs referencing the elements that\nlabel the component for screen reader users.",
978
1022
  "fieldName": "accessibleNameRef",
979
1023
  "inheritedFrom": {
980
1024
  "name": "FieldMixin",
@@ -1014,7 +1058,7 @@
1014
1058
  "type": {
1015
1059
  "text": "boolean"
1016
1060
  },
1017
- "description": "Set to true to display the clear icon which clears the input.\n\nIt is up to the component to choose where to place the clear icon:\nin the Shadow DOM or in the light DOM. In any way, a reference to\nthe clear icon element should be provided via the `clearElement` getter.",
1061
+ "description": "Set to true to display the clear icon which clears the input.\nThis also enables clearing the input when pressing Esc key.",
1018
1062
  "fieldName": "clearButtonVisible",
1019
1063
  "inheritedFrom": {
1020
1064
  "name": "ClearButtonMixin",
@@ -1225,6 +1269,20 @@
1225
1269
  "description": "A mixin to provide logic for vaadin-text-field and related components.",
1226
1270
  "name": "InputFieldMixin",
1227
1271
  "members": [
1272
+ {
1273
+ "kind": "field",
1274
+ "name": "accessibleDescriptionRef",
1275
+ "privacy": "public",
1276
+ "type": {
1277
+ "text": "string"
1278
+ },
1279
+ "description": "A space-separated list of IDs referencing the elements that\ndescribe the component for screen reader users. The referenced\nelements are announced in addition to the helper text and\nthe error message.",
1280
+ "attribute": "accessible-description-ref",
1281
+ "inheritedFrom": {
1282
+ "name": "FieldMixin",
1283
+ "module": "src/field-mixin.js"
1284
+ }
1285
+ },
1228
1286
  {
1229
1287
  "kind": "field",
1230
1288
  "name": "accessibleName",
@@ -1232,7 +1290,7 @@
1232
1290
  "type": {
1233
1291
  "text": "string"
1234
1292
  },
1235
- "description": "String used to label the component to screen reader users.",
1293
+ "description": "String used to label the component for screen reader users.",
1236
1294
  "attribute": "accessible-name",
1237
1295
  "inheritedFrom": {
1238
1296
  "name": "FieldMixin",
@@ -1246,7 +1304,7 @@
1246
1304
  "type": {
1247
1305
  "text": "string"
1248
1306
  },
1249
- "description": "Id of the element used as label of the component to screen reader users.",
1307
+ "description": "A space-separated list of IDs referencing the elements that\nlabel the component for screen reader users.",
1250
1308
  "attribute": "accessible-name-ref",
1251
1309
  "inheritedFrom": {
1252
1310
  "name": "FieldMixin",
@@ -1355,7 +1413,7 @@
1355
1413
  "type": {
1356
1414
  "text": "boolean"
1357
1415
  },
1358
- "description": "Set to true to display the clear icon which clears the input.\n\nIt is up to the component to choose where to place the clear icon:\nin the Shadow DOM or in the light DOM. In any way, a reference to\nthe clear icon element should be provided via the `clearElement` getter.",
1416
+ "description": "Set to true to display the clear icon which clears the input.\nThis also enables clearing the input when pressing Esc key.",
1359
1417
  "attribute": "clear-button-visible",
1360
1418
  "inheritedFrom": {
1361
1419
  "name": "ClearButtonMixin",
@@ -1546,12 +1604,24 @@
1546
1604
  }
1547
1605
  ],
1548
1606
  "attributes": [
1607
+ {
1608
+ "name": "accessible-description-ref",
1609
+ "type": {
1610
+ "text": "string"
1611
+ },
1612
+ "description": "A space-separated list of IDs referencing the elements that\ndescribe the component for screen reader users. The referenced\nelements are announced in addition to the helper text and\nthe error message.",
1613
+ "fieldName": "accessibleDescriptionRef",
1614
+ "inheritedFrom": {
1615
+ "name": "FieldMixin",
1616
+ "module": "src/field-mixin.js"
1617
+ }
1618
+ },
1549
1619
  {
1550
1620
  "name": "accessible-name",
1551
1621
  "type": {
1552
1622
  "text": "string"
1553
1623
  },
1554
- "description": "String used to label the component to screen reader users.",
1624
+ "description": "String used to label the component for screen reader users.",
1555
1625
  "fieldName": "accessibleName",
1556
1626
  "inheritedFrom": {
1557
1627
  "name": "FieldMixin",
@@ -1563,7 +1633,7 @@
1563
1633
  "type": {
1564
1634
  "text": "string"
1565
1635
  },
1566
- "description": "Id of the element used as label of the component to screen reader users.",
1636
+ "description": "A space-separated list of IDs referencing the elements that\nlabel the component for screen reader users.",
1567
1637
  "fieldName": "accessibleNameRef",
1568
1638
  "inheritedFrom": {
1569
1639
  "name": "FieldMixin",
@@ -1635,7 +1705,7 @@
1635
1705
  "type": {
1636
1706
  "text": "boolean"
1637
1707
  },
1638
- "description": "Set to true to display the clear icon which clears the input.\n\nIt is up to the component to choose where to place the clear icon:\nin the Shadow DOM or in the light DOM. In any way, a reference to\nthe clear icon element should be provided via the `clearElement` getter.",
1708
+ "description": "Set to true to display the clear icon which clears the input.\nThis also enables clearing the input when pressing Esc key.",
1639
1709
  "fieldName": "clearButtonVisible",
1640
1710
  "inheritedFrom": {
1641
1711
  "name": "ClearButtonMixin",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/field-base",
3
- "version": "25.3.0-alpha1",
3
+ "version": "25.3.0-alpha11",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -32,17 +32,17 @@
32
32
  ],
33
33
  "dependencies": {
34
34
  "@open-wc/dedupe-mixin": "^1.3.0",
35
- "@vaadin/a11y-base": "25.3.0-alpha1",
36
- "@vaadin/component-base": "25.3.0-alpha1",
35
+ "@vaadin/a11y-base": "25.3.0-alpha11",
36
+ "@vaadin/component-base": "25.3.0-alpha11",
37
37
  "lit": "^3.0.0"
38
38
  },
39
39
  "devDependencies": {
40
- "@vaadin/chai-plugins": "25.3.0-alpha1",
41
- "@vaadin/input-container": "25.3.0-alpha1",
42
- "@vaadin/test-runner-commands": "25.3.0-alpha1",
40
+ "@vaadin/chai-plugins": "25.3.0-alpha11",
41
+ "@vaadin/input-container": "25.3.0-alpha11",
42
+ "@vaadin/test-runner-commands": "25.3.0-alpha11",
43
43
  "@vaadin/testing-helpers": "^2.0.0",
44
44
  "sinon": "^22.0.0"
45
45
  },
46
46
  "customElements": "custom-elements.json",
47
- "gitHead": "5824d54c995945aafd50d30edc2cb763a58a18b6"
47
+ "gitHead": "7e0c61a37e68d8971def9cdf28ad0548a0f530a9"
48
48
  }
@@ -17,10 +17,7 @@ export declare function ClearButtonMixin<T extends Constructor<HTMLElement>>(
17
17
  export declare class ClearButtonMixinClass {
18
18
  /**
19
19
  * Set to true to display the clear icon which clears the input.
20
- *
21
- * It is up to the component to choose where to place the clear icon:
22
- * in the Shadow DOM or in the light DOM. In any way, a reference to
23
- * the clear icon element should be provided via the `clearElement` getter.
20
+ * This also enables clearing the input when pressing Esc key.
24
21
  *
25
22
  * @attr {boolean} clear-button-visible
26
23
  */
@@ -17,10 +17,7 @@ export const ClearButtonMixin = (superclass) =>
17
17
  return {
18
18
  /**
19
19
  * Set to true to display the clear icon which clears the input.
20
- *
21
- * It is up to the component to choose where to place the clear icon:
22
- * in the Shadow DOM or in the light DOM. In any way, a reference to
23
- * the clear icon element should be provided via the `clearElement` getter.
20
+ * This also enables clearing the input when pressing Esc key.
24
21
  *
25
22
  * @attr {boolean} clear-button-visible
26
23
  */
@@ -30,17 +30,30 @@ export declare class FieldMixinClass {
30
30
  errorMessage: string | null | undefined;
31
31
 
32
32
  /**
33
- * String used to label the component to screen reader users.
33
+ * String used to label the component for screen reader users.
34
+ *
34
35
  * @attr {string} accessible-name
35
36
  */
36
37
  accessibleName: string | null | undefined;
37
38
 
38
39
  /**
39
- * Id of the element used as label of the component to screen reader users.
40
+ * A space-separated list of IDs referencing the elements that
41
+ * label the component for screen reader users.
42
+ *
40
43
  * @attr {string} accessible-name-ref
41
44
  */
42
45
  accessibleNameRef: string | null | undefined;
43
46
 
47
+ /**
48
+ * A space-separated list of IDs referencing the elements that
49
+ * describe the component for screen reader users. The referenced
50
+ * elements are announced in addition to the helper text and
51
+ * the error message.
52
+ *
53
+ * @attr {string} accessible-description-ref
54
+ */
55
+ accessibleDescriptionRef: string | null | undefined;
56
+
44
57
  /**
45
58
  * A target element to which ARIA attributes are set.
46
59
  */
@@ -49,12 +62,4 @@ export declare class FieldMixinClass {
49
62
  protected readonly _errorNode: HTMLElement;
50
63
 
51
64
  protected readonly _helperNode?: HTMLElement;
52
-
53
- protected _helperTextChanged(helperText: string | null | undefined): void;
54
-
55
- protected _ariaTargetChanged(target: HTMLElement): void;
56
-
57
- protected _requiredChanged(required: boolean): void;
58
-
59
- protected _invalidChanged(invalid: boolean): void;
60
65
  }
@@ -22,7 +22,6 @@ export const FieldMixin = (superclass) =>
22
22
  */
23
23
  ariaTarget: {
24
24
  type: Object,
25
- observer: '_ariaTargetChanged',
26
25
  },
27
26
 
28
27
  /**
@@ -32,7 +31,6 @@ export const FieldMixin = (superclass) =>
32
31
  */
33
32
  errorMessage: {
34
33
  type: String,
35
- observer: '_errorMessageChanged',
36
34
  },
37
35
 
38
36
  /**
@@ -41,54 +39,59 @@ export const FieldMixin = (superclass) =>
41
39
  */
42
40
  helperText: {
43
41
  type: String,
44
- observer: '_helperTextChanged',
45
42
  },
46
43
 
47
44
  /**
48
- * String used to label the component to screen reader users.
45
+ * String used to label the component for screen reader users.
46
+ *
49
47
  * @attr {string} accessible-name
50
48
  */
51
49
  accessibleName: {
52
50
  type: String,
53
- observer: '_accessibleNameChanged',
54
51
  },
55
52
 
56
53
  /**
57
- * Id of the element used as label of the component to screen reader users.
54
+ * A space-separated list of IDs referencing the elements that
55
+ * label the component for screen reader users.
56
+ *
58
57
  * @attr {string} accessible-name-ref
59
58
  */
60
59
  accessibleNameRef: {
61
60
  type: String,
62
- observer: '_accessibleNameRefChanged',
63
61
  },
64
- };
65
- }
66
62
 
67
- static get observers() {
68
- return ['_invalidChanged(invalid)', '_requiredChanged(required)'];
63
+ /**
64
+ * A space-separated list of IDs referencing the elements that
65
+ * describe the component for screen reader users. The referenced
66
+ * elements are announced in addition to the helper text and
67
+ * the error message.
68
+ *
69
+ * @attr {string} accessible-description-ref
70
+ */
71
+ accessibleDescriptionRef: {
72
+ type: String,
73
+ },
74
+ };
69
75
  }
70
76
 
71
77
  constructor() {
72
78
  super();
73
79
 
74
- this._fieldAriaController = new FieldAriaController(this);
75
- this._helperController = new HelperController(this);
76
- this._errorController = new ErrorController(this);
77
-
78
- this._errorController.addEventListener('slot-content-changed', (event) => {
79
- this.toggleAttribute('has-error-message', event.detail.hasContent);
80
- });
81
-
82
80
  this._labelController.addEventListener('slot-content-changed', (event) => {
83
- const { hasContent, node } = event.detail;
84
- this.__labelChanged(hasContent, node);
81
+ this.#onLabelSlotContentChange(event);
85
82
  });
86
83
 
84
+ this._helperController = new HelperController(this);
87
85
  this._helperController.addEventListener('slot-content-changed', (event) => {
88
- const { hasContent, node } = event.detail;
89
- this.toggleAttribute('has-helper', hasContent);
90
- this.__helperChanged(hasContent, node);
86
+ this.#onHelperSlotContentChange(event);
91
87
  });
88
+
89
+ this._errorController = new ErrorController(this);
90
+ this._errorController.addEventListener('slot-content-changed', (event) => {
91
+ this.#onErrorSlotContentChange(event);
92
+ });
93
+
94
+ this._fieldAriaController = new FieldAriaController(this);
92
95
  }
93
96
 
94
97
  /**
@@ -116,76 +119,86 @@ export const FieldMixin = (superclass) =>
116
119
  this.addController(this._errorController);
117
120
  }
118
121
 
119
- /** @private */
120
- __helperChanged(hasHelper, helperNode) {
121
- if (hasHelper) {
122
- this._fieldAriaController.setHelperId(helperNode.id);
123
- } else {
124
- this._fieldAriaController.setHelperId(null);
122
+ /** @protected */
123
+ updated(props) {
124
+ super.updated(props);
125
+
126
+ if (props.has('invalid')) {
127
+ this._errorController.setInvalid(this.invalid);
125
128
  }
126
- }
127
129
 
128
- /** @protected */
129
- _accessibleNameChanged(accessibleName) {
130
- this._fieldAriaController.setAriaLabel(accessibleName);
130
+ if (props.has('errorMessage')) {
131
+ this._errorController.setErrorMessage(this.errorMessage);
132
+ }
133
+
134
+ if (props.has('helperText')) {
135
+ this._helperController.setHelperText(this.helperText);
136
+ }
137
+
138
+ if (props.has('ariaTarget')) {
139
+ this._fieldAriaController.setTarget(this.ariaTarget);
140
+ }
141
+
142
+ if (props.has('required')) {
143
+ this._fieldAriaController.setRequired(this.required);
144
+ }
145
+
146
+ if (props.has('accessibleName')) {
147
+ this.__updateFieldAriaControllerLabel();
148
+ }
149
+
150
+ if (props.has('accessibleName') || props.has('accessibleNameRef')) {
151
+ this.__updateFieldAriaControllerLabelledBy();
152
+ }
153
+
154
+ if (props.has('accessibleDescriptionRef')) {
155
+ this.__updateFieldAriaControllerDescribedBy();
156
+ }
131
157
  }
132
158
 
133
- /** @protected */
134
- _accessibleNameRefChanged(accessibleNameRef) {
135
- this._fieldAriaController.setLabelId(accessibleNameRef, true);
159
+ /** @private */
160
+ __updateFieldAriaControllerLabel() {
161
+ this._fieldAriaController.setLabel(this.accessibleName);
136
162
  }
137
163
 
138
164
  /** @private */
139
- __labelChanged(hasLabel, labelNode) {
140
- // Label ID should be only added when the label content is present.
141
- // Otherwise, it may conflict with an `aria-label` attribute possibly added by the user.
142
- if (hasLabel) {
143
- this._fieldAriaController.setLabelId(labelNode.id);
144
- } else {
145
- this._fieldAriaController.setLabelId(null);
165
+ __updateFieldAriaControllerLabelledBy() {
166
+ let id = null;
167
+
168
+ if (this.accessibleNameRef) {
169
+ id = this.accessibleNameRef;
170
+ } else if (this.hasAttribute('has-label') && !this.accessibleName) {
171
+ // Label ID should be only added when the label content is present
172
+ // and not overridden by `accessible-name` which sets `aria-label`.
173
+ id = this._labelNode?.id;
146
174
  }
147
- }
148
175
 
149
- /**
150
- * @param {string | null | undefined} errorMessage
151
- * @protected
152
- */
153
- _errorMessageChanged(errorMessage) {
154
- this._errorController.setErrorMessage(errorMessage);
176
+ this._fieldAriaController.setLabelledBy(id);
155
177
  }
156
178
 
157
- /**
158
- * @param {string} helperText
159
- * @protected
160
- */
161
- _helperTextChanged(helperText) {
162
- this._helperController.setHelperText(helperText);
179
+ /** @private */
180
+ __updateFieldAriaControllerDescribedBy() {
181
+ this._fieldAriaController.setDescribedBy(this.accessibleDescriptionRef);
163
182
  }
164
183
 
165
- /**
166
- * @param {HTMLElement | null | undefined} target
167
- * @protected
168
- */
169
- _ariaTargetChanged(target) {
170
- if (target) {
171
- this._fieldAriaController.setTarget(target);
172
- }
184
+ #onLabelSlotContentChange(_event) {
185
+ this.__updateFieldAriaControllerLabelledBy();
173
186
  }
174
187
 
175
- /**
176
- * @param {boolean} required
177
- * @protected
178
- */
179
- _requiredChanged(required) {
180
- this._fieldAriaController.setRequired(required);
188
+ #onHelperSlotContentChange(event) {
189
+ const { hasContent } = event.detail;
190
+
191
+ this.toggleAttribute('has-helper', hasContent);
192
+
193
+ if (hasContent) {
194
+ this._fieldAriaController.setHelperId(this._helperNode?.id);
195
+ } else {
196
+ this._fieldAriaController.setHelperId(null);
197
+ }
181
198
  }
182
199
 
183
- /**
184
- * @param {boolean} invalid
185
- * @protected
186
- */
187
- _invalidChanged(invalid) {
188
- this._errorController.setInvalid(invalid);
200
+ #onErrorSlotContentChange(event) {
201
+ this.toggleAttribute('has-error-message', event.detail.hasContent);
189
202
 
190
203
  // This timeout is needed to prevent NVDA from announcing the error message twice:
191
204
  // 1. Once adding the `[role=alert]` attribute when updating `has-error-message` (OK).
@@ -194,9 +207,8 @@ export const FieldMixin = (superclass) =>
194
207
  setTimeout(() => {
195
208
  // Error message ID needs to be dynamically added / removed based on the validity
196
209
  // Otherwise assistive technologies would announce the error, even if we hide it.
197
- if (invalid) {
198
- const node = this._errorNode;
199
- this._fieldAriaController.setErrorId(node?.id);
210
+ if (this.invalid) {
211
+ this._fieldAriaController.setErrorId(this._errorNode?.id);
200
212
  } else {
201
213
  this._fieldAriaController.setErrorId(null);
202
214
  }
@@ -10,23 +10,21 @@
10
10
  export class LabelledInputController {
11
11
  constructor(input, labelController) {
12
12
  this.input = input;
13
- this.__preventDuplicateLabelClick = this.__preventDuplicateLabelClick.bind(this);
14
13
 
15
14
  labelController.addEventListener('slot-content-changed', (event) => {
16
- this.__initLabel(event.detail.node);
15
+ this.#initLabel(event.detail.node);
17
16
  });
18
17
 
19
18
  // Initialize the default label element
20
- this.__initLabel(labelController.node);
19
+ this.#initLabel(labelController.node);
21
20
  }
22
21
 
23
22
  /**
24
23
  * @param {HTMLElement} label
25
- * @private
26
24
  */
27
- __initLabel(label) {
25
+ #initLabel(label) {
28
26
  if (label) {
29
- label.addEventListener('click', this.__preventDuplicateLabelClick);
27
+ label.addEventListener('click', this.#preventDuplicateLabelClick);
30
28
 
31
29
  if (this.input) {
32
30
  label.setAttribute('for', this.input.id);
@@ -40,13 +38,12 @@ export class LabelledInputController {
40
38
  * This results in two click events arriving at the host, but we only want one.
41
39
  * This method prevents the duplicate click and ensures the correct isTrusted event
42
40
  * with the correct event.target arrives at the host.
43
- * @private
44
41
  */
45
- __preventDuplicateLabelClick() {
42
+ #preventDuplicateLabelClick = () => {
46
43
  const inputClickHandler = (e) => {
47
44
  e.stopImmediatePropagation();
48
45
  this.input.removeEventListener('click', inputClickHandler);
49
46
  };
50
47
  this.input.addEventListener('click', inputClickHandler);
51
- }
48
+ };
52
49
  }
@@ -18,10 +18,6 @@ export const checkable = (part, propName = part) => css`
18
18
  --_cursor: var(--vaadin-clickable-cursor);
19
19
  }
20
20
 
21
- :host([disabled]) {
22
- --_cursor: var(--vaadin-disabled-cursor);
23
- }
24
-
25
21
  :host(:not([has-label])) {
26
22
  column-gap: 0;
27
23
  }
@@ -41,7 +37,6 @@ export const checkable = (part, propName = part) => css`
41
37
  color: var(--vaadin-${unsafeCSS(propName)}-label-color, var(--vaadin-input-field-label-color, var(--vaadin-text-color)));
42
38
  word-break: break-word;
43
39
  cursor: var(--_cursor);
44
- /* TODO clicking the label part doesn't toggle the checked state, even though it triggers the active state */
45
40
  }
46
41
 
47
42
  [part='${unsafeCSS(part)}'],
@@ -95,8 +90,8 @@ export const checkable = (part, propName = part) => css`
95
90
  box-sizing: border-box;
96
91
  --_color: var(--vaadin-${unsafeCSS(propName)}-marker-color, var(--vaadin-${unsafeCSS(propName)}-background, var(--vaadin-background-color)));
97
92
  color: var(--_color);
98
- height: var(--vaadin-${unsafeCSS(propName)}-size, 1lh);
99
- width: var(--vaadin-${unsafeCSS(propName)}-size, 1lh);
93
+ height: var(--vaadin-${unsafeCSS(propName)}-size, round(1.125em, 2px));
94
+ width: var(--vaadin-${unsafeCSS(propName)}-size, round(1.125em, 2px));
100
95
  position: relative;
101
96
  cursor: var(--_cursor);
102
97
  display: flex;
@@ -109,10 +104,24 @@ export const checkable = (part, propName = part) => css`
109
104
  --vaadin-${unsafeCSS(propName)}-border-color: transparent;
110
105
  }
111
106
 
107
+ :host([readonly]) {
108
+ --vaadin-${unsafeCSS(part)}-background: transparent;
109
+ --vaadin-${unsafeCSS(part)}-border-color: var(--vaadin-border-color);
110
+ --vaadin-${unsafeCSS(part)}-marker-color: var(--vaadin-text-color);
111
+ --_border-style: dashed;
112
+ --_cursor: var(--vaadin-disabled-cursor);
113
+
114
+ [part='label'],
115
+ ::slotted(label) {
116
+ cursor: default;
117
+ }
118
+ }
119
+
112
120
  :host([disabled]) {
113
121
  --vaadin-${unsafeCSS(propName)}-background: var(--vaadin-input-field-disabled-background, var(--vaadin-background-container-strong));
114
122
  --vaadin-${unsafeCSS(propName)}-border-color: transparent;
115
123
  --vaadin-${unsafeCSS(propName)}-marker-color: var(--vaadin-text-color-disabled);
124
+ --_cursor: var(--vaadin-disabled-cursor);
116
125
  }
117
126
 
118
127
  /* Focus ring */
@@ -146,6 +155,30 @@ export const checkable = (part, propName = part) => css`
146
155
  opacity: 0;
147
156
  }
148
157
 
158
+ /* Reverse variant */
159
+ :host([theme~='reverse']) {
160
+ grid-template-columns: 1fr auto;
161
+
162
+ &::before {
163
+ display: none;
164
+ }
165
+
166
+ [part='label'],
167
+ [part='helper-text'],
168
+ [part='error-message'] {
169
+ grid-column: 1;
170
+ }
171
+
172
+ [part='${unsafeCSS(part)}'],
173
+ ::slotted(input) {
174
+ grid-column: 2;
175
+ }
176
+
177
+ ::slotted(input) {
178
+ margin-inline: calc(min(0px, (24px - 100%) / -2) - var(--vaadin-${unsafeCSS(propName)}-gap, var(--vaadin-gap-s))) 0 !important;
179
+ }
180
+ }
181
+
149
182
  @media (forced-colors: active) {
150
183
  :host(:is([checked], [indeterminate])) {
151
184
  --vaadin-${unsafeCSS(propName)}-border-color: CanvasText !important;
@@ -18,19 +18,18 @@ export class VirtualKeyboardController {
18
18
  host.addEventListener('opened-changed', () => {
19
19
  if (!host.opened) {
20
20
  // Prevent opening the virtual keyboard when the input gets re-focused on dropdown close
21
- this.__setVirtualKeyboardEnabled(false);
21
+ this.#setVirtualKeyboardEnabled(false);
22
22
  }
23
23
  });
24
24
 
25
25
  // Re-enable virtual keyboard on blur, so it gets opened when the field is focused again
26
- host.addEventListener('blur', () => this.__setVirtualKeyboardEnabled(true));
26
+ host.addEventListener('blur', () => this.#setVirtualKeyboardEnabled(true));
27
27
 
28
28
  // Re-enable the virtual keyboard whenever the field is touched
29
- host.addEventListener('touchstart', () => this.__setVirtualKeyboardEnabled(true));
29
+ host.addEventListener('touchstart', () => this.#setVirtualKeyboardEnabled(true));
30
30
  }
31
31
 
32
- /** @private */
33
- __setVirtualKeyboardEnabled(value) {
32
+ #setVirtualKeyboardEnabled(value) {
34
33
  if (this.host.inputElement) {
35
34
  this.host.inputElement.inputMode = value ? '' : 'none';
36
35
  }