@vaadin/form-layout 24.6.5 → 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 +13 -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 -455
- 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
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
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
|
+
}
|
|
@@ -0,0 +1,330 @@
|
|
|
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 { isElementHidden } from '@vaadin/a11y-base/src/focus-utils.js';
|
|
7
|
+
import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
|
|
8
|
+
|
|
9
|
+
function isValidCSSLength(value) {
|
|
10
|
+
// Check if the value is a valid CSS length and not `inherit` or `normal`,
|
|
11
|
+
// which are also valid values for `word-spacing`, see:
|
|
12
|
+
// https://drafts.csswg.org/css-text-3/#word-spacing-property
|
|
13
|
+
return CSS.supports('word-spacing', value) && !['inherit', 'normal'].includes(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @polymerMixin
|
|
18
|
+
* @mixes ResizeMixin
|
|
19
|
+
*/
|
|
20
|
+
export const FormLayoutMixin = (superClass) =>
|
|
21
|
+
class extends ResizeMixin(superClass) {
|
|
22
|
+
static get properties() {
|
|
23
|
+
return {
|
|
24
|
+
/**
|
|
25
|
+
* @typedef FormLayoutResponsiveStep
|
|
26
|
+
* @type {object}
|
|
27
|
+
* @property {string} minWidth - The threshold value for this step in CSS length units.
|
|
28
|
+
* @property {number} columns - Number of columns. Only natural numbers are valid.
|
|
29
|
+
* @property {string} labelsPosition - Labels position option, valid values: `"aside"` (default), `"top"`.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Allows specifying a responsive behavior with the number of columns
|
|
34
|
+
* and the label position depending on the layout width.
|
|
35
|
+
*
|
|
36
|
+
* Format: array of objects, each object defines one responsive step
|
|
37
|
+
* with `minWidth` CSS length, `columns` number, and optional
|
|
38
|
+
* `labelsPosition` string of `"aside"` or `"top"`. At least one item is required.
|
|
39
|
+
*
|
|
40
|
+
* #### Examples
|
|
41
|
+
*
|
|
42
|
+
* ```javascript
|
|
43
|
+
* formLayout.responsiveSteps = [{columns: 1}];
|
|
44
|
+
* // The layout is always a single column, labels aside.
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* ```javascript
|
|
48
|
+
* formLayout.responsiveSteps = [
|
|
49
|
+
* {minWidth: 0, columns: 1},
|
|
50
|
+
* {minWidth: '40em', columns: 2}
|
|
51
|
+
* ];
|
|
52
|
+
* // Sets two responsive steps:
|
|
53
|
+
* // 1. When the layout width is < 40em, one column, labels aside.
|
|
54
|
+
* // 2. Width >= 40em, two columns, labels aside.
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* ```javascript
|
|
58
|
+
* formLayout.responsiveSteps = [
|
|
59
|
+
* {minWidth: 0, columns: 1, labelsPosition: 'top'},
|
|
60
|
+
* {minWidth: '20em', columns: 1},
|
|
61
|
+
* {minWidth: '40em', columns: 2}
|
|
62
|
+
* ];
|
|
63
|
+
* // Default value. Three responsive steps:
|
|
64
|
+
* // 1. Width < 20em, one column, labels on top.
|
|
65
|
+
* // 2. 20em <= width < 40em, one column, labels aside.
|
|
66
|
+
* // 3. Width >= 40em, two columns, labels aside.
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @type {!Array<!FormLayoutResponsiveStep>}
|
|
70
|
+
*/
|
|
71
|
+
responsiveSteps: {
|
|
72
|
+
type: Array,
|
|
73
|
+
value() {
|
|
74
|
+
return [
|
|
75
|
+
{ minWidth: 0, columns: 1, labelsPosition: 'top' },
|
|
76
|
+
{ minWidth: '20em', columns: 1 },
|
|
77
|
+
{ minWidth: '40em', columns: 2 },
|
|
78
|
+
];
|
|
79
|
+
},
|
|
80
|
+
observer: '_responsiveStepsChanged',
|
|
81
|
+
sync: true,
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Current number of columns in the layout
|
|
86
|
+
* @private
|
|
87
|
+
*/
|
|
88
|
+
_columnCount: {
|
|
89
|
+
type: Number,
|
|
90
|
+
sync: true,
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Indicates that labels are on top
|
|
95
|
+
* @private
|
|
96
|
+
*/
|
|
97
|
+
_labelsOnTop: {
|
|
98
|
+
type: Boolean,
|
|
99
|
+
sync: true,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static get observers() {
|
|
105
|
+
return ['_invokeUpdateLayout(_columnCount, _labelsOnTop)'];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** @protected */
|
|
109
|
+
connectedCallback() {
|
|
110
|
+
super.connectedCallback();
|
|
111
|
+
|
|
112
|
+
// Set up an observer to update layout when new children are added or removed.
|
|
113
|
+
this.__childrenObserver = new MutationObserver(() => this._updateLayout());
|
|
114
|
+
this.__childrenObserver.observe(this, { childList: true });
|
|
115
|
+
|
|
116
|
+
// Set up an observer to update layout when children's attributes change.
|
|
117
|
+
this.__childrenAttributesObserver = new MutationObserver((mutations) => {
|
|
118
|
+
if (mutations.some((mutation) => mutation.target.parentElement === this)) {
|
|
119
|
+
this._updateLayout();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
this.__childrenAttributesObserver.observe(this, {
|
|
123
|
+
subtree: true,
|
|
124
|
+
attributes: true,
|
|
125
|
+
attributeFilter: ['colspan', 'data-colspan', 'hidden'],
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
requestAnimationFrame(() => this._selectResponsiveStep());
|
|
129
|
+
requestAnimationFrame(() => this._updateLayout());
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** @protected */
|
|
133
|
+
disconnectedCallback() {
|
|
134
|
+
super.disconnectedCallback();
|
|
135
|
+
|
|
136
|
+
this.__childrenObserver.disconnect();
|
|
137
|
+
this.__childrenAttributesObserver.disconnect();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** @private */
|
|
141
|
+
_naturalNumberOrOne(n) {
|
|
142
|
+
if (typeof n === 'number' && n >= 1 && n < Infinity) {
|
|
143
|
+
return Math.floor(n);
|
|
144
|
+
}
|
|
145
|
+
return 1;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** @private */
|
|
149
|
+
_responsiveStepsChanged(responsiveSteps, oldResponsiveSteps) {
|
|
150
|
+
try {
|
|
151
|
+
if (!Array.isArray(responsiveSteps)) {
|
|
152
|
+
throw new Error('Invalid "responsiveSteps" type, an Array is required.');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (responsiveSteps.length < 1) {
|
|
156
|
+
throw new Error('Invalid empty "responsiveSteps" array, at least one item is required.');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
responsiveSteps.forEach((step) => {
|
|
160
|
+
if (this._naturalNumberOrOne(step.columns) !== step.columns) {
|
|
161
|
+
throw new Error(`Invalid 'columns' value of ${step.columns}, a natural number is required.`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (step.minWidth !== undefined && !isValidCSSLength(step.minWidth)) {
|
|
165
|
+
throw new Error(`Invalid 'minWidth' value of ${step.minWidth}, a valid CSS length required.`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (step.labelsPosition !== undefined && ['aside', 'top'].indexOf(step.labelsPosition) === -1) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Invalid 'labelsPosition' value of ${step.labelsPosition}, 'aside' or 'top' string is required.`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
} catch (e) {
|
|
175
|
+
if (oldResponsiveSteps && oldResponsiveSteps !== responsiveSteps) {
|
|
176
|
+
console.warn(`${e.message} Using previously set 'responsiveSteps' instead.`);
|
|
177
|
+
this.responsiveSteps = oldResponsiveSteps;
|
|
178
|
+
} else {
|
|
179
|
+
console.warn(`${e.message} Using default 'responsiveSteps' instead.`);
|
|
180
|
+
this.responsiveSteps = [
|
|
181
|
+
{ minWidth: 0, columns: 1, labelsPosition: 'top' },
|
|
182
|
+
{ minWidth: '20em', columns: 1 },
|
|
183
|
+
{ minWidth: '40em', columns: 2 },
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this._selectResponsiveStep();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** @private */
|
|
192
|
+
_selectResponsiveStep() {
|
|
193
|
+
// Iterate through responsiveSteps and choose the step
|
|
194
|
+
let selectedStep;
|
|
195
|
+
const tmpStyleProp = 'background-position';
|
|
196
|
+
this.responsiveSteps.forEach((step) => {
|
|
197
|
+
// Convert minWidth to px units for comparison
|
|
198
|
+
this.$.layout.style.setProperty(tmpStyleProp, step.minWidth);
|
|
199
|
+
const stepMinWidthPx = parseFloat(getComputedStyle(this.$.layout).getPropertyValue(tmpStyleProp));
|
|
200
|
+
|
|
201
|
+
// Compare step min-width with the host width, select the passed step
|
|
202
|
+
if (stepMinWidthPx <= this.offsetWidth) {
|
|
203
|
+
selectedStep = step;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
this.$.layout.style.removeProperty(tmpStyleProp);
|
|
207
|
+
|
|
208
|
+
// Sometimes converting units is not possible, e.g, when element is
|
|
209
|
+
// not connected. Then the `selectedStep` stays `undefined`.
|
|
210
|
+
if (selectedStep) {
|
|
211
|
+
// Apply the chosen responsive step's properties
|
|
212
|
+
this._columnCount = selectedStep.columns;
|
|
213
|
+
this._labelsOnTop = selectedStep.labelsPosition === 'top';
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** @private */
|
|
218
|
+
_invokeUpdateLayout() {
|
|
219
|
+
this._updateLayout();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Update the layout.
|
|
224
|
+
* @protected
|
|
225
|
+
*/
|
|
226
|
+
_updateLayout() {
|
|
227
|
+
// Do not update layout when invisible
|
|
228
|
+
if (isElementHidden(this)) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/*
|
|
233
|
+
The item width formula:
|
|
234
|
+
|
|
235
|
+
itemWidth = colspan / columnCount * 100% - columnSpacing
|
|
236
|
+
|
|
237
|
+
We have to subtract columnSpacing, because the column spacing space is taken
|
|
238
|
+
by item margins of 1/2 * spacing on both sides
|
|
239
|
+
*/
|
|
240
|
+
|
|
241
|
+
const style = getComputedStyle(this);
|
|
242
|
+
const columnSpacing = style.getPropertyValue('--vaadin-form-layout-column-spacing');
|
|
243
|
+
|
|
244
|
+
const direction = style.direction;
|
|
245
|
+
const marginStartProp = `margin-${direction === 'ltr' ? 'left' : 'right'}`;
|
|
246
|
+
const marginEndProp = `margin-${direction === 'ltr' ? 'right' : 'left'}`;
|
|
247
|
+
|
|
248
|
+
const containerWidth = this.offsetWidth;
|
|
249
|
+
|
|
250
|
+
let col = 0;
|
|
251
|
+
Array.from(this.children)
|
|
252
|
+
.filter((child) => child.localName === 'br' || getComputedStyle(child).display !== 'none')
|
|
253
|
+
.forEach((child, index, children) => {
|
|
254
|
+
if (child.localName === 'br') {
|
|
255
|
+
// Reset column count on line break
|
|
256
|
+
col = 0;
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const attrColspan = child.getAttribute('colspan') || child.getAttribute('data-colspan');
|
|
261
|
+
let colspan;
|
|
262
|
+
colspan = this._naturalNumberOrOne(parseFloat(attrColspan));
|
|
263
|
+
|
|
264
|
+
// Never span further than the number of columns
|
|
265
|
+
colspan = Math.min(colspan, this._columnCount);
|
|
266
|
+
|
|
267
|
+
const childRatio = colspan / this._columnCount;
|
|
268
|
+
child.style.width = `calc(${childRatio * 100}% - ${1 - childRatio} * ${columnSpacing})`;
|
|
269
|
+
|
|
270
|
+
if (col + colspan > this._columnCount) {
|
|
271
|
+
// Too big to fit on this row, let's wrap it
|
|
272
|
+
col = 0;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// At the start edge
|
|
276
|
+
if (col === 0) {
|
|
277
|
+
child.style.setProperty(marginStartProp, '0px');
|
|
278
|
+
} else {
|
|
279
|
+
child.style.removeProperty(marginStartProp);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const nextIndex = index + 1;
|
|
283
|
+
const nextLineBreak = nextIndex < children.length && children[nextIndex].localName === 'br';
|
|
284
|
+
|
|
285
|
+
// At the end edge
|
|
286
|
+
if (col + colspan === this._columnCount) {
|
|
287
|
+
child.style.setProperty(marginEndProp, '0px');
|
|
288
|
+
} else if (nextLineBreak) {
|
|
289
|
+
const colspanRatio = (this._columnCount - col - colspan) / this._columnCount;
|
|
290
|
+
child.style.setProperty(
|
|
291
|
+
marginEndProp,
|
|
292
|
+
`calc(${colspanRatio * containerWidth}px + ${colspanRatio} * ${columnSpacing})`,
|
|
293
|
+
);
|
|
294
|
+
} else {
|
|
295
|
+
child.style.removeProperty(marginEndProp);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Move the column counter
|
|
299
|
+
col = (col + colspan) % this._columnCount;
|
|
300
|
+
|
|
301
|
+
if (child.localName === 'vaadin-form-item') {
|
|
302
|
+
if (this._labelsOnTop) {
|
|
303
|
+
if (child.getAttribute('label-position') !== 'top') {
|
|
304
|
+
child.__useLayoutLabelPosition = true;
|
|
305
|
+
child.setAttribute('label-position', 'top');
|
|
306
|
+
}
|
|
307
|
+
} else if (child.__useLayoutLabelPosition) {
|
|
308
|
+
delete child.__useLayoutLabelPosition;
|
|
309
|
+
child.removeAttribute('label-position');
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* @protected
|
|
317
|
+
* @override
|
|
318
|
+
*/
|
|
319
|
+
_onResize(contentRect) {
|
|
320
|
+
if (contentRect.width === 0 && contentRect.height === 0) {
|
|
321
|
+
this.$.layout.style.opacity = '0';
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
this._selectResponsiveStep();
|
|
326
|
+
this._updateLayout();
|
|
327
|
+
|
|
328
|
+
this.$.layout.style.opacity = '';
|
|
329
|
+
}
|
|
330
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2018 - 2025 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
import type { CSSResult } from 'lit';
|
|
7
|
+
|
|
8
|
+
export const formLayoutStyles: CSSResult;
|
|
9
|
+
|
|
10
|
+
export const formItemStyles: CSSResult;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2018 - 2025 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
import { css } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
7
|
+
|
|
8
|
+
export const formLayoutStyles = css`
|
|
9
|
+
:host {
|
|
10
|
+
/* Default values */
|
|
11
|
+
--vaadin-form-layout-row-spacing: 1em;
|
|
12
|
+
--vaadin-form-layout-column-spacing: 2em;
|
|
13
|
+
--vaadin-form-layout-label-width: 8em;
|
|
14
|
+
--vaadin-form-layout-label-spacing: 1em;
|
|
15
|
+
|
|
16
|
+
display: block;
|
|
17
|
+
max-width: 100%;
|
|
18
|
+
align-self: stretch;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
:host([hidden]) {
|
|
22
|
+
display: none !important;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#layout {
|
|
26
|
+
display: flex;
|
|
27
|
+
|
|
28
|
+
align-items: baseline; /* default \`stretch\` is not appropriate */
|
|
29
|
+
|
|
30
|
+
flex-wrap: wrap; /* the items should wrap */
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#layout ::slotted(*) {
|
|
34
|
+
/* Items should neither grow nor shrink. */
|
|
35
|
+
flex-grow: 0;
|
|
36
|
+
flex-shrink: 0;
|
|
37
|
+
|
|
38
|
+
/* Margins make spacing between the columns */
|
|
39
|
+
margin-left: calc(0.5 * var(--vaadin-form-layout-column-spacing));
|
|
40
|
+
margin-right: calc(0.5 * var(--vaadin-form-layout-column-spacing));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#layout ::slotted(br) {
|
|
44
|
+
display: none;
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
export const formItemStyles = css`
|
|
49
|
+
:host {
|
|
50
|
+
display: inline-flex;
|
|
51
|
+
flex-direction: row;
|
|
52
|
+
align-items: baseline;
|
|
53
|
+
margin: calc(0.5 * var(--vaadin-form-item-row-spacing, var(--vaadin-form-layout-row-spacing, 1em))) 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
:host([label-position='top']) {
|
|
57
|
+
flex-direction: column;
|
|
58
|
+
align-items: stretch;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
:host([hidden]) {
|
|
62
|
+
display: none !important;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#label {
|
|
66
|
+
width: var(--vaadin-form-item-label-width, var(--vaadin-form-layout-label-width, 8em));
|
|
67
|
+
flex: 0 0 auto;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
:host([label-position='top']) #label {
|
|
71
|
+
width: auto;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#spacing {
|
|
75
|
+
width: var(--vaadin-form-item-label-spacing, var(--vaadin-form-layout-label-spacing, 1em));
|
|
76
|
+
flex: 0 0 auto;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#content {
|
|
80
|
+
flex: 1 1 auto;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#content ::slotted(.full-width) {
|
|
84
|
+
box-sizing: border-box;
|
|
85
|
+
width: 100%;
|
|
86
|
+
min-width: 0;
|
|
87
|
+
}
|
|
88
|
+
`;
|
|
@@ -1,19 +1,13 @@
|
|
|
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 { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
|
|
7
|
-
import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
|
|
8
7
|
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
8
|
+
import { FormLayoutMixin } from './vaadin-form-layout-mixin.js';
|
|
9
9
|
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
export type FormLayoutResponsiveStep = {
|
|
13
|
-
minWidth?: string | 0;
|
|
14
|
-
columns: number;
|
|
15
|
-
labelsPosition?: FormLayoutLabelsPosition;
|
|
16
|
-
};
|
|
10
|
+
export * from './vaadin-form-layout-mixin.js';
|
|
17
11
|
|
|
18
12
|
/**
|
|
19
13
|
* `<vaadin-form-layout>` is a Web Component providing configurable responsive
|
|
@@ -104,52 +98,9 @@ export type FormLayoutResponsiveStep = {
|
|
|
104
98
|
* Custom CSS property | Description | Default
|
|
105
99
|
* ---|---|---
|
|
106
100
|
* `--vaadin-form-layout-column-spacing` | Length of the spacing between columns | `2em`
|
|
101
|
+
* `--vaadin-form-layout-row-spacing` | Length of the spacing between rows | `1em`
|
|
107
102
|
*/
|
|
108
|
-
declare class FormLayout extends
|
|
109
|
-
/**
|
|
110
|
-
* Allows specifying a responsive behavior with the number of columns
|
|
111
|
-
* and the label position depending on the layout width.
|
|
112
|
-
*
|
|
113
|
-
* Format: array of objects, each object defines one responsive step
|
|
114
|
-
* with `minWidth` CSS length, `columns` number, and optional
|
|
115
|
-
* `labelsPosition` string of `"aside"` or `"top"`. At least one item is required.
|
|
116
|
-
*
|
|
117
|
-
* #### Examples
|
|
118
|
-
*
|
|
119
|
-
* ```javascript
|
|
120
|
-
* formLayout.responsiveSteps = [{columns: 1}];
|
|
121
|
-
* // The layout is always a single column, labels aside.
|
|
122
|
-
* ```
|
|
123
|
-
*
|
|
124
|
-
* ```javascript
|
|
125
|
-
* formLayout.responsiveSteps = [
|
|
126
|
-
* {minWidth: 0, columns: 1},
|
|
127
|
-
* {minWidth: '40em', columns: 2}
|
|
128
|
-
* ];
|
|
129
|
-
* // Sets two responsive steps:
|
|
130
|
-
* // 1. When the layout width is < 40em, one column, labels aside.
|
|
131
|
-
* // 2. Width >= 40em, two columns, labels aside.
|
|
132
|
-
* ```
|
|
133
|
-
*
|
|
134
|
-
* ```javascript
|
|
135
|
-
* formLayout.responsiveSteps = [
|
|
136
|
-
* {minWidth: 0, columns: 1, labelsPosition: 'top'},
|
|
137
|
-
* {minWidth: '20em', columns: 1},
|
|
138
|
-
* {minWidth: '40em', columns: 2}
|
|
139
|
-
* ];
|
|
140
|
-
* // Default value. Three responsive steps:
|
|
141
|
-
* // 1. Width < 20em, one column, labels on top.
|
|
142
|
-
* // 2. 20em <= width < 40em, one column, labels aside.
|
|
143
|
-
* // 3. Width >= 40em, two columns, labels aside.
|
|
144
|
-
* ```
|
|
145
|
-
*/
|
|
146
|
-
responsiveSteps: FormLayoutResponsiveStep[];
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Update the layout.
|
|
150
|
-
*/
|
|
151
|
-
protected _updateLayout(): void;
|
|
152
|
-
}
|
|
103
|
+
declare class FormLayout extends FormLayoutMixin(ElementMixin(ThemableMixin(HTMLElement))) {}
|
|
153
104
|
|
|
154
105
|
declare global {
|
|
155
106
|
interface HTMLElementTagNameMap {
|