@vaadin/form-layout 24.7.0-rc1 → 24.8.0-alpha1
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 -11
- package/src/layouts/abstract-layout.js +85 -0
- package/src/layouts/auto-responsive-layout.js +177 -0
- package/src/layouts/responsive-steps-layout.js +233 -0
- package/src/vaadin-form-layout-mixin.d.ts +83 -2
- package/src/vaadin-form-layout-mixin.js +151 -201
- package/src/vaadin-form-layout-styles.js +158 -11
- package/src/vaadin-form-layout.d.ts +82 -0
- package/src/vaadin-form-layout.js +82 -0
- package/src/vaadin-form-row.d.ts +24 -0
- package/src/vaadin-form-row.js +37 -0
- package/src/vaadin-lit-form-row.d.ts +6 -0
- package/src/vaadin-lit-form-row.js +38 -0
- package/theme/lumo/vaadin-form-row.d.ts +1 -0
- package/theme/lumo/vaadin-form-row.js +1 -0
- package/theme/lumo/vaadin-lit-form-row.d.ts +1 -0
- package/theme/lumo/vaadin-lit-form-row.js +1 -0
- package/theme/material/vaadin-form-row.d.ts +1 -0
- package/theme/material/vaadin-form-row.js +1 -0
- package/theme/material/vaadin-lit-form-row.d.ts +1 -0
- package/theme/material/vaadin-lit-form-row.js +1 -0
- package/vaadin-form-row.d.ts +1 -0
- package/vaadin-form-row.js +2 -0
- package/vaadin-lit-form-row.d.ts +1 -0
- package/vaadin-lit-form-row.js +2 -0
- package/web-types.json +177 -2
- package/web-types.lit.json +57 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/form-layout",
|
|
3
|
-
"version": "24.
|
|
3
|
+
"version": "24.8.0-alpha1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -37,24 +37,24 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@open-wc/dedupe-mixin": "^1.3.0",
|
|
39
39
|
"@polymer/polymer": "^3.0.0",
|
|
40
|
-
"@vaadin/a11y-base": "24.
|
|
41
|
-
"@vaadin/component-base": "24.
|
|
42
|
-
"@vaadin/vaadin-lumo-styles": "24.
|
|
43
|
-
"@vaadin/vaadin-material-styles": "24.
|
|
44
|
-
"@vaadin/vaadin-themable-mixin": "24.
|
|
40
|
+
"@vaadin/a11y-base": "24.8.0-alpha1",
|
|
41
|
+
"@vaadin/component-base": "24.8.0-alpha1",
|
|
42
|
+
"@vaadin/vaadin-lumo-styles": "24.8.0-alpha1",
|
|
43
|
+
"@vaadin/vaadin-material-styles": "24.8.0-alpha1",
|
|
44
|
+
"@vaadin/vaadin-themable-mixin": "24.8.0-alpha1",
|
|
45
45
|
"lit": "^3.0.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@vaadin/chai-plugins": "24.
|
|
49
|
-
"@vaadin/custom-field": "24.
|
|
50
|
-
"@vaadin/test-runner-commands": "24.
|
|
48
|
+
"@vaadin/chai-plugins": "24.8.0-alpha1",
|
|
49
|
+
"@vaadin/custom-field": "24.8.0-alpha1",
|
|
50
|
+
"@vaadin/test-runner-commands": "24.8.0-alpha1",
|
|
51
51
|
"@vaadin/testing-helpers": "^1.1.0",
|
|
52
|
-
"@vaadin/text-field": "24.
|
|
52
|
+
"@vaadin/text-field": "24.8.0-alpha1",
|
|
53
53
|
"sinon": "^18.0.0"
|
|
54
54
|
},
|
|
55
55
|
"web-types": [
|
|
56
56
|
"web-types.json",
|
|
57
57
|
"web-types.lit.json"
|
|
58
58
|
],
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "dfec8767c5d1c343ae689c93429c3364f4182f76"
|
|
60
60
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2021 - 2025 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* An abstract class for layout implementation. Not intended for public use.
|
|
9
|
+
*
|
|
10
|
+
* @private
|
|
11
|
+
*/
|
|
12
|
+
export class AbstractLayout {
|
|
13
|
+
/**
|
|
14
|
+
* @param {HTMLElement} host
|
|
15
|
+
* @param {{ mutationObserverOptions: MutationObserverInit }} config
|
|
16
|
+
*/
|
|
17
|
+
constructor(host, config) {
|
|
18
|
+
this.host = host;
|
|
19
|
+
this.props = {};
|
|
20
|
+
this.config = config;
|
|
21
|
+
this.isConnected = false;
|
|
22
|
+
|
|
23
|
+
/** @private */
|
|
24
|
+
this.__resizeObserver = new ResizeObserver((entries) => setTimeout(() => this._onResize(entries)));
|
|
25
|
+
|
|
26
|
+
/** @private */
|
|
27
|
+
this.__mutationObserver = new MutationObserver((records) => this._onMutation(records));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Connects the layout to the host element.
|
|
32
|
+
*/
|
|
33
|
+
connect() {
|
|
34
|
+
if (this.isConnected) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.isConnected = true;
|
|
39
|
+
this.__resizeObserver.observe(this.host);
|
|
40
|
+
this.__mutationObserver.observe(this.host, this.config.mutationObserverOptions);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Disconnects the layout from the host element.
|
|
45
|
+
*/
|
|
46
|
+
disconnect() {
|
|
47
|
+
if (!this.isConnected) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.isConnected = false;
|
|
52
|
+
this.__resizeObserver.disconnect();
|
|
53
|
+
this.__mutationObserver.disconnect();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Sets the properties of the layout controller.
|
|
58
|
+
*/
|
|
59
|
+
setProps(props) {
|
|
60
|
+
this.props = props;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Updates the layout based on the current properties.
|
|
65
|
+
*/
|
|
66
|
+
updateLayout() {
|
|
67
|
+
// To be implemented
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {ResizeObserverEntry[]} _entries
|
|
72
|
+
* @protected
|
|
73
|
+
*/
|
|
74
|
+
_onResize(_entries) {
|
|
75
|
+
// To be implemented
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {MutationRecord[]} _records
|
|
80
|
+
* @protected
|
|
81
|
+
*/
|
|
82
|
+
_onMutation(_records) {
|
|
83
|
+
// To be implemented
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2021 - 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';
|
|
7
|
+
import { AbstractLayout } from './abstract-layout.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Check if the node is a line break element.
|
|
11
|
+
*
|
|
12
|
+
* @param {HTMLElement} el
|
|
13
|
+
* @return {boolean}
|
|
14
|
+
*/
|
|
15
|
+
function isBreakLine(el) {
|
|
16
|
+
return el.localName === 'br';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A class that implements the auto-responsive layout algorithm.
|
|
21
|
+
* Not intended for public use.
|
|
22
|
+
*
|
|
23
|
+
* @private
|
|
24
|
+
*/
|
|
25
|
+
export class AutoResponsiveLayout extends AbstractLayout {
|
|
26
|
+
constructor(host) {
|
|
27
|
+
super(host, {
|
|
28
|
+
mutationObserverOptions: {
|
|
29
|
+
subtree: true,
|
|
30
|
+
childList: true,
|
|
31
|
+
attributes: true,
|
|
32
|
+
attributeFilter: ['colspan', 'data-colspan', 'hidden'],
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** @override */
|
|
38
|
+
connect() {
|
|
39
|
+
if (this.isConnected) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
super.connect();
|
|
44
|
+
|
|
45
|
+
this.updateLayout();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** @override */
|
|
49
|
+
disconnect() {
|
|
50
|
+
if (!this.isConnected) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
super.disconnect();
|
|
55
|
+
|
|
56
|
+
const { host } = this;
|
|
57
|
+
host.style.removeProperty('--_column-width');
|
|
58
|
+
host.style.removeProperty('--_max-columns');
|
|
59
|
+
host.$.layout.removeAttribute('fits-labels-aside');
|
|
60
|
+
host.$.layout.style.removeProperty('--_grid-rendered-column-count');
|
|
61
|
+
|
|
62
|
+
this.__children.forEach((child) => {
|
|
63
|
+
child.style.removeProperty('--_grid-colstart');
|
|
64
|
+
child.style.removeProperty('--_grid-colspan');
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** @override */
|
|
69
|
+
setProps(props) {
|
|
70
|
+
super.setProps(props);
|
|
71
|
+
|
|
72
|
+
if (this.isConnected) {
|
|
73
|
+
this.updateLayout();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** @override */
|
|
78
|
+
updateLayout() {
|
|
79
|
+
const { host, props } = this;
|
|
80
|
+
if (!this.isConnected || isElementHidden(host)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let columnCount = 0;
|
|
85
|
+
let maxColumns = 0;
|
|
86
|
+
|
|
87
|
+
const children = this.__children;
|
|
88
|
+
children
|
|
89
|
+
.filter((child) => isBreakLine(child) || !isElementHidden(child))
|
|
90
|
+
.forEach((child, index, children) => {
|
|
91
|
+
const prevChild = children[index - 1];
|
|
92
|
+
|
|
93
|
+
if (isBreakLine(child)) {
|
|
94
|
+
columnCount = 0;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (
|
|
99
|
+
(prevChild && prevChild.parentElement !== child.parentElement) ||
|
|
100
|
+
(!props.autoRows && child.parentElement === host)
|
|
101
|
+
) {
|
|
102
|
+
columnCount = 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (props.autoRows && columnCount === 0) {
|
|
106
|
+
child.style.setProperty('--_grid-colstart', 1);
|
|
107
|
+
} else {
|
|
108
|
+
child.style.removeProperty('--_grid-colstart');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const colspan = child.getAttribute('colspan') || child.getAttribute('data-colspan');
|
|
112
|
+
if (colspan) {
|
|
113
|
+
columnCount += parseInt(colspan);
|
|
114
|
+
child.style.setProperty('--_grid-colspan', colspan);
|
|
115
|
+
} else {
|
|
116
|
+
columnCount += 1;
|
|
117
|
+
child.style.removeProperty('--_grid-colspan');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
maxColumns = Math.max(maxColumns, columnCount);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
children.filter(isElementHidden).forEach((child) => {
|
|
124
|
+
child.style.removeProperty('--_grid-colstart');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
host.style.setProperty('--_column-width', props.columnWidth);
|
|
128
|
+
host.style.setProperty('--_max-columns', Math.min(props.maxColumns, maxColumns));
|
|
129
|
+
|
|
130
|
+
host.$.layout.toggleAttribute('fits-labels-aside', this.props.labelsAside && this.__fitsLabelsAside);
|
|
131
|
+
host.$.layout.style.setProperty('--_grid-rendered-column-count', this.__renderedColumnCount);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** @override */
|
|
135
|
+
_onResize() {
|
|
136
|
+
this.updateLayout();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** @override */
|
|
140
|
+
_onMutation(records) {
|
|
141
|
+
const shouldUpdateLayout = records.some(({ target }) => {
|
|
142
|
+
return (
|
|
143
|
+
target === this.host ||
|
|
144
|
+
target.parentElement === this.host ||
|
|
145
|
+
target.parentElement.localName === 'vaadin-form-row'
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
if (shouldUpdateLayout) {
|
|
149
|
+
this.updateLayout();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** @private */
|
|
154
|
+
get __children() {
|
|
155
|
+
return [...this.host.children].flatMap((child) => {
|
|
156
|
+
return child.localName === 'vaadin-form-row' ? [...child.children] : child;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** @private */
|
|
161
|
+
get __renderedColumnCount() {
|
|
162
|
+
// Calculate the number of rendered columns, excluding CSS grid auto columns (0px)
|
|
163
|
+
const { gridTemplateColumns } = getComputedStyle(this.host.$.layout);
|
|
164
|
+
return gridTemplateColumns.split(' ').filter((width) => width !== '0px').length;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** @private */
|
|
168
|
+
get __columnWidthWithLabelsAside() {
|
|
169
|
+
const { backgroundPositionY } = getComputedStyle(this.host.$.layout, '::before');
|
|
170
|
+
return parseFloat(backgroundPositionY);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** @private */
|
|
174
|
+
get __fitsLabelsAside() {
|
|
175
|
+
return this.host.offsetWidth >= this.__columnWidthWithLabelsAside;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2021 - 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';
|
|
7
|
+
import { AbstractLayout } from './abstract-layout.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
|
+
function naturalNumberOrOne(n) {
|
|
17
|
+
if (typeof n === 'number' && n >= 1 && n < Infinity) {
|
|
18
|
+
return Math.floor(n);
|
|
19
|
+
}
|
|
20
|
+
return 1;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A class that implements the layout algorithm based on responsive steps.
|
|
25
|
+
* Not intended for public use.
|
|
26
|
+
*
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
export class ResponsiveStepsLayout extends AbstractLayout {
|
|
30
|
+
constructor(host) {
|
|
31
|
+
super(host, {
|
|
32
|
+
mutationObserverOptions: {
|
|
33
|
+
subtree: true,
|
|
34
|
+
childList: true,
|
|
35
|
+
attributes: true,
|
|
36
|
+
attributeFilter: ['colspan', 'data-colspan', 'hidden'],
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** @override */
|
|
42
|
+
connect() {
|
|
43
|
+
if (this.isConnected) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
super.connect();
|
|
48
|
+
|
|
49
|
+
this.__selectResponsiveStep();
|
|
50
|
+
this.updateLayout();
|
|
51
|
+
|
|
52
|
+
requestAnimationFrame(() => this.__selectResponsiveStep());
|
|
53
|
+
requestAnimationFrame(() => this.updateLayout());
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** @override */
|
|
57
|
+
setProps(props) {
|
|
58
|
+
const { responsiveSteps } = props;
|
|
59
|
+
if (!Array.isArray(responsiveSteps)) {
|
|
60
|
+
throw new Error('Invalid "responsiveSteps" type, an Array is required.');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (responsiveSteps.length < 1) {
|
|
64
|
+
throw new Error('Invalid empty "responsiveSteps" array, at least one item is required.');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
responsiveSteps.forEach((step) => {
|
|
68
|
+
if (naturalNumberOrOne(step.columns) !== step.columns) {
|
|
69
|
+
throw new Error(`Invalid 'columns' value of ${step.columns}, a natural number is required.`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (step.minWidth !== undefined && !isValidCSSLength(step.minWidth)) {
|
|
73
|
+
throw new Error(`Invalid 'minWidth' value of ${step.minWidth}, a valid CSS length required.`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (step.labelsPosition !== undefined && ['aside', 'top'].indexOf(step.labelsPosition) === -1) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Invalid 'labelsPosition' value of ${step.labelsPosition}, 'aside' or 'top' string is required.`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
super.setProps(props);
|
|
84
|
+
|
|
85
|
+
if (this.isConnected) {
|
|
86
|
+
this.__selectResponsiveStep();
|
|
87
|
+
this.updateLayout();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** @override */
|
|
92
|
+
updateLayout() {
|
|
93
|
+
const { host } = this;
|
|
94
|
+
|
|
95
|
+
// Do not update layout when invisible
|
|
96
|
+
if (!this.isConnected || isElementHidden(host)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/*
|
|
101
|
+
The item width formula:
|
|
102
|
+
|
|
103
|
+
itemWidth = colspan / columnCount * 100% - columnSpacing
|
|
104
|
+
|
|
105
|
+
We have to subtract columnSpacing, because the column spacing space is taken
|
|
106
|
+
by item margins of 1/2 * spacing on both sides
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
const style = getComputedStyle(host);
|
|
110
|
+
const columnSpacing = style.getPropertyValue('--vaadin-form-layout-column-spacing');
|
|
111
|
+
|
|
112
|
+
const direction = style.direction;
|
|
113
|
+
const marginStartProp = `margin-${direction === 'ltr' ? 'left' : 'right'}`;
|
|
114
|
+
const marginEndProp = `margin-${direction === 'ltr' ? 'right' : 'left'}`;
|
|
115
|
+
|
|
116
|
+
const containerWidth = host.offsetWidth;
|
|
117
|
+
|
|
118
|
+
let col = 0;
|
|
119
|
+
Array.from(host.children)
|
|
120
|
+
.filter((child) => child.localName === 'br' || getComputedStyle(child).display !== 'none')
|
|
121
|
+
.forEach((child, index, children) => {
|
|
122
|
+
if (child.localName === 'br') {
|
|
123
|
+
// Reset column count on line break
|
|
124
|
+
col = 0;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const attrColspan = child.getAttribute('colspan') || child.getAttribute('data-colspan');
|
|
129
|
+
let colspan;
|
|
130
|
+
colspan = naturalNumberOrOne(parseFloat(attrColspan));
|
|
131
|
+
|
|
132
|
+
// Never span further than the number of columns
|
|
133
|
+
colspan = Math.min(colspan, this.__columnCount);
|
|
134
|
+
|
|
135
|
+
const childRatio = colspan / this.__columnCount;
|
|
136
|
+
child.style.width = `calc(${childRatio * 100}% - ${1 - childRatio} * ${columnSpacing})`;
|
|
137
|
+
|
|
138
|
+
if (col + colspan > this.__columnCount) {
|
|
139
|
+
// Too big to fit on this row, let's wrap it
|
|
140
|
+
col = 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// At the start edge
|
|
144
|
+
if (col === 0) {
|
|
145
|
+
child.style.setProperty(marginStartProp, '0px');
|
|
146
|
+
} else {
|
|
147
|
+
child.style.removeProperty(marginStartProp);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const nextIndex = index + 1;
|
|
151
|
+
const nextLineBreak = nextIndex < children.length && children[nextIndex].localName === 'br';
|
|
152
|
+
|
|
153
|
+
// At the end edge
|
|
154
|
+
if (col + colspan === this.__columnCount) {
|
|
155
|
+
child.style.setProperty(marginEndProp, '0px');
|
|
156
|
+
} else if (nextLineBreak) {
|
|
157
|
+
const colspanRatio = (this.__columnCount - col - colspan) / this.__columnCount;
|
|
158
|
+
child.style.setProperty(
|
|
159
|
+
marginEndProp,
|
|
160
|
+
`calc(${colspanRatio * containerWidth}px + ${colspanRatio} * ${columnSpacing})`,
|
|
161
|
+
);
|
|
162
|
+
} else {
|
|
163
|
+
child.style.removeProperty(marginEndProp);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Move the column counter
|
|
167
|
+
col = (col + colspan) % this.__columnCount;
|
|
168
|
+
|
|
169
|
+
if (child.localName === 'vaadin-form-item') {
|
|
170
|
+
if (this.__labelsOnTop) {
|
|
171
|
+
if (child.getAttribute('label-position') !== 'top') {
|
|
172
|
+
child.__useLayoutLabelPosition = true;
|
|
173
|
+
child.setAttribute('label-position', 'top');
|
|
174
|
+
}
|
|
175
|
+
} else if (child.__useLayoutLabelPosition) {
|
|
176
|
+
delete child.__useLayoutLabelPosition;
|
|
177
|
+
child.removeAttribute('label-position');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** @override */
|
|
184
|
+
_onResize() {
|
|
185
|
+
const { host } = this;
|
|
186
|
+
if (isElementHidden(host)) {
|
|
187
|
+
host.$.layout.style.opacity = '0';
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
this.__selectResponsiveStep();
|
|
192
|
+
this.updateLayout();
|
|
193
|
+
|
|
194
|
+
host.$.layout.style.opacity = '';
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** @override */
|
|
198
|
+
_onMutation(records) {
|
|
199
|
+
const shouldUpdateLayout = records.some(({ target }) => {
|
|
200
|
+
return target === this.host || target.parentElement === this.host;
|
|
201
|
+
});
|
|
202
|
+
if (shouldUpdateLayout) {
|
|
203
|
+
this.updateLayout();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** @private */
|
|
208
|
+
__selectResponsiveStep() {
|
|
209
|
+
const { host, props } = this;
|
|
210
|
+
// Iterate through responsiveSteps and choose the step
|
|
211
|
+
let selectedStep;
|
|
212
|
+
const tmpStyleProp = 'background-position';
|
|
213
|
+
props.responsiveSteps.forEach((step) => {
|
|
214
|
+
// Convert minWidth to px units for comparison
|
|
215
|
+
host.$.layout.style.setProperty(tmpStyleProp, step.minWidth);
|
|
216
|
+
const stepMinWidthPx = parseFloat(getComputedStyle(host.$.layout).getPropertyValue(tmpStyleProp));
|
|
217
|
+
|
|
218
|
+
// Compare step min-width with the host width, select the passed step
|
|
219
|
+
if (stepMinWidthPx <= host.offsetWidth) {
|
|
220
|
+
selectedStep = step;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
host.$.layout.style.removeProperty(tmpStyleProp);
|
|
224
|
+
|
|
225
|
+
// Sometimes converting units is not possible, e.g, when element is
|
|
226
|
+
// not connected. Then the `selectedStep` stays `undefined`.
|
|
227
|
+
if (selectedStep) {
|
|
228
|
+
// Apply the chosen responsive step's properties
|
|
229
|
+
this.__columnCount = selectedStep.columns;
|
|
230
|
+
this.__labelsOnTop = selectedStep.labelsPosition === 'top';
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -4,7 +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 type { Constructor } from '@open-wc/dedupe-mixin';
|
|
7
|
-
import type {
|
|
7
|
+
import type { SlotStylesMixinClass } from '@vaadin/component-base/src/slot-styles-mixin.js';
|
|
8
8
|
|
|
9
9
|
export type FormLayoutLabelsPosition = 'aside' | 'top';
|
|
10
10
|
|
|
@@ -19,7 +19,7 @@ export type FormLayoutResponsiveStep = {
|
|
|
19
19
|
*/
|
|
20
20
|
export declare function FormLayoutMixin<T extends Constructor<HTMLElement>>(
|
|
21
21
|
base: T,
|
|
22
|
-
): Constructor<
|
|
22
|
+
): Constructor<FormLayoutMixinClass> & Constructor<SlotStylesMixinClass> & T;
|
|
23
23
|
|
|
24
24
|
export declare class FormLayoutMixinClass {
|
|
25
25
|
/**
|
|
@@ -61,6 +61,87 @@ export declare class FormLayoutMixinClass {
|
|
|
61
61
|
*/
|
|
62
62
|
responsiveSteps: FormLayoutResponsiveStep[];
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Enables the auto responsive mode in which the component automatically creates and adjusts
|
|
66
|
+
* columns based on the container's width. Columns have a fixed width defined by `columnWidth`
|
|
67
|
+
* and their number increases up to the limit set by `maxColumns`. The component dynamically
|
|
68
|
+
* adjusts the number of columns as the container size changes. When this mode is enabled,
|
|
69
|
+
* the `responsiveSteps` are ignored.
|
|
70
|
+
*
|
|
71
|
+
* By default, each field is placed on a new row. To organize fields into rows, there are
|
|
72
|
+
* two options:
|
|
73
|
+
*
|
|
74
|
+
* 1. Use `<vaadin-form-row>` to explicitly group fields into rows.
|
|
75
|
+
*
|
|
76
|
+
* 2. Enable the `autoRows` property to automatically arrange fields in available columns,
|
|
77
|
+
* wrapping to a new row when necessary. `<br>` elements can be used to force a new row.
|
|
78
|
+
*
|
|
79
|
+
* @attr {boolean} auto-responsive
|
|
80
|
+
*/
|
|
81
|
+
autoResponsive: boolean;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* When `autoResponsive` is enabled, defines the width of each column.
|
|
85
|
+
* The value must be defined in CSS length units, e.g., `100px` or `13em`.
|
|
86
|
+
* The default value is `13em`.
|
|
87
|
+
*
|
|
88
|
+
* @attr {string} column-width
|
|
89
|
+
*/
|
|
90
|
+
columnWidth: string;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* When `autoResponsive` is enabled, defines the maximum number of columns
|
|
94
|
+
* that the layout can create. The layout will create columns up to this
|
|
95
|
+
* limit based on the available container width. The default value is `10`.
|
|
96
|
+
*
|
|
97
|
+
* @attr {number} max-columns
|
|
98
|
+
*/
|
|
99
|
+
maxColumns: number;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* When enabled with `autoResponsive`, distributes fields across columns
|
|
103
|
+
* by placing each field in the next available column and wrapping to
|
|
104
|
+
* the next row when the current row is full. `<br>` elements can be
|
|
105
|
+
* used to force a new row.
|
|
106
|
+
*
|
|
107
|
+
* @attr {boolean} auto-rows
|
|
108
|
+
*/
|
|
109
|
+
autoRows: boolean;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* When enabled with `autoResponsive`, `<vaadin-form-item>` prefers positioning
|
|
113
|
+
* labels beside the fields. If the layout is too narrow to fit a single column
|
|
114
|
+
* with side labels, they revert to their default position above the fields.
|
|
115
|
+
*
|
|
116
|
+
* To customize the label width and the gap between the label and the field,
|
|
117
|
+
* use the following CSS properties:
|
|
118
|
+
*
|
|
119
|
+
* - `--vaadin-form-layout-label-width`
|
|
120
|
+
* - `--vaadin-form-layout-label-spacing`
|
|
121
|
+
*
|
|
122
|
+
* @attr {boolean} labels-aside
|
|
123
|
+
*/
|
|
124
|
+
labelsAside: boolean;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* When `autoResponsive` is enabled, specifies whether the columns should expand
|
|
128
|
+
* in width to evenly fill any remaining space after the layout has created as
|
|
129
|
+
* many fixed-width (`columnWidth`) columns as possible within the `maxColumns`
|
|
130
|
+
* limit. The default value is `false`.
|
|
131
|
+
*
|
|
132
|
+
* @attr {boolean} expand-columns
|
|
133
|
+
*/
|
|
134
|
+
expandColumns: boolean;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* When `autoResponsive` is enabled, specifies whether fields should stretch
|
|
138
|
+
* to take up all available space within columns. This setting also applies
|
|
139
|
+
* to fields inside `<vaadin-form-item>` elements. The default value is `false`.
|
|
140
|
+
*
|
|
141
|
+
* @attr {boolean} expand-fields
|
|
142
|
+
*/
|
|
143
|
+
expandFields: boolean;
|
|
144
|
+
|
|
64
145
|
/**
|
|
65
146
|
* Update the layout.
|
|
66
147
|
*/
|