From 0d670dd333253bfab6c1adebf3d5c7582db5c1f5 Mon Sep 17 00:00:00 2001 From: Diogo Bastos <50139916+DiogoABastos@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:09:03 +0000 Subject: [PATCH] AAE-27107 Improve repeatable section widget (#11336) * AAE-27107 Improve logic * AAE-27107 Add tests * AAE-27107 Fix logic * AAE-27107 Fix test * AAE-27107 Fix updateForm logic --- .../components/form-renderer.component.html | 31 ++- .../components/form-renderer.component.scss | 40 +++- .../form-renderer.component.spec.ts | 4 +- .../components/form-renderer.component.ts | 4 +- .../widgets/core/form-field.model.spec.ts | 225 +++++++++++++----- .../widgets/core/form-field.model.ts | 206 ++++++++++------ .../widgets/repeat/repeat.widget.html | 8 +- .../widgets/repeat/repeat.widget.scss | 19 +- .../widgets/repeat/repeat.widget.spec.ts | 40 +--- .../widgets/repeat/repeat.widget.ts | 6 - lib/core/src/lib/i18n/en.json | 1 + 11 files changed, 385 insertions(+), 199 deletions(-) diff --git a/lib/core/src/lib/form/components/form-renderer.component.html b/lib/core/src/lib/form/components/form-renderer.component.html index 40e8a1502b..7bfa224b65 100644 --- a/lib/core/src/lib/form/components/form-renderer.component.html +++ b/lib/core/src/lib/form/components/form-renderer.component.html @@ -81,8 +81,24 @@
-

{{ 'FORM.FORM_RENDERER.ROW_LABEL' | translate: {number: rowIndex + 1} }}

+ [class.adf-grid-list-container-disabled]="currentRootElement.field.readOnly" + > +
+ {{ 'FORM.FORM_RENDERER.ROW_LABEL' | translate: {number: rowIndex + 1} }} + + @let shouldDisplayRemoveRowButton = !currentRootElement.field.rows[rowIndex].isInitial || (currentRootElement.field.rows[rowIndex].isInitial && currentRootElement.field.params.allowInitialRowsDelete); + @if (shouldDisplayRemoveRowButton) { + + } +
@for (column of row.columns; track $index) {
} - - @let shouldDisplayRemoveRowButton = !currentRootElement.field.rows[rowIndex].isInitial || (currentRootElement.field.rows[rowIndex].isInitial && currentRootElement.field.params.allowInitialRowsDelete); - @if (shouldDisplayRemoveRowButton) { - - }
} diff --git a/lib/core/src/lib/form/components/form-renderer.component.scss b/lib/core/src/lib/form/components/form-renderer.component.scss index 5036a5d4ed..29e9a1c94b 100644 --- a/lib/core/src/lib/form/components/form-renderer.component.scss +++ b/lib/core/src/lib/form/components/form-renderer.component.scss @@ -98,28 +98,48 @@ padding-right: 3px; } - &-remove-row-button { - margin-top: 20px; + &-row { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; - #{ms.$mat-icon} { + &-remove-button { + padding: 0; + width: 30px; + height: 30px; display: flex; - justify-content: center; align-items: center; - font-size: 20px; + justify-content: center; + + #{ms.$mat-icon} { + display: flex; + justify-content: center; + align-items: center; + font-size: 18px; + } } } &-container { - padding: 0 10px; - - &-label { - margin: 5px 0 5px -10px; - } + padding: 0 15px; &-multiple { border-bottom: 1px solid rgba(0, 0, 0, 0.54); margin-bottom: 25px; } + + &-disabled { + &.adf-grid-list-container-multiple { + border-bottom: 1px solid + var(--mdc-text-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent)); + } + + .adf-grid-list-row-label { + color: var(--mdc-text-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent)); + cursor: default; + } + } } } diff --git a/lib/core/src/lib/form/components/form-renderer.component.spec.ts b/lib/core/src/lib/form/components/form-renderer.component.spec.ts index 929b5b9bfd..34606c6c41 100644 --- a/lib/core/src/lib/form/components/form-renderer.component.spec.ts +++ b/lib/core/src/lib/form/components/form-renderer.component.spec.ts @@ -921,7 +921,7 @@ describe('Form Renderer Component', () => { expect(row).toBeTruthy(); const removeRowButton = testingUtils.getByCSS( - '#field-RepeatableSection0tbw2y-container .adf-grid-list-container .adf-grid-list-remove-row-button' + '#field-RepeatableSection0tbw2y-container .adf-grid-list-container .adf-grid-list-row-remove-button' ); expect(removeRowButton).toBeTruthy(); @@ -938,7 +938,7 @@ describe('Form Renderer Component', () => { expect(row).toBeTruthy(); const removeRowButton = testingUtils.getByCSS( - '#field-RepeatableSection0tbw2y-container .adf-grid-list-container .adf-grid-list-remove-row-button' + '#field-RepeatableSection0tbw2y-container .adf-grid-list-container .adf-grid-list-row-remove-button' ); expect(removeRowButton).toBeFalsy(); diff --git a/lib/core/src/lib/form/components/form-renderer.component.ts b/lib/core/src/lib/form/components/form-renderer.component.ts index 8781c1d3a2..ff5c695e21 100644 --- a/lib/core/src/lib/form/components/form-renderer.component.ts +++ b/lib/core/src/lib/form/components/form-renderer.component.ts @@ -32,6 +32,7 @@ import { FormSectionComponent } from './form-section/form-section.component'; import { DecimalRenderMiddlewareService } from './middlewares/decimal-middleware.service'; import { MatDialog } from '@angular/material/dialog'; import { ConfirmDialogComponent } from '../../../lib/dialogs/confirm-dialog/confirm.dialog'; +import { MatTooltipModule } from '@angular/material/tooltip'; @Component({ selector: 'adf-form-renderer', @@ -63,7 +64,8 @@ import { ConfirmDialogComponent } from '../../../lib/dialogs/confirm-dialog/conf NgClass, HeaderWidgetComponent, FormSectionComponent, - RepeatWidgetComponent + RepeatWidgetComponent, + MatTooltipModule ], encapsulation: ViewEncapsulation.None }) diff --git a/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts b/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts index d769168a99..3ae5fb4f31 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts @@ -1272,61 +1272,121 @@ describe('FormFieldModel', () => { let form: FormModel; let field: FormFieldModel; + const fields = { + '1': [ + { + id: 'Text0wwp7n', + name: 'Text', + type: 'text', + readOnly: false, + required: false, + colspan: 1, + rowspan: 1, + placeholder: null, + minLength: 0, + maxLength: 0, + regexPattern: null, + visibilityCondition: null, + params: { + existingColspan: 1, + maxColspan: 2 + } + } + ], + '2': [ + { + id: 'Integer0rzkwq', + name: 'Integer', + type: 'integer', + readOnly: false, + colspan: 1, + rowspan: 1, + placeholder: null, + minValue: null, + maxValue: null, + required: false, + visibilityCondition: null, + params: { + existingColspan: 1, + maxColspan: 2 + } + } + ] + }; + + const fieldsDisabled = { + '1': [ + { + id: 'Text0wwp7n', + name: 'Text', + type: 'text', + readOnly: true, + required: false, + colspan: 1, + rowspan: 1, + placeholder: null, + minLength: 0, + maxLength: 0, + regexPattern: null, + visibilityCondition: null, + params: { + existingColspan: 1, + maxColspan: 2 + } + } + ], + '2': [ + { + id: 'Integer0rzkwq', + name: 'Integer', + type: 'integer', + readOnly: false, + colspan: 1, + rowspan: 1, + placeholder: null, + minValue: null, + maxValue: null, + required: false, + visibilityCondition: null, + params: { + existingColspan: 1, + maxColspan: 2 + } + } + ] + }; + + const json = { + id: 'RepeatableSection0tbw2y', + name: 'Repeatable Section', + type: 'repeatable-section', + tab: null, + params: { + initialNumberOfRows: 2, + allowInitialRowsDelete: true, + maxNumberOfRows: 5 + }, + numberOfColumns: 2, + fields + }; + + const jsonDisabled = { + id: 'RepeatableSection0tbw2y', + name: 'Repeatable Section', + type: 'repeatable-section', + tab: null, + params: { + initialNumberOfRows: 2, + allowInitialRowsDelete: true, + maxNumberOfRows: 5 + }, + numberOfColumns: 2, + fields: fieldsDisabled + }; + beforeEach(() => { form = new FormModel(); - field = new FormFieldModel(form, { - id: 'RepeatableSection0tbw2y', - name: 'Repeatable Section', - type: 'repeatable-section', - tab: null, - params: { - initialNumberOfRows: 2, - allowInitialRowsDelete: true, - newRowsLimit: 3 - }, - numberOfColumns: 2, - fields: { - '1': [ - { - id: 'Text0wwp7n', - name: 'Text', - type: 'text', - readOnly: false, - required: false, - colspan: 1, - rowspan: 1, - placeholder: null, - minLength: 0, - maxLength: 0, - regexPattern: null, - visibilityCondition: null, - params: { - existingColspan: 1, - maxColspan: 2 - } - } - ], - '2': [ - { - id: 'Integer0rzkwq', - name: 'Integer', - type: 'integer', - readOnly: false, - colspan: 1, - rowspan: 1, - placeholder: null, - minValue: null, - maxValue: null, - required: false, - visibilityCondition: null, - params: { - existingColspan: 1, - maxColspan: 2 - } - } - ] - } - }); + field = new FormFieldModel(form, json); }); describe('add row', () => { @@ -1408,5 +1468,64 @@ describe('FormFieldModel', () => { expect(form.values[field.id]).toEqual(formValues.removeState); }); }); + + describe('disabled state', () => { + const textWidgetId = 'Text0wwp7n'; + + /** + * + * @param expectation expectation function + */ + function checkChildrenWidgets(expectation: any) { + for (const row of field.rows) { + for (const column of row.columns) { + for (const child of column.fields) { + expectation(child); + } + } + } + } + it('should make all children fields disabled if repeatable section is disabled', () => { + field.readOnly = true; + + checkChildrenWidgets((child) => { + expect(child.readOnly).toBeTruthy(); + }); + }); + + it('should allow for initial disabled children widgets if repeatable section is enabled', () => { + field = new FormFieldModel(form, jsonDisabled); + + checkChildrenWidgets((child) => { + if (child.id.startsWith(textWidgetId)) { + expect(child.readOnly).toBeTruthy(); + return; + } + + expect(child.readOnly).toBeFalsy(); + }); + }); + + it('should keep initial disabled children widgets if repeatable section is re-enabled', () => { + field = new FormFieldModel(form, jsonDisabled); + field.rows[0].columns[0].fields[0].readOnly = true; + field.readOnly = true; + + checkChildrenWidgets((child) => { + expect(child.readOnly).toBeTruthy(); + }); + + field.readOnly = false; + + checkChildrenWidgets((child) => { + if (child.id.startsWith(textWidgetId)) { + expect(child.readOnly).toBeTruthy(); + return; + } + + expect(child.readOnly).toBeFalsy(); + }); + }); + }); }); }); diff --git a/lib/core/src/lib/form/components/widgets/core/form-field.model.ts b/lib/core/src/lib/form/components/widgets/core/form-field.model.ts index efefa06177..a7c309c43e 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field.model.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field.model.ts @@ -137,6 +137,12 @@ export class FormFieldModel extends FormWidgetModel { set readOnly(readOnly: boolean) { this._readOnly = readOnly; + + if (this.type === FormFieldTypes.REPEATABLE_SECTION) { + this.updateRepeatableSectionReadOnlyState(readOnly); + return; + } + this.updateForm(); } @@ -312,7 +318,7 @@ export class FormFieldModel extends FormWidgetModel { } const col = new ContainerColumnModel(); - col.fields = (fields[currentField] || []).map((field: any) => new FormFieldModel(form, field)); + col.fields = (fields[currentField] || []).map((field: any) => new FormFieldModel(form, field, this.setupParentConfig(field))); col.rowspan = fields[currentField].length; if (!FormFieldTypes.isSectionType(this.type)) { @@ -324,6 +330,18 @@ export class FormFieldModel extends FormWidgetModel { }); } + private setupParentConfig(field: any) { + if (this.parent) { + return { + ...this.parent, + uid: this.getUniqueId(field, this.parent.uid.split(ROW_ID_PREFIX)[1]), + value: this.parent.value?.[field.id] + }; + } + + return undefined; + } + private repeatableSectionFactory(json: any, form: any): void { const { numberOfColumns = 1, params, value, fields = {} } = json; @@ -333,26 +351,26 @@ export class FormFieldModel extends FormWidgetModel { this.colspan = 1; this.rows = []; - for (let i = 0; i < this.getNumberOfRows(params.initialNumberOfRows, params.newRowsLimit, value); i++) { + for (let i = 0; i < this.getNumberOfRows(params.initialNumberOfRows, params.maxNumberOfRows, value); i++) { this.rows.push(this.createRow(fields, form, i, value?.[i], i < params?.initialNumberOfRows)); } this.columns = this.rows[0].columns; } - private getNumberOfRows(initialNrRows: number = 1, rowsLimit?: number, value?: any) { - return value?.length && !!rowsLimit ? Math.min(value?.length, initialNrRows + rowsLimit) : (value?.length ?? initialNrRows); + private getNumberOfRows(initialNrRows: number = 1, maxNrRows?: number, value?: any) { + return value?.length ? (maxNrRows ? Math.min(value.length, maxNrRows) : value.length) : initialNrRows; } private createRow(fields: any, form: any, index: number, value?: any, isInitial: boolean = false) { const row = new ContainerRowModel(isInitial); - row.columns.push(...this.createColumns(fields, form, index, value)); + row.columns.push(...this.createColumns(fields, form, row.id, index, value)); return row; } - private createColumns(fields: any, form: any, index?: number, value?: any) { + private createColumns(fields: any, form: any, rowId: string, index?: number, value?: any) { const columns: ContainerColumnModel[] = []; Object.keys(fields).forEach((currentField) => { @@ -365,10 +383,10 @@ export class FormFieldModel extends FormWidgetModel { (field: any) => new FormFieldModel(form, field, { id: this.id, - uid: this.getUniqueId(field), + uid: this.getUniqueId(field, rowId), fields: this.fields, rowIndex: index ?? 0, - value: value?.[field.id] + value: field.type === FormFieldTypes.SECTION ? value : value?.[field.id] }) ); col.rowspan = fields[currentField].length; @@ -384,8 +402,34 @@ export class FormFieldModel extends FormWidgetModel { return columns; } - private getUniqueId(field: FormFieldModel): string { - return field.id + ROW_ID_PREFIX + window.crypto.getRandomValues(new Uint32Array(1))[0].toString(); + private updateRepeatableSectionReadOnlyState(state: boolean) { + for (const row of this.rows) { + for (const column of row.columns) { + for (const field of column.fields) { + if (field.type === FormFieldTypes.SECTION) { + this.updateInnerSectionReadOnlyState(field, state); + } + + field.readOnly = this.getRepeatableSectionFieldReadOnlyState(field, state); + } + } + } + } + + private updateInnerSectionReadOnlyState(section: FormFieldModel, state: boolean) { + for (const column of section.columns) { + for (const field of column.fields) { + field.readOnly = this.getRepeatableSectionFieldReadOnlyState(field, state); + } + } + } + + private getRepeatableSectionFieldReadOnlyState(field: FormFieldModel, state: boolean): boolean { + return state || field.json.readOnly; + } + + private getUniqueId(field: FormFieldModel, rowId: string): string { + return field.id + ROW_ID_PREFIX + rowId; } private updateChildrenFieldsRowIndex() { @@ -400,11 +444,23 @@ export class FormFieldModel extends FormWidgetModel { private createInitialValue(fields: any) { return Object.keys(fields) - .map((currentField) => (fields[currentField] || []).map((field) => field.id)) - .flat(1) + .map((currentField) => (fields[currentField] || []).map((field) => this.getFieldId(field))) + .flat(2) .reduce((acc, curr) => ((acc[curr] = null), acc), {}); } + private getFieldId(field: any) { + if (field.type === FormFieldTypes.SECTION) { + const fields = field.fields; + + return Object.keys(fields) + .map((currentField) => (fields[currentField] || []).map((e) => e.id)) + .flat(1); + } + + return field.id; + } + private updateContainerColspan(fields: FormFieldModel[]): void { fields.forEach((colField: FormFieldModel) => { this.colspan = Math.max(this.colspan, colField.colspan); @@ -420,7 +476,7 @@ export class FormFieldModel extends FormWidgetModel { } private shouldAddRow(): boolean { - return !this.params.newRowsLimit || this.rows.length < this.params.initialNumberOfRows + this.params.newRowsLimit; + return !this.params.maxNumberOfRows || this.rows.length < this.params.maxNumberOfRows; } removeRow(index: number) { @@ -526,7 +582,7 @@ export class FormFieldModel extends FormWidgetModel { } if (this.isCheckboxField(json)) { - return json.value === 'true' || json.value === true; + return json.value === 'true' || json.value === true || initialValue === true || initialValue === 'true'; } return value; @@ -537,16 +593,22 @@ export class FormFieldModel extends FormWidgetModel { return; } + const formValue = this.getFormValue(); + if (this.parent) { - this.updateRepeatableSectionValue(); - return; + this.updateRepeatableSectionValue(formValue); + } else { + this.updateValue(formValue); } + this.form.onFormFieldChanged(this); + } + + getFormValue() { switch (this.type) { case FormFieldTypes.DROPDOWN: { if (!this.value) { - this.form.values[this.id] = null; - break; + return null; } /* @@ -554,63 +616,57 @@ export class FormFieldModel extends FormWidgetModel { but saving back as object: { id: , name: } */ if (Array.isArray(this.value)) { - this.form.values[this.id] = this.value; - break; + return this.value; } if (typeof this.value === 'string') { if (this.value === 'empty' || this.value === '') { - this.form.values[this.id] = null; - break; + return null; } const matchingOption: FormFieldOption = this.options.find((opt) => opt.id === this.value); - this.form.values[this.id] = matchingOption || null; + return matchingOption || null; } if (typeof this.value === 'object') { if (this.value.id === 'empty' || this.value.id === '') { - this.form.values[this.id] = null; - break; + return null; } const matchingOption: FormFieldOption = this.options.find((opt) => opt.id === this.value.id); - this.form.values[this.id] = matchingOption; + return matchingOption; } - break; + + return null; } case FormFieldTypes.RADIO_BUTTONS: { const radioButton: FormFieldOption = this.options.find((opt) => opt.id === this.value); if (this.optionType === 'rest') { - this.form.values[this.id] = radioButton - ? { ...radioButton, options: this.options } - : { id: null, name: null, options: this.options }; - } else { - this.form.values[this.id] = radioButton ? { ...radioButton } : null; + return radioButton ? { ...radioButton, options: this.options } : { id: null, name: null, options: this.options }; } - break; + return radioButton ? { ...radioButton } : null; } case FormFieldTypes.UPLOAD: { this.form.hasUpload = true; + if (this.value && this.value.length > 0) { - this.form.values[this.id] = Array.isArray(this.value) ? this.value.map((elem) => elem.id).join(',') : [this.value]; - } else { - this.form.values[this.id] = null; + return Array.isArray(this.value) ? this.value.map((elem) => elem.id).join(',') : [this.value]; } - break; + + return null; } case FormFieldTypes.TYPEAHEAD: { const typeAheadEntry: FormFieldOption[] = this.options.filter((opt) => opt.id === this.value || opt.name === this.value); + if (typeAheadEntry.length > 0) { - this.form.values[this.id] = typeAheadEntry[0]; - } else if (this.options.length > 0) { - this.form.values[this.id] = null; + return typeAheadEntry[0]; } - break; + + return null; } case FormFieldTypes.DATE: { if (typeof this.value === 'string' && this.value === 'today') { @@ -618,6 +674,7 @@ export class FormFieldModel extends FormWidgetModel { } let dateValue; + try { dateValue = DateFnsUtils.parseDate(this.value, this.dateDisplayFormat); } catch { @@ -626,12 +683,13 @@ export class FormFieldModel extends FormWidgetModel { if (isValidDate(dateValue)) { const datePart = DateFnsUtils.formatDate(dateValue, 'yyyy-MM-dd'); - this.form.values[this.id] = `${datePart}T00:00:00.000Z`; - } else { - this.form.values[this.id] = null; - this._value = this.value; + + return `${datePart}T00:00:00.000Z`; } - break; + + this._value = this.value; + + return null; } case FormFieldTypes.DATETIME: { if (typeof this.value === 'string' && this.value === 'now') { @@ -641,39 +699,32 @@ export class FormFieldModel extends FormWidgetModel { const dateTimeValue = this.value !== null ? DateFnsUtils.getDate(this.value) : null; if (isValidDate(dateTimeValue)) { - this.form.values[this.id] = dateTimeValue.toISOString(); - } else { - this.form.values[this.id] = null; - this._value = this.value; + return dateTimeValue.toISOString(); } - break; + + this._value = this.value; + + return null; } case FormFieldTypes.NUMBER: { - this.form.values[this.id] = this.enableFractions ? parseFloat(this.value) : parseInt(this.value, 10); - break; + return this.enableFractions ? parseFloat(this.value) : parseInt(this.value, 10); } case FormFieldTypes.AMOUNT: { - this.form.values[this.id] = this.enableFractions ? parseFloat(this.value) : parseInt(this.value, 10); - break; + return this.enableFractions ? parseFloat(this.value) : parseInt(this.value, 10); } case FormFieldTypes.DECIMAL: { - this.form.values[this.id] = parseFloat(this.value); - break; + return parseFloat(this.value); } case FormFieldTypes.BOOLEAN: { - this.form.values[this.id] = this.value !== null && this.value !== undefined ? this.value : false; - break; + return this.value !== null && this.value !== undefined ? this.value : false; } case FormFieldTypes.PEOPLE: { - this.form.values[this.id] = this.value ? this.value : null; - break; + return this.value ? this.value : null; } case FormFieldTypes.FUNCTIONAL_GROUP: { - this.form.values[this.id] = this.value ? this.value : null; - break; + return this.value ? this.value : null; } case FormFieldTypes.REPEATABLE_SECTION: { - this.form.values[this.id] = this.value ? this.value : []; this.repeatableSectionFactory( { ...this.json, @@ -681,18 +732,31 @@ export class FormFieldModel extends FormWidgetModel { }, this.form ); - break; + + return this.value ? this.value : this.form.values[this.id]; } default: if (this.shouldUpdateFormValues(this.type)) { - this.form.values[this.id] = this.value; + return this.value; } - } - this.form.onFormFieldChanged(this); + return undefined; + } } - private updateRepeatableSectionValue() { + private updateValue(value: any) { + if (value === undefined) { + return; + } + + this.form.values[this.id] = value; + } + + private updateRepeatableSectionValue(value: string) { + if (this.type === FormFieldTypes.SECTION) { + return; + } + if (!this.form.values[this.parent.id]) { this.form.values[this.parent.id] = []; } @@ -701,9 +765,7 @@ export class FormFieldModel extends FormWidgetModel { this.form.values[this.parent.id][this.parent.rowIndex] = this.createInitialValue(this.parent.fields); } - this.form.values[this.parent.id][this.parent.rowIndex][this.id.split(ROW_ID_PREFIX)[0]] = this.value; - - this.form.onFormFieldChanged(this); + this.form.values[this.parent.id][this.parent.rowIndex][this.id.split(ROW_ID_PREFIX)[0]] = value; } /** diff --git a/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.html b/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.html index 1366112f9a..cb46d4eacb 100644 --- a/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.html +++ b/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.html @@ -1,4 +1,4 @@ -
+

@if (!isEditor) { - @let shouldDisplayAddRowButton = !element.field.params.newRowsLimit || (element.field.params.newRowsLimit > getAddedRowsCount()); + @let shouldDisplayAddRowButton = !element.field.params.maxNumberOfRows || (element.field.params.maxNumberOfRows > element.field.rows.length); @if (shouldDisplayAddRowButton) { } @else { - @let rowLimit = element.field.params.initialNumberOfRows + (element.field.params.newRowsLimit ?? 0); - {{ 'FORM.FIELD.REPEATABLE_SECTION.ROW_LIMIT_REACHED' | translate: { limit: rowLimit } }} + {{ 'FORM.FIELD.REPEATABLE_SECTION.ROW_LIMIT_REACHED' | translate: { limit: element.field.params.maxNumberOfRows } }} } }

diff --git a/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.scss b/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.scss index 777d30e7c7..95c2b6382e 100644 --- a/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.scss +++ b/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.scss @@ -8,10 +8,6 @@ font-weight: var(--adf-header-font-weight); color: var(--adf-header-color); line-height: normal; - - &.adf-collapsible { - cursor: pointer; - } } &-row-action { @@ -23,3 +19,18 @@ font-size: 12px; } } + +.adf-readonly { + .adf-container-widget { + &-repeat__text { + color: var(--mdc-text-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent)); + border-bottom: 1px solid var(--mdc-text-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent)); + cursor: default; + } + + &-row-limit { + color: var(--mdc-text-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent)); + font-size: 12px; + } + } +} diff --git a/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.spec.ts b/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.spec.ts index dcbccdfede..1041dde171 100644 --- a/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.spec.ts +++ b/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.spec.ts @@ -30,11 +30,11 @@ describe('RepeatWidgetComponent', () => { /** * * @param initialNumberOfRows initial number of rows - * @param newRowsLimit limit for additional rows + * @param maxNumberOfRows maximum number of rows * @param allowInitialRowsDelete should allow deleting rows * @returns repeatable section json based on params */ - function getFormFieldJson(initialNumberOfRows: number = 2, newRowsLimit?: number, allowInitialRowsDelete: boolean = true) { + function getFormFieldJson(initialNumberOfRows: number = 2, maxNumberOfRows?: number, allowInitialRowsDelete: boolean = true) { return { id: 'RepeatableSection0tbw2y', name: 'Repeatable Section', @@ -43,7 +43,7 @@ describe('RepeatWidgetComponent', () => { params: { initialNumberOfRows, allowInitialRowsDelete, - newRowsLimit + maxNumberOfRows }, numberOfColumns: 2, fields: { @@ -126,7 +126,7 @@ describe('RepeatWidgetComponent', () => { }); it('should display add row button if limit is defined but not reached', () => { - component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 1))); + component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 3))); fixture.detectChanges(); @@ -135,8 +135,7 @@ describe('RepeatWidgetComponent', () => { }); it('should NOT display add row button if limit is defined and reached', () => { - component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 1))); - spyOn(component, 'getAddedRowsCount').and.returnValue(1); + component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 2))); fixture.detectChanges(); @@ -145,8 +144,7 @@ describe('RepeatWidgetComponent', () => { }); it('should display row limit if limit has been reached', () => { - component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 1))); - spyOn(component, 'getAddedRowsCount').and.returnValue(1); + component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 2))); fixture.detectChanges(); @@ -165,31 +163,5 @@ describe('RepeatWidgetComponent', () => { testingUtils.clickByCSS('button.adf-container-widget-row-action'); expect(component.addRow).toHaveBeenCalled(); }); - - describe('getAddedRowsCount', () => { - it('should get correct rows count if initial rows are allowed to be deleted', () => { - component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 1))); - - fixture.detectChanges(); - - expect(component.getAddedRowsCount()).toBe(0); - - component.addRow(); - - fixture.detectChanges(); - - expect(component.getAddedRowsCount()).toBe(1); - }); - - it('should get correct rows count if initial rows are NOT allowed to be deleted', () => { - component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 1, false))); - - expect(component.getAddedRowsCount()).toBe(0); - - component.addRow(); - - expect(component.getAddedRowsCount()).toBe(1); - }); - }); }); }); diff --git a/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.ts b/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.ts index c30159ce19..c0437d86c0 100644 --- a/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.ts +++ b/lib/core/src/lib/form/components/widgets/repeat/repeat.widget.ts @@ -36,10 +36,4 @@ export class RepeatWidgetComponent { addRow() { this.element.field.addRow(this.element.json.fields, this.element.form); } - - getAddedRowsCount(): number { - return this.element.json.params.allowInitialRowsDelete - ? this.element.field.rows.length - this.element.json.params.initialNumberOfRows - : this.element.field.rows.filter((row) => !row.isInitial).length; - } } diff --git a/lib/core/src/lib/i18n/en.json b/lib/core/src/lib/i18n/en.json index 1386fb140d..86d8661b24 100644 --- a/lib/core/src/lib/i18n/en.json +++ b/lib/core/src/lib/i18n/en.json @@ -78,6 +78,7 @@ "FORM_RENDERER": { "NAMELESS_TASK": "Nameless task", "ROW_LABEL": "Row {{ number }}", + "DELETE_ROW": "Delete row", "REMOVE_ROW_DIALOG": { "TITLE": "Delete the row", "MESSAGE": "Are you sure you want to delete this row?",