@vaadin/form-layout 24.7.0-alpha1 → 24.7.0-alpha3
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 +12 -11
- 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
|
@@ -0,0 +1,433 @@
|
|
|
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 { isElementHidden } from '@vaadin/a11y-base/src/focus-utils.js';
|
|
7
|
+
import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @polymerMixin
|
|
11
|
+
* @mixes ResizeMixin
|
|
12
|
+
*/
|
|
13
|
+
export const FormLayoutMixin = (superClass) =>
|
|
14
|
+
class extends ResizeMixin(superClass) {
|
|
15
|
+
static get properties() {
|
|
16
|
+
return {
|
|
17
|
+
/**
|
|
18
|
+
* @typedef FormLayoutResponsiveStep
|
|
19
|
+
* @type {object}
|
|
20
|
+
* @property {string} minWidth - The threshold value for this step in CSS length units.
|
|
21
|
+
* @property {number} columns - Number of columns. Only natural numbers are valid.
|
|
22
|
+
* @property {string} labelsPosition - Labels position option, valid values: `"aside"` (default), `"top"`.
|
|
23
|
+
*/
|
|
24
|
+
|
|
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
|
+
* @type {!Array<!FormLayoutResponsiveStep>}
|
|
63
|
+
*/
|
|
64
|
+
responsiveSteps: {
|
|
65
|
+
type: Array,
|
|
66
|
+
value() {
|
|
67
|
+
return [
|
|
68
|
+
{ minWidth: 0, columns: 1, labelsPosition: 'top' },
|
|
69
|
+
{ minWidth: '20em', columns: 1 },
|
|
70
|
+
{ minWidth: '40em', columns: 2 },
|
|
71
|
+
];
|
|
72
|
+
},
|
|
73
|
+
observer: '_responsiveStepsChanged',
|
|
74
|
+
sync: true,
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Current number of columns in the layout
|
|
79
|
+
* @private
|
|
80
|
+
*/
|
|
81
|
+
_columnCount: {
|
|
82
|
+
type: Number,
|
|
83
|
+
sync: true,
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Indicates that labels are on top
|
|
88
|
+
* @private
|
|
89
|
+
*/
|
|
90
|
+
_labelsOnTop: {
|
|
91
|
+
type: Boolean,
|
|
92
|
+
sync: true,
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
/** @private */
|
|
96
|
+
__isVisible: {
|
|
97
|
+
type: Boolean,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static get observers() {
|
|
103
|
+
return ['_invokeUpdateLayout(_columnCount, _labelsOnTop)'];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** @protected */
|
|
107
|
+
ready() {
|
|
108
|
+
// Here we attach a style element that we use for validating
|
|
109
|
+
// CSS values in `responsiveSteps`. We can't add this to the `<template>`,
|
|
110
|
+
// because Polymer will throw it away. We need to create this before
|
|
111
|
+
// `super.ready()`, because `super.ready()` invokes property observers,
|
|
112
|
+
// and the observer for `responsiveSteps` does CSS value validation.
|
|
113
|
+
this.appendChild(this._styleElement);
|
|
114
|
+
|
|
115
|
+
super.ready();
|
|
116
|
+
|
|
117
|
+
this.addEventListener('animationend', this.__onAnimationEnd);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
constructor() {
|
|
121
|
+
super();
|
|
122
|
+
|
|
123
|
+
this._styleElement = document.createElement('style');
|
|
124
|
+
// Ensure there is a child text node in the style element
|
|
125
|
+
this._styleElement.textContent = ' ';
|
|
126
|
+
|
|
127
|
+
this.__intersectionObserver = new IntersectionObserver(([entry]) => {
|
|
128
|
+
if (!entry.isIntersecting) {
|
|
129
|
+
// Prevent possible jump when layout becomes visible
|
|
130
|
+
this.$.layout.style.opacity = 0;
|
|
131
|
+
}
|
|
132
|
+
if (!this.__isVisible && entry.isIntersecting) {
|
|
133
|
+
this._updateLayout();
|
|
134
|
+
this.$.layout.style.opacity = '';
|
|
135
|
+
}
|
|
136
|
+
this.__isVisible = entry.isIntersecting;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** @protected */
|
|
141
|
+
connectedCallback() {
|
|
142
|
+
super.connectedCallback();
|
|
143
|
+
|
|
144
|
+
requestAnimationFrame(() => this._selectResponsiveStep());
|
|
145
|
+
requestAnimationFrame(() => this._updateLayout());
|
|
146
|
+
|
|
147
|
+
this._observeChildrenColspanChange();
|
|
148
|
+
this.__intersectionObserver.observe(this.$.layout);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** @protected */
|
|
152
|
+
disconnectedCallback() {
|
|
153
|
+
super.disconnectedCallback();
|
|
154
|
+
|
|
155
|
+
this.__mutationObserver.disconnect();
|
|
156
|
+
this.__childObserver.disconnect();
|
|
157
|
+
this.__intersectionObserver.disconnect();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** @private */
|
|
161
|
+
_observeChildrenColspanChange() {
|
|
162
|
+
// Observe changes in form items' `colspan` attribute and update styles
|
|
163
|
+
const mutationObserverConfig = { attributes: true };
|
|
164
|
+
|
|
165
|
+
this.__mutationObserver = new MutationObserver((mutationRecord) => {
|
|
166
|
+
mutationRecord.forEach((mutation) => {
|
|
167
|
+
if (
|
|
168
|
+
mutation.type === 'attributes' &&
|
|
169
|
+
(mutation.attributeName === 'colspan' ||
|
|
170
|
+
mutation.attributeName === 'data-colspan' ||
|
|
171
|
+
mutation.attributeName === 'hidden')
|
|
172
|
+
) {
|
|
173
|
+
this._updateLayout();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Observe changes to initial children
|
|
179
|
+
[...this.children].forEach((child) => {
|
|
180
|
+
this.__mutationObserver.observe(child, mutationObserverConfig);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Observe changes to lazily added nodes
|
|
184
|
+
this.__childObserver = new MutationObserver((mutations) => {
|
|
185
|
+
const addedNodes = [];
|
|
186
|
+
const removedNodes = [];
|
|
187
|
+
|
|
188
|
+
mutations.forEach((mutation) => {
|
|
189
|
+
addedNodes.push(...this._getObservableNodes(mutation.addedNodes));
|
|
190
|
+
removedNodes.push(...this._getObservableNodes(mutation.removedNodes));
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
addedNodes.forEach((child) => {
|
|
194
|
+
this.__mutationObserver.observe(child, mutationObserverConfig);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (addedNodes.length > 0 || removedNodes.length > 0) {
|
|
198
|
+
this._updateLayout();
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
this.__childObserver.observe(this, { childList: true });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** @private */
|
|
206
|
+
_getObservableNodes(nodeList) {
|
|
207
|
+
const ignore = ['template', 'style', 'dom-repeat', 'dom-if'];
|
|
208
|
+
return Array.from(nodeList).filter(
|
|
209
|
+
(node) => node.nodeType === Node.ELEMENT_NODE && ignore.indexOf(node.localName.toLowerCase()) === -1,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** @private */
|
|
214
|
+
_naturalNumberOrOne(n) {
|
|
215
|
+
if (typeof n === 'number' && n >= 1 && n < Infinity) {
|
|
216
|
+
return Math.floor(n);
|
|
217
|
+
}
|
|
218
|
+
return 1;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** @private */
|
|
222
|
+
_isValidCSSLength(value) {
|
|
223
|
+
// Let us choose a CSS property for validating CSS <length> values:
|
|
224
|
+
// - `border-spacing` accepts `<length> | inherit`, it's the best! But
|
|
225
|
+
// it does not disallow invalid values at all in MSIE :-(
|
|
226
|
+
// - `letter-spacing` and `word-spacing` accept
|
|
227
|
+
// `<length> | normal | inherit`, and disallows everything else, like
|
|
228
|
+
// `<percentage>`, `auto` and such, good enough.
|
|
229
|
+
// - `word-spacing` is used since its shorter.
|
|
230
|
+
|
|
231
|
+
// Disallow known keywords allowed as the `word-spacing` value
|
|
232
|
+
if (value === 'inherit' || value === 'normal') {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Use the value in a stylesheet and check the parsed value. Invalid
|
|
237
|
+
// input value results in empty parsed value.
|
|
238
|
+
this._styleElement.firstChild.nodeValue = `#styleElement { word-spacing: ${value}; }`;
|
|
239
|
+
|
|
240
|
+
if (!this._styleElement.sheet) {
|
|
241
|
+
// Stylesheet is not ready, probably not attached to the document yet.
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Safari 9 sets invalid CSS rules' value to `null`
|
|
246
|
+
return ['', null].indexOf(this._styleElement.sheet.cssRules[0].style.getPropertyValue('word-spacing')) < 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** @private */
|
|
250
|
+
_responsiveStepsChanged(responsiveSteps, oldResponsiveSteps) {
|
|
251
|
+
try {
|
|
252
|
+
if (!Array.isArray(responsiveSteps)) {
|
|
253
|
+
throw new Error('Invalid "responsiveSteps" type, an Array is required.');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (responsiveSteps.length < 1) {
|
|
257
|
+
throw new Error('Invalid empty "responsiveSteps" array, at least one item is required.');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
responsiveSteps.forEach((step) => {
|
|
261
|
+
if (this._naturalNumberOrOne(step.columns) !== step.columns) {
|
|
262
|
+
throw new Error(`Invalid 'columns' value of ${step.columns}, a natural number is required.`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (step.minWidth !== undefined && !this._isValidCSSLength(step.minWidth)) {
|
|
266
|
+
throw new Error(`Invalid 'minWidth' value of ${step.minWidth}, a valid CSS length required.`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (step.labelsPosition !== undefined && ['aside', 'top'].indexOf(step.labelsPosition) === -1) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
`Invalid 'labelsPosition' value of ${step.labelsPosition}, 'aside' or 'top' string is required.`,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
} catch (e) {
|
|
276
|
+
if (oldResponsiveSteps && oldResponsiveSteps !== responsiveSteps) {
|
|
277
|
+
console.warn(`${e.message} Using previously set 'responsiveSteps' instead.`);
|
|
278
|
+
this.responsiveSteps = oldResponsiveSteps;
|
|
279
|
+
} else {
|
|
280
|
+
console.warn(`${e.message} Using default 'responsiveSteps' instead.`);
|
|
281
|
+
this.responsiveSteps = [
|
|
282
|
+
{ minWidth: 0, columns: 1, labelsPosition: 'top' },
|
|
283
|
+
{ minWidth: '20em', columns: 1 },
|
|
284
|
+
{ minWidth: '40em', columns: 2 },
|
|
285
|
+
];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
this._selectResponsiveStep();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** @private */
|
|
293
|
+
__onAnimationEnd(e) {
|
|
294
|
+
if (e.animationName.indexOf('vaadin-form-layout-appear') === 0) {
|
|
295
|
+
this._selectResponsiveStep();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** @private */
|
|
300
|
+
_selectResponsiveStep() {
|
|
301
|
+
// Iterate through responsiveSteps and choose the step
|
|
302
|
+
let selectedStep;
|
|
303
|
+
const tmpStyleProp = 'background-position';
|
|
304
|
+
this.responsiveSteps.forEach((step) => {
|
|
305
|
+
// Convert minWidth to px units for comparison
|
|
306
|
+
this.$.layout.style.setProperty(tmpStyleProp, step.minWidth);
|
|
307
|
+
const stepMinWidthPx = parseFloat(getComputedStyle(this.$.layout).getPropertyValue(tmpStyleProp));
|
|
308
|
+
|
|
309
|
+
// Compare step min-width with the host width, select the passed step
|
|
310
|
+
if (stepMinWidthPx <= this.offsetWidth) {
|
|
311
|
+
selectedStep = step;
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
this.$.layout.style.removeProperty(tmpStyleProp);
|
|
315
|
+
|
|
316
|
+
// Sometimes converting units is not possible, e.g, when element is
|
|
317
|
+
// not connected. Then the `selectedStep` stays `undefined`.
|
|
318
|
+
if (selectedStep) {
|
|
319
|
+
// Apply the chosen responsive step's properties
|
|
320
|
+
this._columnCount = selectedStep.columns;
|
|
321
|
+
this._labelsOnTop = selectedStep.labelsPosition === 'top';
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** @private */
|
|
326
|
+
_invokeUpdateLayout() {
|
|
327
|
+
this._updateLayout();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Update the layout.
|
|
332
|
+
* @protected
|
|
333
|
+
*/
|
|
334
|
+
_updateLayout() {
|
|
335
|
+
// Do not update layout when invisible
|
|
336
|
+
if (isElementHidden(this)) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/*
|
|
341
|
+
The item width formula:
|
|
342
|
+
|
|
343
|
+
itemWidth = colspan / columnCount * 100% - columnSpacing
|
|
344
|
+
|
|
345
|
+
We have to subtract columnSpacing, because the column spacing space is taken
|
|
346
|
+
by item margins of 1/2 * spacing on both sides
|
|
347
|
+
*/
|
|
348
|
+
|
|
349
|
+
const style = getComputedStyle(this);
|
|
350
|
+
const columnSpacing = style.getPropertyValue('--vaadin-form-layout-column-spacing');
|
|
351
|
+
|
|
352
|
+
const direction = style.direction;
|
|
353
|
+
const marginStartProp = `margin-${direction === 'ltr' ? 'left' : 'right'}`;
|
|
354
|
+
const marginEndProp = `margin-${direction === 'ltr' ? 'right' : 'left'}`;
|
|
355
|
+
|
|
356
|
+
const containerWidth = this.offsetWidth;
|
|
357
|
+
|
|
358
|
+
let col = 0;
|
|
359
|
+
Array.from(this.children)
|
|
360
|
+
.filter((child) => child.localName === 'br' || getComputedStyle(child).display !== 'none')
|
|
361
|
+
.forEach((child, index, children) => {
|
|
362
|
+
if (child.localName === 'br') {
|
|
363
|
+
// Reset column count on line break
|
|
364
|
+
col = 0;
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const attrColspan = child.getAttribute('colspan') || child.getAttribute('data-colspan');
|
|
369
|
+
let colspan;
|
|
370
|
+
colspan = this._naturalNumberOrOne(parseFloat(attrColspan));
|
|
371
|
+
|
|
372
|
+
// Never span further than the number of columns
|
|
373
|
+
colspan = Math.min(colspan, this._columnCount);
|
|
374
|
+
|
|
375
|
+
const childRatio = colspan / this._columnCount;
|
|
376
|
+
|
|
377
|
+
// Note: using 99.9% for 100% fixes rounding errors in MS Edge
|
|
378
|
+
// (< v16), otherwise the items might wrap, resizing is wobbly.
|
|
379
|
+
child.style.width = `calc(${childRatio * 99.9}% - ${1 - childRatio} * ${columnSpacing})`;
|
|
380
|
+
|
|
381
|
+
if (col + colspan > this._columnCount) {
|
|
382
|
+
// Too big to fit on this row, let's wrap it
|
|
383
|
+
col = 0;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// At the start edge
|
|
387
|
+
if (col === 0) {
|
|
388
|
+
child.style.setProperty(marginStartProp, '0px');
|
|
389
|
+
} else {
|
|
390
|
+
child.style.removeProperty(marginStartProp);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const nextIndex = index + 1;
|
|
394
|
+
const nextLineBreak = nextIndex < children.length && children[nextIndex].localName === 'br';
|
|
395
|
+
|
|
396
|
+
// At the end edge
|
|
397
|
+
if (col + colspan === this._columnCount) {
|
|
398
|
+
child.style.setProperty(marginEndProp, '0px');
|
|
399
|
+
} else if (nextLineBreak) {
|
|
400
|
+
const colspanRatio = (this._columnCount - col - colspan) / this._columnCount;
|
|
401
|
+
child.style.setProperty(
|
|
402
|
+
marginEndProp,
|
|
403
|
+
`calc(${colspanRatio * containerWidth}px + ${colspanRatio} * ${columnSpacing})`,
|
|
404
|
+
);
|
|
405
|
+
} else {
|
|
406
|
+
child.style.removeProperty(marginEndProp);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Move the column counter
|
|
410
|
+
col = (col + colspan) % this._columnCount;
|
|
411
|
+
|
|
412
|
+
if (child.localName === 'vaadin-form-item') {
|
|
413
|
+
if (this._labelsOnTop) {
|
|
414
|
+
if (child.getAttribute('label-position') !== 'top') {
|
|
415
|
+
child.__useLayoutLabelPosition = true;
|
|
416
|
+
child.setAttribute('label-position', 'top');
|
|
417
|
+
}
|
|
418
|
+
} else if (child.__useLayoutLabelPosition) {
|
|
419
|
+
delete child.__useLayoutLabelPosition;
|
|
420
|
+
child.removeAttribute('label-position');
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* @protected
|
|
428
|
+
* @override
|
|
429
|
+
*/
|
|
430
|
+
_onResize() {
|
|
431
|
+
this._selectResponsiveStep();
|
|
432
|
+
}
|
|
433
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2018 - 2024 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,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2018 - 2024 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
|
+
display: block;
|
|
11
|
+
max-width: 100%;
|
|
12
|
+
animation: 1ms vaadin-form-layout-appear;
|
|
13
|
+
/* CSS API for host */
|
|
14
|
+
--vaadin-form-item-label-width: 8em;
|
|
15
|
+
--vaadin-form-item-label-spacing: 1em;
|
|
16
|
+
--vaadin-form-item-row-spacing: 1em;
|
|
17
|
+
--vaadin-form-layout-column-spacing: 2em; /* (default) */
|
|
18
|
+
align-self: stretch;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@keyframes vaadin-form-layout-appear {
|
|
22
|
+
to {
|
|
23
|
+
opacity: 1 !important; /* stylelint-disable-line keyframe-declaration-no-important */
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
:host([hidden]) {
|
|
28
|
+
display: none !important;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#layout {
|
|
32
|
+
display: flex;
|
|
33
|
+
|
|
34
|
+
align-items: baseline; /* default \`stretch\` is not appropriate */
|
|
35
|
+
|
|
36
|
+
flex-wrap: wrap; /* the items should wrap */
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#layout ::slotted(*) {
|
|
40
|
+
/* Items should neither grow nor shrink. */
|
|
41
|
+
flex-grow: 0;
|
|
42
|
+
flex-shrink: 0;
|
|
43
|
+
|
|
44
|
+
/* Margins make spacing between the columns */
|
|
45
|
+
margin-left: calc(0.5 * var(--vaadin-form-layout-column-spacing));
|
|
46
|
+
margin-right: calc(0.5 * var(--vaadin-form-layout-column-spacing));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#layout ::slotted(br) {
|
|
50
|
+
display: none;
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
export const formItemStyles = css`
|
|
55
|
+
:host {
|
|
56
|
+
display: inline-flex;
|
|
57
|
+
flex-direction: row;
|
|
58
|
+
align-items: baseline;
|
|
59
|
+
margin: calc(0.5 * var(--vaadin-form-item-row-spacing, 1em)) 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
:host([label-position='top']) {
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
align-items: stretch;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
:host([hidden]) {
|
|
68
|
+
display: none !important;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#label {
|
|
72
|
+
width: var(--vaadin-form-item-label-width, 8em);
|
|
73
|
+
flex: 0 0 auto;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
:host([label-position='top']) #label {
|
|
77
|
+
width: auto;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#spacing {
|
|
81
|
+
width: var(--vaadin-form-item-label-spacing, 1em);
|
|
82
|
+
flex: 0 0 auto;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#content {
|
|
86
|
+
flex: 1 1 auto;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#content ::slotted(.full-width) {
|
|
90
|
+
box-sizing: border-box;
|
|
91
|
+
width: 100%;
|
|
92
|
+
min-width: 0;
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
@@ -4,16 +4,10 @@
|
|
|
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
|
|
@@ -105,51 +99,7 @@ export type FormLayoutResponsiveStep = {
|
|
|
105
99
|
* ---|---|---
|
|
106
100
|
* `--vaadin-form-layout-column-spacing` | Length of the spacing between columns | `2em`
|
|
107
101
|
*/
|
|
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
|
-
}
|
|
102
|
+
declare class FormLayout extends FormLayoutMixin(ElementMixin(ThemableMixin(HTMLElement))) {}
|
|
153
103
|
|
|
154
104
|
declare global {
|
|
155
105
|
interface HTMLElementTagNameMap {
|