Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 46 additions & 212 deletions packages/main/src/Form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ import type { Slot, DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js";
import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
import slot from "@ui5/webcomponents-base/dist/decorators/slot-strict.js";
import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js";
import type { AriaRole } from "@ui5/webcomponents-base";
import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js";

// Utils
import { getFormItemLayoutValue, getGroupsColSpan } from "./form-utils/FormUtils.js";
import type { Breakpoint } from "./form-utils/FormUtils.js";

// Template
import FormTemplate from "./FormTemplate.js";

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

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

const additionalStylesMap = new Map<string, string>();

const StepColumn = {
"S": 1,
"M": 2,
"L": 3,
"XL": 6,
};

const breakpoints = ["S", "M", "L", "Xl"];
const MAX_FORM_ITEM_CELLS = 12;
const DEFAULT_FORM_ITEM_LAYOUT = "4fr 8fr 0fr";
const DEFAULT_FORM_ITEM_LAYOUT_S = "1fr";

/**
* Interface for components that can be slotted inside `ui5-form` as items.
* @public
Expand Down Expand Up @@ -352,6 +342,10 @@ class Form extends UI5Element {
*
* **Note:** Mixing FormGroups and standalone FormItems (not belonging to a group) is not supported.
* Either use FormGroups and make sure all FormItems are part of a FormGroup, or use just FormItems without any FormGroups.
*
* **Note:** As of version 2.21.0 the support for standalone FormItems (not belonging to a group) is deprecated.
* We recommend using FormGroups, as they provide better accessibility and layout options.
*
* @public
*/
@slot({
Expand All @@ -368,40 +362,37 @@ class Form extends UI5Element {
/**
* @private
*/
@property({ type: Number })
@property({ type: Number, noAttribute: true })
columnsS = 1;
@property({ type: Number })
@property({ type: Number, noAttribute: true })
labelSpanS = 12
@property({ type: Number })
@property({ type: Number, noAttribute: true })
emptySpanS = 0

@property({ type: Number })
@property({ type: Number, noAttribute: true })
columnsM = 1;
@property({ type: Number })
@property({ type: Number, noAttribute: true })
labelSpanM = 4;
@property({ type: Number })
@property({ type: Number, noAttribute: true })
emptySpanM = 0

@property({ type: Number })
@property({ type: Number, noAttribute: true })
columnsL = 2;
@property({ type: Number })
@property({ type: Number, noAttribute: true })
labelSpanL = 4;
@property({ type: Number })
@property({ type: Number, noAttribute: true })
emptySpanL = 0

@property({ type: Number })
@property({ type: Number, noAttribute: true })
columnsXl = 3;
@property({ type: Number })
@property({ type: Number, noAttribute: true })
labelSpanXl = 4;
@property({ type: Number })
@property({ type: Number, noAttribute: true })
emptySpanXl = 0;

onBeforeRendering() {
// Parse the layout and set it to the FormGroups/FormItems.
this.setColumnLayout();

// Parse the labelSpan and emptySpan and set it to the FormGroups/FormItems.
this.setFormItemLayout();
this.parseLayoutConfiguration();

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

onAfterRendering() {
// Create additional CSS for number of columns that are not supported by default.
this.createAdditionalCSSStyleSheet();

this.setFastNavGroup();
}

setColumnLayout() {
const layoutArr = this.layout.split(" ");
layoutArr.forEach((breakpoint: string) => {
parseLayoutConfiguration() {
this.layout.split(" ").forEach((breakpoint: string) => {
if (breakpoint.startsWith("S")) {
this.columnsS = parseInt(breakpoint.slice(1));
} else if (breakpoint.startsWith("M")) {
Expand All @@ -430,9 +417,7 @@ class Form extends UI5Element {
this.columnsXl = parseInt(breakpoint.slice(2));
}
});
}

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

setFormItemLayout() {
this.parseFormItemSpan();

[
{
breakpoint: "S",
labelSpan: this.labelSpanS,
emptySpan: this.emptySpanS,
},
{
breakpoint: "M",
labelSpan: this.labelSpanM,
emptySpan: this.emptySpanM,
},
{
breakpoint: "L",
labelSpan: this.labelSpanL,
emptySpan: this.emptySpanL,
},
{
breakpoint: "XL",
labelSpan: this.labelSpanXl,
emptySpan: this.emptySpanXl,
},
].forEach(layout => {
if (this.isValidFormItemLayout(layout.labelSpan, layout.emptySpan)) {
const formItemLayout = layout.labelSpan === MAX_FORM_ITEM_CELLS ? `1fr` : `${layout.labelSpan}fr ${MAX_FORM_ITEM_CELLS - (layout.labelSpan + layout.emptySpan)}fr ${layout.emptySpan}fr`;
this.style.setProperty(`--ui5-form-item-layout-${layout.breakpoint}`, formItemLayout);
} else {
// eslint-disable-next-line
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.`)
this.style.setProperty(`--ui5-form-item-layout-${layout.breakpoint}`, layout.breakpoint === "S" ? DEFAULT_FORM_ITEM_LAYOUT_S : DEFAULT_FORM_ITEM_LAYOUT);
}
});
}
getFormItemLayout(breakpoint: Breakpoint) {
let labelSpan,
emptySpan;

if (breakpoint === "S") {
labelSpan = this.labelSpanS;
emptySpan = this.emptySpanS;
} else if (breakpoint === "M") {
labelSpan = this.labelSpanM;
emptySpan = this.emptySpanM;
} else if (breakpoint === "L") {
labelSpan = this.labelSpanL;
emptySpan = this.emptySpanL;
} else if (breakpoint === "XL") {
labelSpan = this.labelSpanXl;
emptySpan = this.emptySpanXl;
}

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

setFastNavGroup() {
Expand All @@ -517,44 +483,13 @@ class Form extends UI5Element {
});

sortedItems.forEach((item: IFormItem, idx: number) => {
item.colsXl = this.getGroupsColSpan(this.columnsXl, itemsCount, idx, item);
item.colsL = this.getGroupsColSpan(this.columnsL, itemsCount, idx, item);
item.colsM = this.getGroupsColSpan(this.columnsM, itemsCount, idx, item);
item.colsS = this.getGroupsColSpan(this.columnsS, itemsCount, idx, item);
item.colsXl = getGroupsColSpan(this.columnsXl, itemsCount, idx, item);
item.colsL = getGroupsColSpan(this.columnsL, itemsCount, idx, item);
item.colsM = getGroupsColSpan(this.columnsM, itemsCount, idx, item);
item.colsS = getGroupsColSpan(this.columnsS, itemsCount, idx, item);
});
}

getGroupsColSpan(cols: number, groups: number, index: number, group: IFormItem): number {
// Case 0: column span is set from outside.
if (group.columnSpan) {
return group.columnSpan;
}

// CASE 1: The number of available columns match the number of groups, or only 1 column is available - each group takes 1 column.
// For example: 1 column - 1 group, 2 columns - 2 groups, 3 columns - 3 groups, 4columns - 4 groups
if (cols === 1 || cols <= groups) {
return 1;
}

// CASE 2: The number of available columns IS multiple of the number of groups.
// For example: 2 column - 1 group, 3 columns - 1 groups, 4 columns - 1 group, 4 columns - 2 groups
if (cols % groups === 0) {
return cols / groups;
}

// CASE 3: The number of available columns IS NOT multiple of the number of groups.
const MIN_COL_SPAN = 1;
const delta = cols - groups;

// 7 cols & 4 groups => 2, 2, 2, 1
if (delta <= groups) {
return index < delta ? MIN_COL_SPAN + 1 : MIN_COL_SPAN;
}

// 7 cols & 3 groups => 3, 2, 2
return index === 0 ? MIN_COL_SPAN + (delta - groups) + 1 : MIN_COL_SPAN + 1;
}

setItemsState() {
this.items.forEach((item: IFormItem) => {
item.itemSpacing = this.itemSpacing;
Expand Down Expand Up @@ -600,28 +535,6 @@ class Form extends UI5Element {

get groupItemsInfo(): Array<GroupItemsInfo> {
return this.items.map((groupItem: IFormItem, index: number) => {
const items = this.getItemsInfo((Array.from(groupItem.children) as Array<IFormItem>));
breakpoints.forEach(breakpoint => {
const cols = ((groupItem[`cols${breakpoint}` as keyof IFormItem]) as number || 1);
const rows = Math.ceil(items.length / cols);
const total = cols * rows;
const lastRowColumns = (cols - (total - items.length) - 1); // all other indecies start from 0
let currentItem = 0;

for (let i = 0; i < total; i++) {
const column = Math.floor(i / rows);
const row = i % rows;

if (row === rows - 1 && column > lastRowColumns) {
// eslint-disable-next-line no-continue
continue;
}

items[currentItem].item.style.setProperty(`--ui5-form-item-order-${breakpoint}`, `${column + row * cols}`);
currentItem++;
}
});

const accessibleNameRef = (groupItem as FormGroup).effectiveAccessibleNameRef;

return {
Expand All @@ -644,88 +557,9 @@ class Form extends UI5Element {
return (items || this.items).map((item: IFormItem) => {
return {
item,
// eslint-disable-next-line
// TODO: remove classes and classMap after deleting the hbs template
classes: item.columnSpan ? `ui5-form-item-span-${item.columnSpan}` : "",
classMap: {
[`ui5-form-item-span-${item.columnSpan}`]: item.columnSpan !== undefined,
},
};
});
}

createAdditionalCSSStyleSheet() {
[
{ breakpoint: "S", columns: this.columnsS },
{ breakpoint: "M", columns: this.columnsM },
{ breakpoint: "L", columns: this.columnsL },
{ breakpoint: "XL", columns: this.columnsXl },
].forEach(step => {
const additionalStyle: string | undefined = this.getAdditionalCSS(step.breakpoint, step.columns);

if (additionalStyle) {
this.shadowRoot!.adoptedStyleSheets = [...this.shadowRoot!.adoptedStyleSheets, this.getCSSStyleSheet(additionalStyle)];
}
});
}

getAdditionalCSS(step: string, colsNumber: number): string | undefined {
if (StepColumn[step as keyof typeof StepColumn] >= colsNumber) {
return;
}

const key = `${step}-${colsNumber}`;

if (!additionalStylesMap.has(key)) {
let containerQuery;
let supporedColumnsNumber = StepColumn.S;
let stepSpanCSS = "";
let cols = colsNumber;

if (step === "S") {
supporedColumnsNumber = StepColumn.S;
containerQuery = `@container (max-width: 599px) {`;
} else if (step === "M") {
supporedColumnsNumber = StepColumn.M;
containerQuery = `@container (width > 599px) and (width < 1024px) {`;
} else if (step === "L") {
supporedColumnsNumber = StepColumn.L;
containerQuery = `@container (width > 1023px) and (width < 1439px) {`;
} else if (step === "XL") {
containerQuery = `@container (min-width: 1440px) {`;
supporedColumnsNumber = StepColumn.XL;
}

while (cols > supporedColumnsNumber) {
stepSpanCSS += `
:host([columns-${step.toLocaleLowerCase()}="${cols}"]) .ui5-form-layout {
grid-template-columns: repeat(${cols}, 1fr);
}

.ui5-form-column-span${step}-${cols},
.ui5-form-item-span-${cols} {
grid-column: span ${cols};
}

.ui5-form-column-span${step}-${cols} .ui5-form-group-layout {
grid-template-columns: repeat(${cols}, 1fr);
}
`;
cols--;
}

const css = `${containerQuery}${stepSpanCSS}}`;
additionalStylesMap.set(key, css);
}

return additionalStylesMap.get(key)!;
}

getCSSStyleSheet(cssText: string): CSSStyleSheet {
const style = new CSSStyleSheet();
style.replaceSync(cssText);
return style;
}
}

Form.define();
Expand Down
9 changes: 9 additions & 0 deletions packages/main/src/FormGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ class FormGroup extends UI5Element implements IFormItem {
@property({ type: Number })
columnSpan?: number;

/**
* Defines column span of the component as a string.
*
* @default undefined
* @public
*/
@property()
colSpan?: string;

/**
* Defines the accessible ARIA name of the component.
* @default undefined
Expand Down
2 changes: 2 additions & 0 deletions packages/main/src/FormItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class FormItem extends UI5Element implements IFormItem {
* or the Form. The available columns can be affected by the FormGroup#columnSpan and/or the Form#layout.
* A number bigger than the available columns won't take effect.
*
* @deprecated As of version 2.21.0, this property is deprecated.
* **Note:** This property will not have any effect on the layout of the form item
* @default undefined
* @public
*/
Expand Down
Loading
Loading