@vaadin/form-layout 24.7.0-alpha1 → 24.7.0-alpha10
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 +14 -11
- package/src/vaadin-form-item-mixin.d.ts +22 -0
- package/src/vaadin-form-item-mixin.js +227 -0
- package/src/vaadin-form-item.d.ts +14 -14
- package/src/vaadin-form-item.js +23 -232
- package/src/vaadin-form-layout-mixin.d.ts +68 -0
- package/src/vaadin-form-layout-mixin.js +330 -0
- package/src/vaadin-form-layout-styles.d.ts +10 -0
- package/src/vaadin-form-layout-styles.js +88 -0
- package/src/vaadin-form-layout.d.ts +5 -54
- package/src/vaadin-form-layout.js +15 -470
- package/src/vaadin-lit-form-item.d.ts +6 -0
- package/src/vaadin-lit-form-item.js +48 -0
- package/src/vaadin-lit-form-layout.d.ts +6 -0
- package/src/vaadin-lit-form-layout.js +44 -0
- package/theme/lumo/vaadin-form-item-styles.js +0 -4
- package/theme/lumo/vaadin-form-layout-styles.js +1 -0
- package/theme/lumo/vaadin-lit-form-item.d.ts +2 -0
- package/theme/lumo/vaadin-lit-form-item.js +2 -0
- package/theme/lumo/vaadin-lit-form-layout.d.ts +2 -0
- package/theme/lumo/vaadin-lit-form-layout.js +2 -0
- package/theme/material/vaadin-lit-form-item.d.ts +2 -0
- package/theme/material/vaadin-lit-form-item.js +2 -0
- package/theme/material/vaadin-lit-form-layout.d.ts +1 -0
- package/theme/material/vaadin-lit-form-layout.js +1 -0
- package/vaadin-lit-form-item.d.ts +1 -0
- package/vaadin-lit-form-item.js +2 -0
- package/vaadin-lit-form-layout.d.ts +1 -0
- package/vaadin-lit-form-layout.js +2 -0
- package/web-types.json +3 -3
- package/web-types.lit.json +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/form-layout",
|
|
3
|
-
"version": "24.7.0-
|
|
3
|
+
"version": "24.7.0-alpha10",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -35,23 +35,26 @@
|
|
|
35
35
|
"polymer"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
+
"@open-wc/dedupe-mixin": "^1.3.0",
|
|
38
39
|
"@polymer/polymer": "^3.0.0",
|
|
39
|
-
"@vaadin/a11y-base": "24.7.0-
|
|
40
|
-
"@vaadin/component-base": "24.7.0-
|
|
41
|
-
"@vaadin/vaadin-lumo-styles": "24.7.0-
|
|
42
|
-
"@vaadin/vaadin-material-styles": "24.7.0-
|
|
43
|
-
"@vaadin/vaadin-themable-mixin": "24.7.0-
|
|
40
|
+
"@vaadin/a11y-base": "24.7.0-alpha10",
|
|
41
|
+
"@vaadin/component-base": "24.7.0-alpha10",
|
|
42
|
+
"@vaadin/vaadin-lumo-styles": "24.7.0-alpha10",
|
|
43
|
+
"@vaadin/vaadin-material-styles": "24.7.0-alpha10",
|
|
44
|
+
"@vaadin/vaadin-themable-mixin": "24.7.0-alpha10",
|
|
45
|
+
"lit": "^3.0.0"
|
|
44
46
|
},
|
|
45
47
|
"devDependencies": {
|
|
46
|
-
"@vaadin/chai-plugins": "24.7.0-
|
|
47
|
-
"@vaadin/custom-field": "24.7.0-
|
|
48
|
-
"@vaadin/
|
|
49
|
-
"@vaadin/
|
|
48
|
+
"@vaadin/chai-plugins": "24.7.0-alpha10",
|
|
49
|
+
"@vaadin/custom-field": "24.7.0-alpha10",
|
|
50
|
+
"@vaadin/test-runner-commands": "24.7.0-alpha10",
|
|
51
|
+
"@vaadin/testing-helpers": "^1.1.0",
|
|
52
|
+
"@vaadin/text-field": "24.7.0-alpha10",
|
|
50
53
|
"sinon": "^18.0.0"
|
|
51
54
|
},
|
|
52
55
|
"web-types": [
|
|
53
56
|
"web-types.json",
|
|
54
57
|
"web-types.lit.json"
|
|
55
58
|
],
|
|
56
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "c0f8933df2a6a40648d3fb9cfbae6bbf86a8aa90"
|
|
57
60
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2017 - 2025 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
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A mixin providing common form-item functionality.
|
|
10
|
+
*/
|
|
11
|
+
export declare function FormItemMixin<T extends Constructor<HTMLElement>>(base: T): Constructor<FormItemMixinClass> & T;
|
|
12
|
+
|
|
13
|
+
export declare class FormItemMixinClass {
|
|
14
|
+
/**
|
|
15
|
+
* Returns a target element to add ARIA attributes to for a field.
|
|
16
|
+
*
|
|
17
|
+
* - For Vaadin field components, the method returns an element
|
|
18
|
+
* obtained through the `ariaTarget` property defined in `FieldMixin`.
|
|
19
|
+
* - In other cases, the method returns the field element itself.
|
|
20
|
+
*/
|
|
21
|
+
protected _getFieldAriaTarget(field: HTMLElement): HTMLElement;
|
|
22
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2017 - 2025 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
import { addValueToAttribute, removeValueFromAttribute } from '@vaadin/component-base/src/dom-utils.js';
|
|
7
|
+
import { generateUniqueId } from '@vaadin/component-base/src/unique-id-utils.js';
|
|
8
|
+
|
|
9
|
+
let spacingDeprecationNotified = false;
|
|
10
|
+
let labelWidthDeprecationNotified = false;
|
|
11
|
+
let labelSpacingDeprecationNotified = false;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @polymerMixin
|
|
15
|
+
*/
|
|
16
|
+
export const FormItemMixin = (superClass) =>
|
|
17
|
+
class extends superClass {
|
|
18
|
+
constructor() {
|
|
19
|
+
super();
|
|
20
|
+
this.__updateInvalidState = this.__updateInvalidState.bind(this);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* An observer for a field node to reflect its `required` and `invalid` attributes to the component.
|
|
24
|
+
*
|
|
25
|
+
* @type {MutationObserver}
|
|
26
|
+
* @private
|
|
27
|
+
*/
|
|
28
|
+
this.__fieldNodeObserver = new MutationObserver(() => this.__updateRequiredState(this.__fieldNode.required));
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The first label node in the label slot.
|
|
32
|
+
*
|
|
33
|
+
* @type {HTMLElement | null}
|
|
34
|
+
* @private
|
|
35
|
+
*/
|
|
36
|
+
this.__labelNode = null;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The first field node in the content slot.
|
|
40
|
+
*
|
|
41
|
+
* An element is considered a field when it has the `checkValidity` or `validate` method.
|
|
42
|
+
*
|
|
43
|
+
* @type {HTMLElement | null}
|
|
44
|
+
* @private
|
|
45
|
+
*/
|
|
46
|
+
this.__fieldNode = null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** @protected */
|
|
50
|
+
ready() {
|
|
51
|
+
super.ready();
|
|
52
|
+
|
|
53
|
+
const computedStyle = getComputedStyle(this);
|
|
54
|
+
const spacing = computedStyle.getPropertyValue('--vaadin-form-item-row-spacing');
|
|
55
|
+
const labelWidth = computedStyle.getPropertyValue('--vaadin-form-item-label-width');
|
|
56
|
+
const labelSpacing = computedStyle.getPropertyValue('--vaadin-form-item-label-spacing');
|
|
57
|
+
|
|
58
|
+
if (!spacingDeprecationNotified && spacing !== '' && parseInt(spacing) !== 0) {
|
|
59
|
+
console.warn(
|
|
60
|
+
'`--vaadin-form-item-row-spacing` is deprecated since 24.7. Use `--vaadin-form-layout-row-spacing` on <vaadin-form-layout> instead.',
|
|
61
|
+
);
|
|
62
|
+
spacingDeprecationNotified = true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!labelWidthDeprecationNotified && labelWidth !== '' && parseInt(labelWidth) !== 0) {
|
|
66
|
+
console.warn(
|
|
67
|
+
'`--vaadin-form-item-label-width` is deprecated since 24.7. Use `--vaadin-form-layout-label-width` on <vaadin-form-layout> instead.',
|
|
68
|
+
);
|
|
69
|
+
labelWidthDeprecationNotified = true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!labelSpacingDeprecationNotified && labelSpacing !== '' && parseInt(labelSpacing) !== 0) {
|
|
73
|
+
console.warn(
|
|
74
|
+
'`--vaadin-form-item-label-spacing` is deprecated since 24.7. Use `--vaadin-form-layout-label-spacing` on <vaadin-form-layout> instead.',
|
|
75
|
+
);
|
|
76
|
+
labelSpacingDeprecationNotified = true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Returns a target element to add ARIA attributes to for a field.
|
|
82
|
+
*
|
|
83
|
+
* - For Vaadin field components, the method returns an element
|
|
84
|
+
* obtained through the `ariaTarget` property defined in `FieldMixin`.
|
|
85
|
+
* - In other cases, the method returns the field element itself.
|
|
86
|
+
*
|
|
87
|
+
* @param {HTMLElement} field
|
|
88
|
+
* @protected
|
|
89
|
+
*/
|
|
90
|
+
_getFieldAriaTarget(field) {
|
|
91
|
+
return field.ariaTarget || field;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Links the label to a field by adding the label id to
|
|
96
|
+
* the `aria-labelledby` attribute of the field's ARIA target element.
|
|
97
|
+
*
|
|
98
|
+
* @param {HTMLElement} field
|
|
99
|
+
* @private
|
|
100
|
+
*/
|
|
101
|
+
__linkLabelToField(field) {
|
|
102
|
+
addValueToAttribute(this._getFieldAriaTarget(field), 'aria-labelledby', this.__labelId);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Unlinks the label from a field by removing the label id from
|
|
107
|
+
* the `aria-labelledby` attribute of the field's ARIA target element.
|
|
108
|
+
*
|
|
109
|
+
* @param {HTMLElement} field
|
|
110
|
+
* @private
|
|
111
|
+
*/
|
|
112
|
+
__unlinkLabelFromField(field) {
|
|
113
|
+
removeValueFromAttribute(this._getFieldAriaTarget(field), 'aria-labelledby', this.__labelId);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** @private */
|
|
117
|
+
__onLabelClick() {
|
|
118
|
+
const fieldNode = this.__fieldNode;
|
|
119
|
+
if (fieldNode) {
|
|
120
|
+
fieldNode.focus();
|
|
121
|
+
fieldNode.click();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** @private */
|
|
126
|
+
__getValidateFunction(field) {
|
|
127
|
+
return field.validate || field.checkValidity;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* A `slotchange` event handler for the label slot.
|
|
132
|
+
*
|
|
133
|
+
* - Ensures the label id is only assigned to the first label node.
|
|
134
|
+
* - Ensures the label node is linked to the first field node via the `aria-labelledby` attribute
|
|
135
|
+
* if both nodes are provided, and unlinked otherwise.
|
|
136
|
+
*
|
|
137
|
+
* @private
|
|
138
|
+
*/
|
|
139
|
+
__onLabelSlotChange() {
|
|
140
|
+
if (this.__labelNode) {
|
|
141
|
+
this.__labelNode = null;
|
|
142
|
+
|
|
143
|
+
if (this.__fieldNode) {
|
|
144
|
+
this.__unlinkLabelFromField(this.__fieldNode);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const newLabelNode = this.$.labelSlot.assignedElements()[0];
|
|
149
|
+
if (newLabelNode) {
|
|
150
|
+
this.__labelNode = newLabelNode;
|
|
151
|
+
|
|
152
|
+
if (this.__labelNode.id) {
|
|
153
|
+
// The new label node already has an id. Let's use it.
|
|
154
|
+
this.__labelId = this.__labelNode.id;
|
|
155
|
+
} else {
|
|
156
|
+
// The new label node doesn't have an id yet. Generate a unique one.
|
|
157
|
+
this.__labelId = `label-${this.localName}-${generateUniqueId()}`;
|
|
158
|
+
this.__labelNode.id = this.__labelId;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (this.__fieldNode) {
|
|
162
|
+
this.__linkLabelToField(this.__fieldNode);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* A `slotchange` event handler for the content slot.
|
|
169
|
+
*
|
|
170
|
+
* - Ensures the label node is only linked to the first field node via the `aria-labelledby` attribute.
|
|
171
|
+
* - Sets up an observer for the `required` attribute changes on the first field
|
|
172
|
+
* to reflect the attribute on the component. Ensures the observer is disconnected from the field
|
|
173
|
+
* as soon as it is removed or replaced by another one.
|
|
174
|
+
*
|
|
175
|
+
* @private
|
|
176
|
+
*/
|
|
177
|
+
__onContentSlotChange() {
|
|
178
|
+
if (this.__fieldNode) {
|
|
179
|
+
// Discard the old field
|
|
180
|
+
this.__unlinkLabelFromField(this.__fieldNode);
|
|
181
|
+
this.__updateRequiredState(false);
|
|
182
|
+
this.__fieldNodeObserver.disconnect();
|
|
183
|
+
this.__fieldNode = null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const fieldNodes = this.$.contentSlot.assignedElements();
|
|
187
|
+
if (fieldNodes.length > 1) {
|
|
188
|
+
console.warn(
|
|
189
|
+
`WARNING: Since Vaadin 23, placing multiple fields directly to a <vaadin-form-item> is deprecated.
|
|
190
|
+
Please wrap fields with a <vaadin-custom-field> instead.`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const newFieldNode = fieldNodes.find((field) => {
|
|
195
|
+
return !!this.__getValidateFunction(field);
|
|
196
|
+
});
|
|
197
|
+
if (newFieldNode) {
|
|
198
|
+
this.__fieldNode = newFieldNode;
|
|
199
|
+
this.__updateRequiredState(this.__fieldNode.required);
|
|
200
|
+
this.__fieldNodeObserver.observe(this.__fieldNode, { attributes: true, attributeFilter: ['required'] });
|
|
201
|
+
|
|
202
|
+
if (this.__labelNode) {
|
|
203
|
+
this.__linkLabelToField(this.__fieldNode);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** @private */
|
|
209
|
+
__updateRequiredState(required) {
|
|
210
|
+
if (required) {
|
|
211
|
+
this.setAttribute('required', '');
|
|
212
|
+
this.__fieldNode.addEventListener('blur', this.__updateInvalidState);
|
|
213
|
+
this.__fieldNode.addEventListener('change', this.__updateInvalidState);
|
|
214
|
+
} else {
|
|
215
|
+
this.removeAttribute('invalid');
|
|
216
|
+
this.removeAttribute('required');
|
|
217
|
+
this.__fieldNode.removeEventListener('blur', this.__updateInvalidState);
|
|
218
|
+
this.__fieldNode.removeEventListener('change', this.__updateInvalidState);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** @private */
|
|
223
|
+
__updateInvalidState() {
|
|
224
|
+
const isValid = this.__getValidateFunction(this.__fieldNode).call(this.__fieldNode);
|
|
225
|
+
this.toggleAttribute('invalid', isValid === false);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c) 2017 -
|
|
3
|
+
* Copyright (c) 2017 - 2025 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';
|
|
7
|
+
import { FormItemMixin } from './vaadin-form-item-mixin.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* `<vaadin-form-item>` is a Web Component providing labelled form item wrapper
|
|
@@ -42,6 +43,10 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
|
|
|
42
43
|
* because the `label-position` attribute is triggered automatically by the parent
|
|
43
44
|
* `<vaadin-form-layout>`, depending on its width and responsive behavior.
|
|
44
45
|
*
|
|
46
|
+
* **Deprecation note:** The `label-position` attribute is deprecated since 24.7 and
|
|
47
|
+
* will be removed in Vaadin 25, when a new approach for setting the label position
|
|
48
|
+
* will be introduced.
|
|
49
|
+
*
|
|
45
50
|
* ### Input Width
|
|
46
51
|
*
|
|
47
52
|
* By default, `<vaadin-form-item>` does not manipulate the width of the slotted
|
|
@@ -65,6 +70,10 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
|
|
|
65
70
|
* }
|
|
66
71
|
* ```
|
|
67
72
|
*
|
|
73
|
+
* **Deprecation note:** The `label-position` attribute is deprecated since 24.7 and
|
|
74
|
+
* will be removed in Vaadin 25, when a new approach to styling the form-item
|
|
75
|
+
* based on the label position will be introduced.
|
|
76
|
+
*
|
|
68
77
|
* The following shadow DOM parts are available for styling:
|
|
69
78
|
*
|
|
70
79
|
* Part name | Description
|
|
@@ -78,22 +87,13 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
|
|
|
78
87
|
*
|
|
79
88
|
* Custom CSS property | Description | Default
|
|
80
89
|
* ---|---|---
|
|
81
|
-
* `--vaadin-form-item-label-width` | Width of the label column when the labels are aside | `8em`
|
|
82
|
-
* `--vaadin-form-item-label-spacing` | Spacing between the label column and the input column when the labels are aside | `1em`
|
|
83
|
-
* `--vaadin-form-item-row-spacing` | Height of the spacing between the form item elements | `1em`
|
|
90
|
+
* `--vaadin-form-item-label-width` | (DEPRECATED: Use `--vaadin-form-layout-label-width` on `<vaadin-form-layout>` instead) Width of the label column when the labels are aside | `8em`
|
|
91
|
+
* `--vaadin-form-item-label-spacing` | (DEPRECATED: Use `--vaadin-form-layout-label-spacing` on `<vaadin-form-layout>` instead) Spacing between the label column and the input column when the labels are aside | `1em`
|
|
92
|
+
* `--vaadin-form-item-row-spacing` | (DEPRECATED: Use `--vaadin-form-layout-row-spacing` on `<vaadin-form-layout>` instead) Height of the spacing between the form item elements | `1em`
|
|
84
93
|
*
|
|
85
94
|
* See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
|
|
86
95
|
*/
|
|
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
|
-
}
|
|
96
|
+
declare class FormItem extends FormItemMixin(ThemableMixin(HTMLElement)) {}
|
|
97
97
|
|
|
98
98
|
declare global {
|
|
99
99
|
interface HTMLElementTagNameMap {
|
package/src/vaadin-form-item.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c) 2017 -
|
|
3
|
+
* Copyright (c) 2017 - 2025 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
7
|
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { registerStyles, ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
9
|
+
import { FormItemMixin } from './vaadin-form-item-mixin.js';
|
|
10
|
+
import { formItemStyles } from './vaadin-form-layout-styles.js';
|
|
11
|
+
|
|
12
|
+
registerStyles('vaadin-form-item', formItemStyles, { moduleId: 'vaadin-form-item-styles' });
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* `<vaadin-form-item>` is a Web Component providing labelled form item wrapper
|
|
@@ -46,6 +48,10 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
|
|
|
46
48
|
* because the `label-position` attribute is triggered automatically by the parent
|
|
47
49
|
* `<vaadin-form-layout>`, depending on its width and responsive behavior.
|
|
48
50
|
*
|
|
51
|
+
* **Deprecation note:** The `label-position` attribute is deprecated since 24.7 and
|
|
52
|
+
* will be removed in Vaadin 25, when a new approach for setting the label position
|
|
53
|
+
* will be introduced.
|
|
54
|
+
*
|
|
49
55
|
* ### Input Width
|
|
50
56
|
*
|
|
51
57
|
* By default, `<vaadin-form-item>` does not manipulate the width of the slotted
|
|
@@ -69,6 +75,10 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
|
|
|
69
75
|
* }
|
|
70
76
|
* ```
|
|
71
77
|
*
|
|
78
|
+
* **Deprecation note:** The `label-position` attribute is deprecated since 24.7 and
|
|
79
|
+
* will be removed in Vaadin 25, when a new approach to styling the form-item
|
|
80
|
+
* based on the label position will be introduced.
|
|
81
|
+
*
|
|
72
82
|
* The following shadow DOM parts are available for styling:
|
|
73
83
|
*
|
|
74
84
|
* Part name | Description
|
|
@@ -82,60 +92,24 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
|
|
|
82
92
|
*
|
|
83
93
|
* Custom CSS property | Description | Default
|
|
84
94
|
* ---|---|---
|
|
85
|
-
* `--vaadin-form-item-label-width` | Width of the label column when the labels are aside | `8em`
|
|
86
|
-
* `--vaadin-form-item-label-spacing` | Spacing between the label column and the input column when the labels are aside | `1em`
|
|
87
|
-
* `--vaadin-form-item-row-spacing` | Height of the spacing between the form item elements | `1em`
|
|
95
|
+
* `--vaadin-form-item-label-width` | (DEPRECATED: Use `--vaadin-form-layout-label-width` on `<vaadin-form-layout>` instead) Width of the label column when the labels are aside | `8em`
|
|
96
|
+
* `--vaadin-form-item-label-spacing` | (DEPRECATED: Use `--vaadin-form-layout-label-spacing` on `<vaadin-form-layout>` instead) Spacing between the label column and the input column when the labels are aside | `1em`
|
|
97
|
+
* `--vaadin-form-item-row-spacing` | (DEPRECATED: Use `--vaadin-form-layout-row-spacing` on `<vaadin-form-layout>` instead) Height of the spacing between the form item elements | `1em`
|
|
88
98
|
*
|
|
89
99
|
* See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
|
|
90
100
|
*
|
|
91
101
|
* @customElement
|
|
92
102
|
* @extends HTMLElement
|
|
103
|
+
* @mixes FormItemMixin
|
|
93
104
|
* @mixes ThemableMixin
|
|
94
105
|
*/
|
|
95
|
-
class FormItem extends ThemableMixin(PolymerElement) {
|
|
106
|
+
class FormItem extends FormItemMixin(ThemableMixin(PolymerElement)) {
|
|
107
|
+
static get is() {
|
|
108
|
+
return 'vaadin-form-item';
|
|
109
|
+
}
|
|
110
|
+
|
|
96
111
|
static get template() {
|
|
97
112
|
return html`
|
|
98
|
-
<style>
|
|
99
|
-
:host {
|
|
100
|
-
display: inline-flex;
|
|
101
|
-
flex-direction: row;
|
|
102
|
-
align-items: baseline;
|
|
103
|
-
margin: calc(0.5 * var(--vaadin-form-item-row-spacing, 1em)) 0;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
:host([label-position='top']) {
|
|
107
|
-
flex-direction: column;
|
|
108
|
-
align-items: stretch;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
:host([hidden]) {
|
|
112
|
-
display: none !important;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
#label {
|
|
116
|
-
width: var(--vaadin-form-item-label-width, 8em);
|
|
117
|
-
flex: 0 0 auto;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
:host([label-position='top']) #label {
|
|
121
|
-
width: auto;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
#spacing {
|
|
125
|
-
width: var(--vaadin-form-item-label-spacing, 1em);
|
|
126
|
-
flex: 0 0 auto;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
#content {
|
|
130
|
-
flex: 1 1 auto;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
#content ::slotted(.full-width) {
|
|
134
|
-
box-sizing: border-box;
|
|
135
|
-
width: 100%;
|
|
136
|
-
min-width: 0;
|
|
137
|
-
}
|
|
138
|
-
</style>
|
|
139
113
|
<div id="label" part="label" on-click="__onLabelClick">
|
|
140
114
|
<slot name="label" id="labelSlot" on-slotchange="__onLabelSlotChange"></slot>
|
|
141
115
|
<span part="required-indicator" aria-hidden="true"></span>
|
|
@@ -146,189 +120,6 @@ class FormItem extends ThemableMixin(PolymerElement) {
|
|
|
146
120
|
</div>
|
|
147
121
|
`;
|
|
148
122
|
}
|
|
149
|
-
|
|
150
|
-
static get is() {
|
|
151
|
-
return 'vaadin-form-item';
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
constructor() {
|
|
155
|
-
super();
|
|
156
|
-
this.__updateInvalidState = this.__updateInvalidState.bind(this);
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* An observer for a field node to reflect its `required` and `invalid` attributes to the component.
|
|
160
|
-
*
|
|
161
|
-
* @type {MutationObserver}
|
|
162
|
-
* @private
|
|
163
|
-
*/
|
|
164
|
-
this.__fieldNodeObserver = new MutationObserver(() => this.__updateRequiredState(this.__fieldNode.required));
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* The first label node in the label slot.
|
|
168
|
-
*
|
|
169
|
-
* @type {HTMLElement | null}
|
|
170
|
-
* @private
|
|
171
|
-
*/
|
|
172
|
-
this.__labelNode = null;
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* The first field node in the content slot.
|
|
176
|
-
*
|
|
177
|
-
* An element is considered a field when it has the `checkValidity` or `validate` method.
|
|
178
|
-
*
|
|
179
|
-
* @type {HTMLElement | null}
|
|
180
|
-
* @private
|
|
181
|
-
*/
|
|
182
|
-
this.__fieldNode = null;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Returns a target element to add ARIA attributes to for a field.
|
|
187
|
-
*
|
|
188
|
-
* - For Vaadin field components, the method returns an element
|
|
189
|
-
* obtained through the `ariaTarget` property defined in `FieldMixin`.
|
|
190
|
-
* - In other cases, the method returns the field element itself.
|
|
191
|
-
*
|
|
192
|
-
* @param {HTMLElement} field
|
|
193
|
-
* @protected
|
|
194
|
-
*/
|
|
195
|
-
_getFieldAriaTarget(field) {
|
|
196
|
-
return field.ariaTarget || field;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Links the label to a field by adding the label id to
|
|
201
|
-
* the `aria-labelledby` attribute of the field's ARIA target element.
|
|
202
|
-
*
|
|
203
|
-
* @param {HTMLElement} field
|
|
204
|
-
* @private
|
|
205
|
-
*/
|
|
206
|
-
__linkLabelToField(field) {
|
|
207
|
-
addValueToAttribute(this._getFieldAriaTarget(field), 'aria-labelledby', this.__labelId);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Unlinks the label from a field by removing the label id from
|
|
212
|
-
* the `aria-labelledby` attribute of the field's ARIA target element.
|
|
213
|
-
*
|
|
214
|
-
* @param {HTMLElement} field
|
|
215
|
-
* @private
|
|
216
|
-
*/
|
|
217
|
-
__unlinkLabelFromField(field) {
|
|
218
|
-
removeValueFromAttribute(this._getFieldAriaTarget(field), 'aria-labelledby', this.__labelId);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/** @private */
|
|
222
|
-
__onLabelClick() {
|
|
223
|
-
const fieldNode = this.__fieldNode;
|
|
224
|
-
if (fieldNode) {
|
|
225
|
-
fieldNode.focus();
|
|
226
|
-
fieldNode.click();
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/** @private */
|
|
231
|
-
__getValidateFunction(field) {
|
|
232
|
-
return field.validate || field.checkValidity;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* A `slotchange` event handler for the label slot.
|
|
237
|
-
*
|
|
238
|
-
* - Ensures the label id is only assigned to the first label node.
|
|
239
|
-
* - Ensures the label node is linked to the first field node via the `aria-labelledby` attribute
|
|
240
|
-
* if both nodes are provided, and unlinked otherwise.
|
|
241
|
-
*
|
|
242
|
-
* @private
|
|
243
|
-
*/
|
|
244
|
-
__onLabelSlotChange() {
|
|
245
|
-
if (this.__labelNode) {
|
|
246
|
-
this.__labelNode = null;
|
|
247
|
-
|
|
248
|
-
if (this.__fieldNode) {
|
|
249
|
-
this.__unlinkLabelFromField(this.__fieldNode);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const newLabelNode = this.$.labelSlot.assignedElements()[0];
|
|
254
|
-
if (newLabelNode) {
|
|
255
|
-
this.__labelNode = newLabelNode;
|
|
256
|
-
|
|
257
|
-
if (this.__labelNode.id) {
|
|
258
|
-
// The new label node already has an id. Let's use it.
|
|
259
|
-
this.__labelId = this.__labelNode.id;
|
|
260
|
-
} else {
|
|
261
|
-
// The new label node doesn't have an id yet. Generate a unique one.
|
|
262
|
-
this.__labelId = `label-${this.localName}-${generateUniqueId()}`;
|
|
263
|
-
this.__labelNode.id = this.__labelId;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (this.__fieldNode) {
|
|
267
|
-
this.__linkLabelToField(this.__fieldNode);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* A `slotchange` event handler for the content slot.
|
|
274
|
-
*
|
|
275
|
-
* - Ensures the label node is only linked to the first field node via the `aria-labelledby` attribute.
|
|
276
|
-
* - Sets up an observer for the `required` attribute changes on the first field
|
|
277
|
-
* to reflect the attribute on the component. Ensures the observer is disconnected from the field
|
|
278
|
-
* as soon as it is removed or replaced by another one.
|
|
279
|
-
*
|
|
280
|
-
* @private
|
|
281
|
-
*/
|
|
282
|
-
__onContentSlotChange() {
|
|
283
|
-
if (this.__fieldNode) {
|
|
284
|
-
// Discard the old field
|
|
285
|
-
this.__unlinkLabelFromField(this.__fieldNode);
|
|
286
|
-
this.__updateRequiredState(false);
|
|
287
|
-
this.__fieldNodeObserver.disconnect();
|
|
288
|
-
this.__fieldNode = null;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const fieldNodes = this.$.contentSlot.assignedElements();
|
|
292
|
-
if (fieldNodes.length > 1) {
|
|
293
|
-
console.warn(
|
|
294
|
-
`WARNING: Since Vaadin 23, placing multiple fields directly to a <vaadin-form-item> is deprecated.
|
|
295
|
-
Please wrap fields with a <vaadin-custom-field> instead.`,
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const newFieldNode = fieldNodes.find((field) => {
|
|
300
|
-
return !!this.__getValidateFunction(field);
|
|
301
|
-
});
|
|
302
|
-
if (newFieldNode) {
|
|
303
|
-
this.__fieldNode = newFieldNode;
|
|
304
|
-
this.__updateRequiredState(this.__fieldNode.required);
|
|
305
|
-
this.__fieldNodeObserver.observe(this.__fieldNode, { attributes: true, attributeFilter: ['required'] });
|
|
306
|
-
|
|
307
|
-
if (this.__labelNode) {
|
|
308
|
-
this.__linkLabelToField(this.__fieldNode);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/** @private */
|
|
314
|
-
__updateRequiredState(required) {
|
|
315
|
-
if (required) {
|
|
316
|
-
this.setAttribute('required', '');
|
|
317
|
-
this.__fieldNode.addEventListener('blur', this.__updateInvalidState);
|
|
318
|
-
this.__fieldNode.addEventListener('change', this.__updateInvalidState);
|
|
319
|
-
} else {
|
|
320
|
-
this.removeAttribute('invalid');
|
|
321
|
-
this.removeAttribute('required');
|
|
322
|
-
this.__fieldNode.removeEventListener('blur', this.__updateInvalidState);
|
|
323
|
-
this.__fieldNode.removeEventListener('change', this.__updateInvalidState);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/** @private */
|
|
328
|
-
__updateInvalidState() {
|
|
329
|
-
const isValid = this.__getValidateFunction(this.__fieldNode).call(this.__fieldNode);
|
|
330
|
-
this.toggleAttribute('invalid', isValid === false);
|
|
331
|
-
}
|
|
332
123
|
}
|
|
333
124
|
|
|
334
125
|
defineCustomElement(FormItem);
|