@vaadin/form-layout 22.0.2 → 23.0.0-alpha4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/form-layout",
3
- "version": "22.0.2",
3
+ "version": "23.0.0-alpha4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -18,6 +18,7 @@
18
18
  },
19
19
  "main": "vaadin-form-layout.js",
20
20
  "module": "vaadin-form-layout.js",
21
+ "type": "module",
21
22
  "files": [
22
23
  "src",
23
24
  "theme",
@@ -32,18 +33,18 @@
32
33
  "polymer"
33
34
  ],
34
35
  "dependencies": {
35
- "@polymer/iron-resizable-behavior": "^3.0.0",
36
36
  "@polymer/polymer": "^3.0.0",
37
- "@vaadin/component-base": "^22.0.2",
38
- "@vaadin/vaadin-lumo-styles": "^22.0.2",
39
- "@vaadin/vaadin-material-styles": "^22.0.2",
40
- "@vaadin/vaadin-themable-mixin": "^22.0.2"
37
+ "@vaadin/component-base": "23.0.0-alpha4",
38
+ "@vaadin/vaadin-lumo-styles": "23.0.0-alpha4",
39
+ "@vaadin/vaadin-material-styles": "23.0.0-alpha4",
40
+ "@vaadin/vaadin-themable-mixin": "23.0.0-alpha4"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@esm-bundle/chai": "^4.3.4",
44
+ "@vaadin/custom-field": "23.0.0-alpha4",
44
45
  "@vaadin/testing-helpers": "^0.3.2",
45
- "@vaadin/text-field": "^22.0.2",
46
+ "@vaadin/text-field": "23.0.0-alpha4",
46
47
  "sinon": "^9.2.1"
47
48
  },
48
- "gitHead": "df21370c4a655a38eac11f79686021ab3b0887ad"
49
+ "gitHead": "81e2deee5147bb7c1f4884760f4598613306f1fb"
49
50
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @license
3
- * Copyright (c) 2021 Vaadin Ltd.
3
+ * Copyright (c) 2017 - 2022 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
@@ -9,7 +9,7 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
9
9
  * `<vaadin-form-item>` is a Web Component providing labelled form item wrapper
10
10
  * for using inside `<vaadin-form-layout>`.
11
11
  *
12
- * `<vaadin-form-item>` accepts any number of children as the input content,
12
+ * `<vaadin-form-item>` accepts a single child as the input content,
13
13
  * and also has a separate named `label` slot:
14
14
  *
15
15
  * ```html
@@ -19,19 +19,6 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
19
19
  * </vaadin-form-item>
20
20
  * ```
21
21
  *
22
- * Any content can be used. For instance, you can have multiple input elements
23
- * with surrounding text. The label can be an element of any type:
24
- *
25
- * ```html
26
- * <vaadin-form-item>
27
- * <span slot="label">Date of Birth</span>
28
- * <input placeholder="YYYY" size="4"> -
29
- * <input placeholder="MM" size="2"> -
30
- * <input placeholder="DD" size="2"><br>
31
- * <em>Example: 1900-01-01</em>
32
- * </vaadin-form-item>
33
- * ```
34
- *
35
22
  * The label is optional and can be omitted:
36
23
  *
37
24
  * ```html
@@ -58,7 +45,7 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
58
45
  * ### Input Width
59
46
  *
60
47
  * By default, `<vaadin-form-item>` does not manipulate the width of the slotted
61
- * input elements. Optionally you can stretch the child input element to fill
48
+ * input element. Optionally you can stretch the child input element to fill
62
49
  * the available width for the input content by adding the `full-width` class:
63
50
  *
64
51
  * ```html
@@ -97,7 +84,16 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
97
84
  *
98
85
  * See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
99
86
  */
100
- declare class FormItem extends ThemableMixin(HTMLElement) {}
87
+ declare class FormItem extends ThemableMixin(HTMLElement) {
88
+ /**
89
+ * Returns a target element to add ARIA attributes to for a field.
90
+ *
91
+ * - For Vaadin field components, the method returns an element
92
+ * obtained through the `ariaTarget` property defined in `FieldMixin`.
93
+ * - In other cases, the method returns the field element itself.
94
+ */
95
+ protected _getFieldAriaTarget(field: HTMLElement): HTMLElement;
96
+ }
101
97
 
102
98
  declare global {
103
99
  interface HTMLElementTagNameMap {
@@ -1,16 +1,17 @@
1
1
  /**
2
2
  * @license
3
- * Copyright (c) 2021 Vaadin Ltd.
3
+ * Copyright (c) 2017 - 2022 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
7
+ import { addValueToAttribute, removeValueFromAttribute } from '@vaadin/field-base/src/utils.js';
7
8
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
8
9
 
9
10
  /**
10
11
  * `<vaadin-form-item>` is a Web Component providing labelled form item wrapper
11
12
  * for using inside `<vaadin-form-layout>`.
12
13
  *
13
- * `<vaadin-form-item>` accepts any number of children as the input content,
14
+ * `<vaadin-form-item>` accepts a single child as the input content,
14
15
  * and also has a separate named `label` slot:
15
16
  *
16
17
  * ```html
@@ -20,19 +21,6 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
20
21
  * </vaadin-form-item>
21
22
  * ```
22
23
  *
23
- * Any content can be used. For instance, you can have multiple input elements
24
- * with surrounding text. The label can be an element of any type:
25
- *
26
- * ```html
27
- * <vaadin-form-item>
28
- * <span slot="label">Date of Birth</span>
29
- * <input placeholder="YYYY" size="4"> -
30
- * <input placeholder="MM" size="2"> -
31
- * <input placeholder="DD" size="2"><br>
32
- * <em>Example: 1900-01-01</em>
33
- * </vaadin-form-item>
34
- * ```
35
- *
36
24
  * The label is optional and can be omitted:
37
25
  *
38
26
  * ```html
@@ -59,7 +47,7 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
59
47
  * ### Input Width
60
48
  *
61
49
  * By default, `<vaadin-form-item>` does not manipulate the width of the slotted
62
- * input elements. Optionally you can stretch the child input element to fill
50
+ * input element. Optionally you can stretch the child input element to fill
63
51
  * the available width for the input content by adding the `full-width` class:
64
52
  *
65
53
  * ```html
@@ -145,12 +133,13 @@ class FormItem extends ThemableMixin(PolymerElement) {
145
133
  min-width: 0;
146
134
  }
147
135
  </style>
148
- <div id="label" part="label" on-click="_onLabelClick">
149
- <slot name="label" id="labelSlot"></slot>
136
+ <div id="label" part="label" on-click="__onLabelClick">
137
+ <slot name="label" id="labelSlot" on-slotchange="__onLabelSlotChange"></slot>
138
+ <span part="required-indicator" aria-hidden="true"></span>
150
139
  </div>
151
140
  <div id="spacing"></div>
152
141
  <div id="content">
153
- <slot id="contentSlot"></slot>
142
+ <slot id="contentSlot" on-slotchange="__onContentSlotChange"></slot>
154
143
  </div>
155
144
  `;
156
145
  }
@@ -159,17 +148,181 @@ class FormItem extends ThemableMixin(PolymerElement) {
159
148
  return 'vaadin-form-item';
160
149
  }
161
150
 
151
+ constructor() {
152
+ super();
153
+ this.__updateInvalidState = this.__updateInvalidState.bind(this);
154
+
155
+ /**
156
+ * An observer for a field node to reflect its `required` and `invalid` attributes to the component.
157
+ *
158
+ * @type {MutationObserver}
159
+ * @private
160
+ */
161
+ this.__fieldNodeObserver = new MutationObserver(() => this.__updateRequiredState(this.__fieldNode.required));
162
+
163
+ /**
164
+ * The first label node in the label slot.
165
+ *
166
+ * @type {HTMLElement | null}
167
+ * @private
168
+ */
169
+ this.__labelNode = null;
170
+
171
+ /**
172
+ * The first field node in the content slot.
173
+ *
174
+ * An element is considered a field when it has the `checkValidity` or `validate` method.
175
+ *
176
+ * @type {HTMLElement | null}
177
+ * @private
178
+ */
179
+ this.__fieldNode = null;
180
+
181
+ // Ensure every instance has unique ID
182
+ const uniqueId = (FormItem._uniqueLabelId = 1 + FormItem._uniqueLabelId || 0);
183
+ this.__labelId = `label-${this.localName}-${uniqueId}`;
184
+ }
185
+
186
+ /**
187
+ * Returns a target element to add ARIA attributes to for a field.
188
+ *
189
+ * - For Vaadin field components, the method returns an element
190
+ * obtained through the `ariaTarget` property defined in `FieldMixin`.
191
+ * - In other cases, the method returns the field element itself.
192
+ *
193
+ * @param {HTMLElement} field
194
+ * @protected
195
+ */
196
+ _getFieldAriaTarget(field) {
197
+ return field.ariaTarget || field;
198
+ }
199
+
200
+ /**
201
+ * Links the label to a field by adding the label id to
202
+ * the `aria-labelledby` attribute of the field's ARIA target element.
203
+ *
204
+ * @param {HTMLElement} field
205
+ * @private
206
+ */
207
+ __linkLabelToField(field) {
208
+ addValueToAttribute(this._getFieldAriaTarget(field), 'aria-labelledby', this.__labelId);
209
+ }
210
+
211
+ /**
212
+ * Unlinks the label from a field by removing the label id from
213
+ * the `aria-labelledby` attribute of the field's ARIA target element.
214
+ *
215
+ * @param {HTMLElement} field
216
+ * @private
217
+ */
218
+ __unlinkLabelFromField(field) {
219
+ removeValueFromAttribute(this._getFieldAriaTarget(field), 'aria-labelledby', this.__labelId);
220
+ }
221
+
162
222
  /** @private */
163
- _onLabelClick() {
164
- const firstContentElementChild = Array.prototype.find.call(
165
- this.$.contentSlot.assignedNodes(),
166
- (e) => e.nodeType === Node.ELEMENT_NODE
167
- );
168
- if (firstContentElementChild) {
169
- firstContentElementChild.focus();
170
- firstContentElementChild.click();
223
+ __onLabelClick() {
224
+ const fieldNode = this.__fieldNode;
225
+ if (fieldNode) {
226
+ fieldNode.focus();
227
+ fieldNode.click();
171
228
  }
172
229
  }
230
+
231
+ /** @private */
232
+ __getValidateFunction(field) {
233
+ return field.validate || field.checkValidity;
234
+ }
235
+
236
+ /**
237
+ * A `slotchange` event handler for the label slot.
238
+ *
239
+ * - Ensures the label id is only assigned to the first label node.
240
+ * - Ensures the label node is linked to the first field node via the `aria-labelledby` attribute
241
+ * if both nodes are provided, and unlinked otherwise.
242
+ *
243
+ * @private
244
+ */
245
+ __onLabelSlotChange() {
246
+ if (this.__labelNode) {
247
+ this.__labelNode.id = '';
248
+ this.__labelNode = null;
249
+
250
+ if (this.__fieldNode) {
251
+ this.__unlinkLabelFromField(this.__fieldNode);
252
+ }
253
+ }
254
+
255
+ const newLabelNode = this.$.labelSlot.assignedElements()[0];
256
+ if (newLabelNode) {
257
+ this.__labelNode = newLabelNode;
258
+ this.__labelNode.id = this.__labelId;
259
+
260
+ if (this.__fieldNode) {
261
+ this.__linkLabelToField(this.__fieldNode);
262
+ }
263
+ }
264
+ }
265
+
266
+ /**
267
+ * A `slotchange` event handler for the content slot.
268
+ *
269
+ * - Ensures the label node is only linked to the first field node via the `aria-labelledby` attribute.
270
+ * - Sets up an observer for the `required` attribute changes on the first field
271
+ * to reflect the attribute on the component. Ensures the observer is disconnected from the field
272
+ * as soon as it is removed or replaced by another one.
273
+ *
274
+ * @private
275
+ */
276
+ __onContentSlotChange() {
277
+ if (this.__fieldNode) {
278
+ // Discard the old field
279
+ this.__unlinkLabelFromField(this.__fieldNode);
280
+ this.__updateRequiredState(false);
281
+ this.__fieldNodeObserver.disconnect();
282
+ this.__fieldNode = null;
283
+ }
284
+
285
+ const fieldNodes = this.$.contentSlot.assignedElements();
286
+ if (fieldNodes.length > 1) {
287
+ console.warn(
288
+ `WARNING: Since Vaadin 23, placing multiple fields directly to a <vaadin-form-item> is deprecated.
289
+ Please wrap fields with a <vaadin-custom-field> instead.`
290
+ );
291
+ }
292
+
293
+ const newFieldNode = fieldNodes.find((field) => {
294
+ return !!this.__getValidateFunction(field);
295
+ });
296
+ if (newFieldNode) {
297
+ this.__fieldNode = newFieldNode;
298
+ this.__updateRequiredState(this.__fieldNode.required);
299
+ this.__fieldNodeObserver.observe(this.__fieldNode, { attributes: true, attributeFilter: ['required'] });
300
+
301
+ if (this.__labelNode) {
302
+ this.__linkLabelToField(this.__fieldNode);
303
+ }
304
+ }
305
+ }
306
+
307
+ /** @private */
308
+ __updateRequiredState(required) {
309
+ if (required) {
310
+ this.setAttribute('required', '');
311
+ this.__fieldNode.addEventListener('blur', this.__updateInvalidState);
312
+ this.__fieldNode.addEventListener('change', this.__updateInvalidState);
313
+ } else {
314
+ this.removeAttribute('invalid');
315
+ this.removeAttribute('required');
316
+ this.__fieldNode.removeEventListener('blur', this.__updateInvalidState);
317
+ this.__fieldNode.removeEventListener('change', this.__updateInvalidState);
318
+ }
319
+ }
320
+
321
+ /** @private */
322
+ __updateInvalidState() {
323
+ const isValid = this.__getValidateFunction(this.__fieldNode).call(this.__fieldNode);
324
+ this.toggleAttribute('invalid', isValid === false);
325
+ }
173
326
  }
174
327
 
175
328
  customElements.define(FormItem.is, FormItem);
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * @license
3
- * Copyright (c) 2021 Vaadin Ltd.
3
+ * Copyright (c) 2017 - 2022 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
7
+ import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
7
8
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
8
9
 
9
10
  export type FormLayoutLabelsPosition = 'aside' | 'top';
@@ -104,7 +105,7 @@ export type FormLayoutResponsiveStep = {
104
105
  * ---|---|---
105
106
  * `--vaadin-form-layout-column-spacing` | Length of the spacing between columns | `2em`
106
107
  */
107
- declare class FormLayout extends ElementMixin(ThemableMixin(HTMLElement)) {
108
+ declare class FormLayout extends ResizeMixin(ElementMixin(ThemableMixin(HTMLElement))) {
108
109
  /**
109
110
  * Allows specifying a responsive behavior with the number of columns
110
111
  * and the label position depending on the layout width.
@@ -146,8 +147,16 @@ declare class FormLayout extends ElementMixin(ThemableMixin(HTMLElement)) {
146
147
 
147
148
  /**
148
149
  * Set custom CSS property values and update the layout.
150
+ *
151
+ * @deprecated Since Vaadin 23, `updateStyles()` is deprecated.
152
+ * Use the native element.style.setProperty API to set custom CSS property values.
149
153
  */
150
154
  updateStyles(properties?: { [key: string]: string }): void;
155
+
156
+ /**
157
+ * Update the layout.
158
+ */
159
+ protected _updateLayout(): void;
151
160
  }
152
161
 
153
162
  declare global {
@@ -1,14 +1,12 @@
1
1
  /**
2
2
  * @license
3
- * Copyright (c) 2021 Vaadin Ltd.
3
+ * Copyright (c) 2017 - 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 { IronResizableBehavior } from '@polymer/iron-resizable-behavior/iron-resizable-behavior.js';
7
- import { mixinBehaviors } from '@polymer/polymer/lib/legacy/class.js';
8
6
  import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js';
9
- import { beforeNextRender } from '@polymer/polymer/lib/utils/render-status.js';
10
7
  import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
11
8
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
9
+ import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
12
10
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
13
11
 
14
12
  /**
@@ -104,8 +102,9 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
104
102
  * @extends HTMLElement
105
103
  * @mixes ElementMixin
106
104
  * @mixes ThemableMixin
105
+ * @mixes ResizeMixin
107
106
  */
108
- class FormLayout extends ElementMixin(ThemableMixin(mixinBehaviors([IronResizableBehavior], PolymerElement))) {
107
+ class FormLayout extends ResizeMixin(ElementMixin(ThemableMixin(PolymerElement))) {
109
108
  static get template() {
110
109
  return html`
111
110
  <style>
@@ -243,7 +242,7 @@ class FormLayout extends ElementMixin(ThemableMixin(mixinBehaviors([IronResizabl
243
242
  }
244
243
 
245
244
  static get observers() {
246
- return ['_invokeUpdateStyles(_columnCount, _labelsOnTop)'];
245
+ return ['_invokeUpdateLayout(_columnCount, _labelsOnTop)'];
247
246
  }
248
247
 
249
248
  /** @protected */
@@ -254,13 +253,12 @@ class FormLayout extends ElementMixin(ThemableMixin(mixinBehaviors([IronResizabl
254
253
  // `super.ready()`, because `super.ready()` invokes property observers,
255
254
  // and the observer for `responsiveSteps` does CSS value validation.
256
255
  this._styleElement = document.createElement('style');
257
- this.root.appendChild(this._styleElement);
256
+ this.appendChild(this._styleElement);
258
257
  // Ensure there is a child text node in the style element
259
258
  this._styleElement.textContent = ' ';
260
259
 
261
260
  super.ready();
262
261
 
263
- this.addEventListener('iron-resize', this._selectResponsiveStep);
264
262
  this.addEventListener('animationend', this.__onAnimationEnd);
265
263
  }
266
264
 
@@ -268,8 +266,8 @@ class FormLayout extends ElementMixin(ThemableMixin(mixinBehaviors([IronResizabl
268
266
  connectedCallback() {
269
267
  super.connectedCallback();
270
268
 
271
- beforeNextRender(this, this._selectResponsiveStep);
272
- beforeNextRender(this, this.updateStyles);
269
+ requestAnimationFrame(() => this._selectResponsiveStep());
270
+ requestAnimationFrame(() => this._updateLayout());
273
271
 
274
272
  this._observeChildrenColspanChange();
275
273
  }
@@ -293,7 +291,7 @@ class FormLayout extends ElementMixin(ThemableMixin(mixinBehaviors([IronResizabl
293
291
  mutation.type === 'attributes' &&
294
292
  (mutation.attributeName === 'colspan' || mutation.attributeName === 'hidden')
295
293
  ) {
296
- this.updateStyles();
294
+ this._updateLayout();
297
295
  }
298
296
  });
299
297
  });
@@ -307,7 +305,7 @@ class FormLayout extends ElementMixin(ThemableMixin(mixinBehaviors([IronResizabl
307
305
  });
308
306
 
309
307
  if (addedNodes.length > 0 || removedNodes.length > 0) {
310
- this.updateStyles();
308
+ this._updateLayout();
311
309
  }
312
310
  });
313
311
  }
@@ -433,18 +431,33 @@ class FormLayout extends ElementMixin(ThemableMixin(mixinBehaviors([IronResizabl
433
431
  }
434
432
 
435
433
  /** @private */
436
- _invokeUpdateStyles() {
437
- this.updateStyles();
434
+ _invokeUpdateLayout() {
435
+ this._updateLayout();
438
436
  }
439
437
 
440
438
  /**
441
439
  * Set custom CSS property values and update the layout.
442
- * @param {Object<string, string>=} properties
443
- * @protected
440
+ *
441
+ * @deprecated Since Vaadin 23, `updateStyles()` is deprecated.
442
+ * Use the native element.style.setProperty API to set custom CSS property values.
444
443
  */
445
- updateStyles(properties) {
446
- super.updateStyles(properties);
444
+ updateStyles(properties = {}) {
445
+ console.warn(
446
+ `WARNING: Since Vaadin 23, updateStyles() is deprecated. Use the native element.style.setProperty API to set custom CSS property values.`
447
+ );
448
+
449
+ Object.entries(properties).forEach(([key, value]) => {
450
+ this.style.setProperty(key, value);
451
+ });
447
452
 
453
+ this._updateLayout();
454
+ }
455
+
456
+ /**
457
+ * Update the layout.
458
+ * @protected
459
+ */
460
+ _updateLayout() {
448
461
  /*
449
462
  The item width formula:
450
463
 
@@ -525,6 +538,14 @@ class FormLayout extends ElementMixin(ThemableMixin(mixinBehaviors([IronResizabl
525
538
  }
526
539
  });
527
540
  }
541
+
542
+ /**
543
+ * @protected
544
+ * @override
545
+ */
546
+ _onResize() {
547
+ this._selectResponsiveStep();
548
+ }
528
549
  }
529
550
 
530
551
  customElements.define(FormLayout.is, FormLayout);
@@ -23,6 +23,24 @@ registerStyles(
23
23
  transition: color 0.4s;
24
24
  line-height: 1.333;
25
25
  }
26
+
27
+ [part='required-indicator']::after {
28
+ content: var(--lumo-required-field-indicator, '•');
29
+ transition: opacity 0.2s;
30
+ opacity: 0;
31
+ color: var(--lumo-required-field-indicator-color, var(--lumo-primary-text-color));
32
+ position: relative;
33
+ width: 1em;
34
+ text-align: center;
35
+ }
36
+
37
+ :host([required]) [part='required-indicator']::after {
38
+ opacity: 1;
39
+ }
40
+
41
+ :host([invalid]) [part='required-indicator']::after {
42
+ color: var(--lumo-required-field-indicator-color, var(--lumo-error-text-color));
43
+ }
26
44
  `,
27
45
  { moduleId: 'lumo-form-item' }
28
46
  );
@@ -14,6 +14,15 @@ registerStyles(
14
14
  margin-top: 16px;
15
15
  margin-bottom: 8px;
16
16
  }
17
+
18
+ :host([required]) [part='required-indicator']::after {
19
+ content: ' *';
20
+ color: inherit;
21
+ }
22
+
23
+ :host([invalid]) [part='label'] {
24
+ color: var(--material-error-text-color);
25
+ }
17
26
  `,
18
27
  { moduleId: 'material-form-item' }
19
28
  );