@vaadin/form-layout 24.7.0-alpha1 → 24.7.0-alpha2
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 +11 -10
- package/src/vaadin-form-item-mixin.d.ts +22 -0
- package/src/vaadin-form-item-mixin.js +192 -0
- package/src/vaadin-form-item.d.ts +2 -10
- package/src/vaadin-form-item.js +11 -228
- package/src/vaadin-form-layout-mixin.d.ts +68 -0
- package/src/vaadin-form-layout-mixin.js +433 -0
- package/src/vaadin-form-layout-styles.d.ts +10 -0
- package/src/vaadin-form-layout-styles.js +94 -0
- package/src/vaadin-form-layout.d.ts +3 -53
- package/src/vaadin-form-layout.js +11 -469
- 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-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 +1 -1
- package/web-types.lit.json +1 -1
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-alpha2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -35,23 +35,24 @@
|
|
|
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-alpha2",
|
|
41
|
+
"@vaadin/component-base": "24.7.0-alpha2",
|
|
42
|
+
"@vaadin/vaadin-lumo-styles": "24.7.0-alpha2",
|
|
43
|
+
"@vaadin/vaadin-material-styles": "24.7.0-alpha2",
|
|
44
|
+
"@vaadin/vaadin-themable-mixin": "24.7.0-alpha2"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
|
-
"@vaadin/chai-plugins": "24.7.0-
|
|
47
|
-
"@vaadin/custom-field": "24.7.0-
|
|
47
|
+
"@vaadin/chai-plugins": "24.7.0-alpha2",
|
|
48
|
+
"@vaadin/custom-field": "24.7.0-alpha2",
|
|
48
49
|
"@vaadin/testing-helpers": "^1.0.0",
|
|
49
|
-
"@vaadin/text-field": "24.7.0-
|
|
50
|
+
"@vaadin/text-field": "24.7.0-alpha2",
|
|
50
51
|
"sinon": "^18.0.0"
|
|
51
52
|
},
|
|
52
53
|
"web-types": [
|
|
53
54
|
"web-types.json",
|
|
54
55
|
"web-types.lit.json"
|
|
55
56
|
],
|
|
56
|
-
"gitHead": "
|
|
57
|
+
"gitHead": "e2523f9b4abc5a9586fb758166f823dc40c399dd"
|
|
57
58
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2017 - 2024 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,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2017 - 2024 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
|
+
/**
|
|
10
|
+
* @polymerMixin
|
|
11
|
+
*/
|
|
12
|
+
export const FormItemMixin = (superClass) =>
|
|
13
|
+
class extends superClass {
|
|
14
|
+
constructor() {
|
|
15
|
+
super();
|
|
16
|
+
this.__updateInvalidState = this.__updateInvalidState.bind(this);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* An observer for a field node to reflect its `required` and `invalid` attributes to the component.
|
|
20
|
+
*
|
|
21
|
+
* @type {MutationObserver}
|
|
22
|
+
* @private
|
|
23
|
+
*/
|
|
24
|
+
this.__fieldNodeObserver = new MutationObserver(() => this.__updateRequiredState(this.__fieldNode.required));
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The first label node in the label slot.
|
|
28
|
+
*
|
|
29
|
+
* @type {HTMLElement | null}
|
|
30
|
+
* @private
|
|
31
|
+
*/
|
|
32
|
+
this.__labelNode = null;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The first field node in the content slot.
|
|
36
|
+
*
|
|
37
|
+
* An element is considered a field when it has the `checkValidity` or `validate` method.
|
|
38
|
+
*
|
|
39
|
+
* @type {HTMLElement | null}
|
|
40
|
+
* @private
|
|
41
|
+
*/
|
|
42
|
+
this.__fieldNode = null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Returns a target element to add ARIA attributes to for a field.
|
|
47
|
+
*
|
|
48
|
+
* - For Vaadin field components, the method returns an element
|
|
49
|
+
* obtained through the `ariaTarget` property defined in `FieldMixin`.
|
|
50
|
+
* - In other cases, the method returns the field element itself.
|
|
51
|
+
*
|
|
52
|
+
* @param {HTMLElement} field
|
|
53
|
+
* @protected
|
|
54
|
+
*/
|
|
55
|
+
_getFieldAriaTarget(field) {
|
|
56
|
+
return field.ariaTarget || field;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Links the label to a field by adding the label id to
|
|
61
|
+
* the `aria-labelledby` attribute of the field's ARIA target element.
|
|
62
|
+
*
|
|
63
|
+
* @param {HTMLElement} field
|
|
64
|
+
* @private
|
|
65
|
+
*/
|
|
66
|
+
__linkLabelToField(field) {
|
|
67
|
+
addValueToAttribute(this._getFieldAriaTarget(field), 'aria-labelledby', this.__labelId);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Unlinks the label from a field by removing the label id from
|
|
72
|
+
* the `aria-labelledby` attribute of the field's ARIA target element.
|
|
73
|
+
*
|
|
74
|
+
* @param {HTMLElement} field
|
|
75
|
+
* @private
|
|
76
|
+
*/
|
|
77
|
+
__unlinkLabelFromField(field) {
|
|
78
|
+
removeValueFromAttribute(this._getFieldAriaTarget(field), 'aria-labelledby', this.__labelId);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** @private */
|
|
82
|
+
__onLabelClick() {
|
|
83
|
+
const fieldNode = this.__fieldNode;
|
|
84
|
+
if (fieldNode) {
|
|
85
|
+
fieldNode.focus();
|
|
86
|
+
fieldNode.click();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** @private */
|
|
91
|
+
__getValidateFunction(field) {
|
|
92
|
+
return field.validate || field.checkValidity;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A `slotchange` event handler for the label slot.
|
|
97
|
+
*
|
|
98
|
+
* - Ensures the label id is only assigned to the first label node.
|
|
99
|
+
* - Ensures the label node is linked to the first field node via the `aria-labelledby` attribute
|
|
100
|
+
* if both nodes are provided, and unlinked otherwise.
|
|
101
|
+
*
|
|
102
|
+
* @private
|
|
103
|
+
*/
|
|
104
|
+
__onLabelSlotChange() {
|
|
105
|
+
if (this.__labelNode) {
|
|
106
|
+
this.__labelNode = null;
|
|
107
|
+
|
|
108
|
+
if (this.__fieldNode) {
|
|
109
|
+
this.__unlinkLabelFromField(this.__fieldNode);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const newLabelNode = this.$.labelSlot.assignedElements()[0];
|
|
114
|
+
if (newLabelNode) {
|
|
115
|
+
this.__labelNode = newLabelNode;
|
|
116
|
+
|
|
117
|
+
if (this.__labelNode.id) {
|
|
118
|
+
// The new label node already has an id. Let's use it.
|
|
119
|
+
this.__labelId = this.__labelNode.id;
|
|
120
|
+
} else {
|
|
121
|
+
// The new label node doesn't have an id yet. Generate a unique one.
|
|
122
|
+
this.__labelId = `label-${this.localName}-${generateUniqueId()}`;
|
|
123
|
+
this.__labelNode.id = this.__labelId;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (this.__fieldNode) {
|
|
127
|
+
this.__linkLabelToField(this.__fieldNode);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* A `slotchange` event handler for the content slot.
|
|
134
|
+
*
|
|
135
|
+
* - Ensures the label node is only linked to the first field node via the `aria-labelledby` attribute.
|
|
136
|
+
* - Sets up an observer for the `required` attribute changes on the first field
|
|
137
|
+
* to reflect the attribute on the component. Ensures the observer is disconnected from the field
|
|
138
|
+
* as soon as it is removed or replaced by another one.
|
|
139
|
+
*
|
|
140
|
+
* @private
|
|
141
|
+
*/
|
|
142
|
+
__onContentSlotChange() {
|
|
143
|
+
if (this.__fieldNode) {
|
|
144
|
+
// Discard the old field
|
|
145
|
+
this.__unlinkLabelFromField(this.__fieldNode);
|
|
146
|
+
this.__updateRequiredState(false);
|
|
147
|
+
this.__fieldNodeObserver.disconnect();
|
|
148
|
+
this.__fieldNode = null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const fieldNodes = this.$.contentSlot.assignedElements();
|
|
152
|
+
if (fieldNodes.length > 1) {
|
|
153
|
+
console.warn(
|
|
154
|
+
`WARNING: Since Vaadin 23, placing multiple fields directly to a <vaadin-form-item> is deprecated.
|
|
155
|
+
Please wrap fields with a <vaadin-custom-field> instead.`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const newFieldNode = fieldNodes.find((field) => {
|
|
160
|
+
return !!this.__getValidateFunction(field);
|
|
161
|
+
});
|
|
162
|
+
if (newFieldNode) {
|
|
163
|
+
this.__fieldNode = newFieldNode;
|
|
164
|
+
this.__updateRequiredState(this.__fieldNode.required);
|
|
165
|
+
this.__fieldNodeObserver.observe(this.__fieldNode, { attributes: true, attributeFilter: ['required'] });
|
|
166
|
+
|
|
167
|
+
if (this.__labelNode) {
|
|
168
|
+
this.__linkLabelToField(this.__fieldNode);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** @private */
|
|
174
|
+
__updateRequiredState(required) {
|
|
175
|
+
if (required) {
|
|
176
|
+
this.setAttribute('required', '');
|
|
177
|
+
this.__fieldNode.addEventListener('blur', this.__updateInvalidState);
|
|
178
|
+
this.__fieldNode.addEventListener('change', this.__updateInvalidState);
|
|
179
|
+
} else {
|
|
180
|
+
this.removeAttribute('invalid');
|
|
181
|
+
this.removeAttribute('required');
|
|
182
|
+
this.__fieldNode.removeEventListener('blur', this.__updateInvalidState);
|
|
183
|
+
this.__fieldNode.removeEventListener('change', this.__updateInvalidState);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** @private */
|
|
188
|
+
__updateInvalidState() {
|
|
189
|
+
const isValid = this.__getValidateFunction(this.__fieldNode).call(this.__fieldNode);
|
|
190
|
+
this.toggleAttribute('invalid', isValid === false);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
@@ -4,6 +4,7 @@
|
|
|
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
|
|
@@ -84,16 +85,7 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
|
|
|
84
85
|
*
|
|
85
86
|
* See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
|
|
86
87
|
*/
|
|
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
|
-
}
|
|
88
|
+
declare class FormItem extends FormItemMixin(ThemableMixin(HTMLElement)) {}
|
|
97
89
|
|
|
98
90
|
declare global {
|
|
99
91
|
interface HTMLElementTagNameMap {
|
package/src/vaadin-form-item.js
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
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
|
|
@@ -90,52 +92,16 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
|
|
|
90
92
|
*
|
|
91
93
|
* @customElement
|
|
92
94
|
* @extends HTMLElement
|
|
95
|
+
* @mixes FormItemMixin
|
|
93
96
|
* @mixes ThemableMixin
|
|
94
97
|
*/
|
|
95
|
-
class FormItem extends ThemableMixin(PolymerElement) {
|
|
98
|
+
class FormItem extends FormItemMixin(ThemableMixin(PolymerElement)) {
|
|
99
|
+
static get is() {
|
|
100
|
+
return 'vaadin-form-item';
|
|
101
|
+
}
|
|
102
|
+
|
|
96
103
|
static get template() {
|
|
97
104
|
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
105
|
<div id="label" part="label" on-click="__onLabelClick">
|
|
140
106
|
<slot name="label" id="labelSlot" on-slotchange="__onLabelSlotChange"></slot>
|
|
141
107
|
<span part="required-indicator" aria-hidden="true"></span>
|
|
@@ -146,189 +112,6 @@ class FormItem extends ThemableMixin(PolymerElement) {
|
|
|
146
112
|
</div>
|
|
147
113
|
`;
|
|
148
114
|
}
|
|
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
115
|
}
|
|
333
116
|
|
|
334
117
|
defineCustomElement(FormItem);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2017 - 2024 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 { ResizeMixinClass } from '@vaadin/component-base/src/resize-mixin.js';
|
|
8
|
+
|
|
9
|
+
export type FormLayoutLabelsPosition = 'aside' | 'top';
|
|
10
|
+
|
|
11
|
+
export type FormLayoutResponsiveStep = {
|
|
12
|
+
minWidth?: string | 0;
|
|
13
|
+
columns: number;
|
|
14
|
+
labelsPosition?: FormLayoutLabelsPosition;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A mixin providing common form-layout functionality.
|
|
19
|
+
*/
|
|
20
|
+
export declare function FormLayoutMixin<T extends Constructor<HTMLElement>>(
|
|
21
|
+
base: T,
|
|
22
|
+
): Constructor<ResizeMixinClass> & Constructor<FormLayoutMixinClass> & T;
|
|
23
|
+
|
|
24
|
+
export declare class FormLayoutMixinClass {
|
|
25
|
+
/**
|
|
26
|
+
* Allows specifying a responsive behavior with the number of columns
|
|
27
|
+
* and the label position depending on the layout width.
|
|
28
|
+
*
|
|
29
|
+
* Format: array of objects, each object defines one responsive step
|
|
30
|
+
* with `minWidth` CSS length, `columns` number, and optional
|
|
31
|
+
* `labelsPosition` string of `"aside"` or `"top"`. At least one item is required.
|
|
32
|
+
*
|
|
33
|
+
* #### Examples
|
|
34
|
+
*
|
|
35
|
+
* ```javascript
|
|
36
|
+
* formLayout.responsiveSteps = [{columns: 1}];
|
|
37
|
+
* // The layout is always a single column, labels aside.
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* ```javascript
|
|
41
|
+
* formLayout.responsiveSteps = [
|
|
42
|
+
* {minWidth: 0, columns: 1},
|
|
43
|
+
* {minWidth: '40em', columns: 2}
|
|
44
|
+
* ];
|
|
45
|
+
* // Sets two responsive steps:
|
|
46
|
+
* // 1. When the layout width is < 40em, one column, labels aside.
|
|
47
|
+
* // 2. Width >= 40em, two columns, labels aside.
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* ```javascript
|
|
51
|
+
* formLayout.responsiveSteps = [
|
|
52
|
+
* {minWidth: 0, columns: 1, labelsPosition: 'top'},
|
|
53
|
+
* {minWidth: '20em', columns: 1},
|
|
54
|
+
* {minWidth: '40em', columns: 2}
|
|
55
|
+
* ];
|
|
56
|
+
* // Default value. Three responsive steps:
|
|
57
|
+
* // 1. Width < 20em, one column, labels on top.
|
|
58
|
+
* // 2. 20em <= width < 40em, one column, labels aside.
|
|
59
|
+
* // 3. Width >= 40em, two columns, labels aside.
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
responsiveSteps: FormLayoutResponsiveStep[];
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Update the layout.
|
|
66
|
+
*/
|
|
67
|
+
protected _updateLayout(): void;
|
|
68
|
+
}
|