Skip to content

Commit f441ce6

Browse files
committed
wip: rework form to use columns
1 parent 3a56e61 commit f441ce6

10 files changed

Lines changed: 718 additions & 410 deletions

File tree

packages/main/src/Form.ts

Lines changed: 46 additions & 212 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@ import type { Slot, DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.
33
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
44
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
55
import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js";
6-
import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
6+
import slot from "@ui5/webcomponents-base/dist/decorators/slot-strict.js";
77
import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js";
88
import type { AriaRole } from "@ui5/webcomponents-base";
99
import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
1010
import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js";
1111

12+
// Utils
13+
import { getFormItemLayoutValue, getGroupsColSpan } from "./form-utils/FormUtils.js";
14+
import type { Breakpoint } from "./form-utils/FormUtils.js";
15+
1216
// Template
1317
import FormTemplate from "./FormTemplate.js";
1418

@@ -22,20 +26,6 @@ import type TitleLevel from "./types/TitleLevel.js";
2226

2327
import { FORM_ACCESSIBLE_NAME } from "./generated/i18n/i18n-defaults.js";
2428

25-
const additionalStylesMap = new Map<string, string>();
26-
27-
const StepColumn = {
28-
"S": 1,
29-
"M": 2,
30-
"L": 3,
31-
"XL": 6,
32-
};
33-
34-
const breakpoints = ["S", "M", "L", "Xl"];
35-
const MAX_FORM_ITEM_CELLS = 12;
36-
const DEFAULT_FORM_ITEM_LAYOUT = "4fr 8fr 0fr";
37-
const DEFAULT_FORM_ITEM_LAYOUT_S = "1fr";
38-
3929
/**
4030
* Interface for components that can be slotted inside `ui5-form` as items.
4131
* @public
@@ -352,6 +342,10 @@ class Form extends UI5Element {
352342
*
353343
* **Note:** Mixing FormGroups and standalone FormItems (not belonging to a group) is not supported.
354344
* Either use FormGroups and make sure all FormItems are part of a FormGroup, or use just FormItems without any FormGroups.
345+
*
346+
* **Note:** As of version 2.21.0 the support for standalone FormItems (not belonging to a group) is deprecated.
347+
* We recommend using FormGroups, as they provide better accessibility and layout options.
348+
*
355349
* @public
356350
*/
357351
@slot({
@@ -368,40 +362,37 @@ class Form extends UI5Element {
368362
/**
369363
* @private
370364
*/
371-
@property({ type: Number })
365+
@property({ type: Number, noAttribute: true })
372366
columnsS = 1;
373-
@property({ type: Number })
367+
@property({ type: Number, noAttribute: true })
374368
labelSpanS = 12
375-
@property({ type: Number })
369+
@property({ type: Number, noAttribute: true })
376370
emptySpanS = 0
377371

378-
@property({ type: Number })
372+
@property({ type: Number, noAttribute: true })
379373
columnsM = 1;
380-
@property({ type: Number })
374+
@property({ type: Number, noAttribute: true })
381375
labelSpanM = 4;
382-
@property({ type: Number })
376+
@property({ type: Number, noAttribute: true })
383377
emptySpanM = 0
384378

385-
@property({ type: Number })
379+
@property({ type: Number, noAttribute: true })
386380
columnsL = 2;
387-
@property({ type: Number })
381+
@property({ type: Number, noAttribute: true })
388382
labelSpanL = 4;
389-
@property({ type: Number })
383+
@property({ type: Number, noAttribute: true })
390384
emptySpanL = 0
391385

392-
@property({ type: Number })
386+
@property({ type: Number, noAttribute: true })
393387
columnsXl = 3;
394-
@property({ type: Number })
388+
@property({ type: Number, noAttribute: true })
395389
labelSpanXl = 4;
396-
@property({ type: Number })
390+
@property({ type: Number, noAttribute: true })
397391
emptySpanXl = 0;
398392

399393
onBeforeRendering() {
400394
// Parse the layout and set it to the FormGroups/FormItems.
401-
this.setColumnLayout();
402-
403-
// Parse the labelSpan and emptySpan and set it to the FormGroups/FormItems.
404-
this.setFormItemLayout();
395+
this.parseLayoutConfiguration();
405396

406397
// Define how many columns a group should take.
407398
this.setGroupsColSpan();
@@ -411,15 +402,11 @@ class Form extends UI5Element {
411402
}
412403

413404
onAfterRendering() {
414-
// Create additional CSS for number of columns that are not supported by default.
415-
this.createAdditionalCSSStyleSheet();
416-
417405
this.setFastNavGroup();
418406
}
419407

420-
setColumnLayout() {
421-
const layoutArr = this.layout.split(" ");
422-
layoutArr.forEach((breakpoint: string) => {
408+
parseLayoutConfiguration() {
409+
this.layout.split(" ").forEach((breakpoint: string) => {
423410
if (breakpoint.startsWith("S")) {
424411
this.columnsS = parseInt(breakpoint.slice(1));
425412
} else if (breakpoint.startsWith("M")) {
@@ -430,9 +417,7 @@ class Form extends UI5Element {
430417
this.columnsXl = parseInt(breakpoint.slice(2));
431418
}
432419
});
433-
}
434420

435-
parseFormItemSpan() {
436421
this.labelSpan.split(" ").forEach((breakpoint: string) => {
437422
if (breakpoint.startsWith("S")) {
438423
this.labelSpanS = parseInt(breakpoint.slice(1));
@@ -458,44 +443,25 @@ class Form extends UI5Element {
458443
});
459444
}
460445

461-
setFormItemLayout() {
462-
this.parseFormItemSpan();
463-
464-
[
465-
{
466-
breakpoint: "S",
467-
labelSpan: this.labelSpanS,
468-
emptySpan: this.emptySpanS,
469-
},
470-
{
471-
breakpoint: "M",
472-
labelSpan: this.labelSpanM,
473-
emptySpan: this.emptySpanM,
474-
},
475-
{
476-
breakpoint: "L",
477-
labelSpan: this.labelSpanL,
478-
emptySpan: this.emptySpanL,
479-
},
480-
{
481-
breakpoint: "XL",
482-
labelSpan: this.labelSpanXl,
483-
emptySpan: this.emptySpanXl,
484-
},
485-
].forEach(layout => {
486-
if (this.isValidFormItemLayout(layout.labelSpan, layout.emptySpan)) {
487-
const formItemLayout = layout.labelSpan === MAX_FORM_ITEM_CELLS ? `1fr` : `${layout.labelSpan}fr ${MAX_FORM_ITEM_CELLS - (layout.labelSpan + layout.emptySpan)}fr ${layout.emptySpan}fr`;
488-
this.style.setProperty(`--ui5-form-item-layout-${layout.breakpoint}`, formItemLayout);
489-
} else {
490-
// eslint-disable-next-line
491-
console.warn(`Form :: invalid usage of emptySpan and/or labelSpan in ${layout.breakpoint} size. The labelSpan must be <=12 and when emptySpace is used - their combined values must not exceed 11.`)
492-
this.style.setProperty(`--ui5-form-item-layout-${layout.breakpoint}`, layout.breakpoint === "S" ? DEFAULT_FORM_ITEM_LAYOUT_S : DEFAULT_FORM_ITEM_LAYOUT);
493-
}
494-
});
495-
}
446+
getFormItemLayout(breakpoint: Breakpoint) {
447+
let labelSpan,
448+
emptySpan;
449+
450+
if (breakpoint === "S") {
451+
labelSpan = this.labelSpanS;
452+
emptySpan = this.emptySpanS;
453+
} else if (breakpoint === "M") {
454+
labelSpan = this.labelSpanM;
455+
emptySpan = this.emptySpanM;
456+
} else if (breakpoint === "L") {
457+
labelSpan = this.labelSpanL;
458+
emptySpan = this.emptySpanL;
459+
} else if (breakpoint === "XL") {
460+
labelSpan = this.labelSpanXl;
461+
emptySpan = this.emptySpanXl;
462+
}
496463

497-
isValidFormItemLayout(labelSpan: number, emptySpan: number) {
498-
return emptySpan === 0 ? labelSpan <= MAX_FORM_ITEM_CELLS : labelSpan + emptySpan <= MAX_FORM_ITEM_CELLS - 1;
464+
return getFormItemLayoutValue(breakpoint, labelSpan, emptySpan);
499465
}
500466

501467
setFastNavGroup() {
@@ -517,44 +483,13 @@ class Form extends UI5Element {
517483
});
518484

519485
sortedItems.forEach((item: IFormItem, idx: number) => {
520-
item.colsXl = this.getGroupsColSpan(this.columnsXl, itemsCount, idx, item);
521-
item.colsL = this.getGroupsColSpan(this.columnsL, itemsCount, idx, item);
522-
item.colsM = this.getGroupsColSpan(this.columnsM, itemsCount, idx, item);
523-
item.colsS = this.getGroupsColSpan(this.columnsS, itemsCount, idx, item);
486+
item.colsXl = getGroupsColSpan(this.columnsXl, itemsCount, idx, item);
487+
item.colsL = getGroupsColSpan(this.columnsL, itemsCount, idx, item);
488+
item.colsM = getGroupsColSpan(this.columnsM, itemsCount, idx, item);
489+
item.colsS = getGroupsColSpan(this.columnsS, itemsCount, idx, item);
524490
});
525491
}
526492

527-
getGroupsColSpan(cols: number, groups: number, index: number, group: IFormItem): number {
528-
// Case 0: column span is set from outside.
529-
if (group.columnSpan) {
530-
return group.columnSpan;
531-
}
532-
533-
// CASE 1: The number of available columns match the number of groups, or only 1 column is available - each group takes 1 column.
534-
// For example: 1 column - 1 group, 2 columns - 2 groups, 3 columns - 3 groups, 4columns - 4 groups
535-
if (cols === 1 || cols <= groups) {
536-
return 1;
537-
}
538-
539-
// CASE 2: The number of available columns IS multiple of the number of groups.
540-
// For example: 2 column - 1 group, 3 columns - 1 groups, 4 columns - 1 group, 4 columns - 2 groups
541-
if (cols % groups === 0) {
542-
return cols / groups;
543-
}
544-
545-
// CASE 3: The number of available columns IS NOT multiple of the number of groups.
546-
const MIN_COL_SPAN = 1;
547-
const delta = cols - groups;
548-
549-
// 7 cols & 4 groups => 2, 2, 2, 1
550-
if (delta <= groups) {
551-
return index < delta ? MIN_COL_SPAN + 1 : MIN_COL_SPAN;
552-
}
553-
554-
// 7 cols & 3 groups => 3, 2, 2
555-
return index === 0 ? MIN_COL_SPAN + (delta - groups) + 1 : MIN_COL_SPAN + 1;
556-
}
557-
558493
setItemsState() {
559494
this.items.forEach((item: IFormItem) => {
560495
item.itemSpacing = this.itemSpacing;
@@ -600,28 +535,6 @@ class Form extends UI5Element {
600535

601536
get groupItemsInfo(): Array<GroupItemsInfo> {
602537
return this.items.map((groupItem: IFormItem, index: number) => {
603-
const items = this.getItemsInfo((Array.from(groupItem.children) as Array<IFormItem>));
604-
breakpoints.forEach(breakpoint => {
605-
const cols = ((groupItem[`cols${breakpoint}` as keyof IFormItem]) as number || 1);
606-
const rows = Math.ceil(items.length / cols);
607-
const total = cols * rows;
608-
const lastRowColumns = (cols - (total - items.length) - 1); // all other indecies start from 0
609-
let currentItem = 0;
610-
611-
for (let i = 0; i < total; i++) {
612-
const column = Math.floor(i / rows);
613-
const row = i % rows;
614-
615-
if (row === rows - 1 && column > lastRowColumns) {
616-
// eslint-disable-next-line no-continue
617-
continue;
618-
}
619-
620-
items[currentItem].item.style.setProperty(`--ui5-form-item-order-${breakpoint}`, `${column + row * cols}`);
621-
currentItem++;
622-
}
623-
});
624-
625538
const accessibleNameRef = (groupItem as FormGroup).effectiveAccessibleNameRef;
626539

627540
return {
@@ -644,88 +557,9 @@ class Form extends UI5Element {
644557
return (items || this.items).map((item: IFormItem) => {
645558
return {
646559
item,
647-
// eslint-disable-next-line
648-
// TODO: remove classes and classMap after deleting the hbs template
649-
classes: item.columnSpan ? `ui5-form-item-span-${item.columnSpan}` : "",
650-
classMap: {
651-
[`ui5-form-item-span-${item.columnSpan}`]: item.columnSpan !== undefined,
652-
},
653560
};
654561
});
655562
}
656-
657-
createAdditionalCSSStyleSheet() {
658-
[
659-
{ breakpoint: "S", columns: this.columnsS },
660-
{ breakpoint: "M", columns: this.columnsM },
661-
{ breakpoint: "L", columns: this.columnsL },
662-
{ breakpoint: "XL", columns: this.columnsXl },
663-
].forEach(step => {
664-
const additionalStyle: string | undefined = this.getAdditionalCSS(step.breakpoint, step.columns);
665-
666-
if (additionalStyle) {
667-
this.shadowRoot!.adoptedStyleSheets = [...this.shadowRoot!.adoptedStyleSheets, this.getCSSStyleSheet(additionalStyle)];
668-
}
669-
});
670-
}
671-
672-
getAdditionalCSS(step: string, colsNumber: number): string | undefined {
673-
if (StepColumn[step as keyof typeof StepColumn] >= colsNumber) {
674-
return;
675-
}
676-
677-
const key = `${step}-${colsNumber}`;
678-
679-
if (!additionalStylesMap.has(key)) {
680-
let containerQuery;
681-
let supporedColumnsNumber = StepColumn.S;
682-
let stepSpanCSS = "";
683-
let cols = colsNumber;
684-
685-
if (step === "S") {
686-
supporedColumnsNumber = StepColumn.S;
687-
containerQuery = `@container (max-width: 599px) {`;
688-
} else if (step === "M") {
689-
supporedColumnsNumber = StepColumn.M;
690-
containerQuery = `@container (width > 599px) and (width < 1024px) {`;
691-
} else if (step === "L") {
692-
supporedColumnsNumber = StepColumn.L;
693-
containerQuery = `@container (width > 1023px) and (width < 1439px) {`;
694-
} else if (step === "XL") {
695-
containerQuery = `@container (min-width: 1440px) {`;
696-
supporedColumnsNumber = StepColumn.XL;
697-
}
698-
699-
while (cols > supporedColumnsNumber) {
700-
stepSpanCSS += `
701-
:host([columns-${step.toLocaleLowerCase()}="${cols}"]) .ui5-form-layout {
702-
grid-template-columns: repeat(${cols}, 1fr);
703-
}
704-
705-
.ui5-form-column-span${step}-${cols},
706-
.ui5-form-item-span-${cols} {
707-
grid-column: span ${cols};
708-
}
709-
710-
.ui5-form-column-span${step}-${cols} .ui5-form-group-layout {
711-
grid-template-columns: repeat(${cols}, 1fr);
712-
}
713-
`;
714-
cols--;
715-
}
716-
717-
const css = `${containerQuery}${stepSpanCSS}}`;
718-
additionalStylesMap.set(key, css);
719-
}
720-
721-
return additionalStylesMap.get(key)!;
722-
}
723-
724-
getCSSStyleSheet(cssText: string): CSSStyleSheet {
725-
const style = new CSSStyleSheet();
726-
style.replaceSync(cssText);
727-
return style;
728-
}
729563
}
730564

731565
Form.define();

packages/main/src/FormGroup.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ class FormGroup extends UI5Element implements IFormItem {
7474
@property({ type: Number })
7575
columnSpan?: number;
7676

77+
/**
78+
* Defines column span of the component as a string.
79+
*
80+
* @default undefined
81+
* @public
82+
*/
83+
@property()
84+
colSpan?: string;
85+
7786
/**
7887
* Defines the accessible ARIA name of the component.
7988
* @default undefined

packages/main/src/FormItem.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ class FormItem extends UI5Element implements IFormItem {
5656
* or the Form. The available columns can be affected by the FormGroup#columnSpan and/or the Form#layout.
5757
* A number bigger than the available columns won't take effect.
5858
*
59+
* @deprecated As of version 2.21.0, this property is deprecated.
60+
* **Note:** This property will not have any effect on the layout of the form item
5961
* @default undefined
6062
* @public
6163
*/

0 commit comments

Comments
 (0)