From ba231b1920e9f1b00ce6615e90a2b9c19ba285a3 Mon Sep 17 00:00:00 2001 From: Catalin Date: Tue, 13 Feb 2024 13:38:51 +0200 Subject: [PATCH 1/4] fix(grid): update filter options on lang change --- .../ui-grid-dropdown-filter.directive.ts | 49 +++++++++++++++++-- .../components/ui-grid/src/test/column.ts | 6 ++- .../ui-grid/src/ui-grid.component.html | 9 ++-- .../ui-grid/src/ui-grid.component.spec.ts | 31 +++++++++++- .../ui-grid/src/ui-grid.component.ts | 26 +--------- 5 files changed, 85 insertions(+), 36 deletions(-) diff --git a/projects/angular/components/ui-grid/src/filters/ui-grid-dropdown-filter.directive.ts b/projects/angular/components/ui-grid/src/filters/ui-grid-dropdown-filter.directive.ts index 6d7053fe2..bd94460a8 100644 --- a/projects/angular/components/ui-grid/src/filters/ui-grid-dropdown-filter.directive.ts +++ b/projects/angular/components/ui-grid/src/filters/ui-grid-dropdown-filter.directive.ts @@ -1,15 +1,23 @@ import { - isArray, isEqual, + isArray, + isEqual, } from 'lodash-es'; -import { BehaviorSubject } from 'rxjs'; +import { + BehaviorSubject, + Subject, + takeUntil, +} from 'rxjs'; import { Directive, + inject, Input, OnDestroy, + OnInit, } from '@angular/core'; import { ISuggestValueData } from '@uipath/angular/components/ui-suggest'; +import { UiGridIntl } from '../ui-grid.intl'; import { UiGridFilterDirective } from './ui-grid-filter'; /** @@ -46,7 +54,7 @@ export type ISuggestDropdownValueData = ISuggestValueData extends UiGridFilterDirective implements OnDestroy { +export class UiGridDropdownFilterDirective extends UiGridFilterDirective implements OnDestroy, OnInit { /** * The dropdown items. * @@ -56,9 +64,10 @@ export class UiGridDropdownFilterDirective extends UiGridFilterDirective i this._items = value ?? []; this.suggestItems = this._items.map((item, idx) => ({ id: idx + 1, - text: item.label, + text: this.intl.translateDropdownOption(item), data: item.value, })); + this._addNoFilterOption(); } get items() { return this._items!; } @@ -112,6 +121,7 @@ export class UiGridDropdownFilterDirective extends UiGridFilterDirective i * @ignore */ visible$ = new BehaviorSubject(true); + intl = inject(UiGridIntl, { optional: true }) ?? new UiGridIntl(); suggestValue: ISuggestDropdownValueData[] = []; /** @@ -122,6 +132,21 @@ export class UiGridDropdownFilterDirective extends UiGridFilterDirective i private _items: IDropdownOption[] = []; private _value: FilterDropdownPossibleOption; private _multi = false; + private _destroy$ = new Subject(); + + ngOnInit() { + this._addNoFilterOption(); + + this.intl.changes.pipe( + takeUntil(this._destroy$), + ).subscribe(() => { + this.items = this._items; + this.suggestValue = this.suggestValue.map(suggestValue => ({ + ...suggestValue, + text: this.intl.translateDropdownOption(this.findDropDownOptionBySuggestValue(suggestValue)!), + })); + }); + } /** * Updates the dropdown value. @@ -152,6 +177,8 @@ export class UiGridDropdownFilterDirective extends UiGridFilterDirective i ngOnDestroy() { super.ngOnDestroy(); this.filterChange.complete(); + this._destroy$.complete(); + this._destroy$.next(); } findDropDownOptionBySuggestValue(suggestValue: ISuggestDropdownValueData) { @@ -162,4 +189,18 @@ export class UiGridDropdownFilterDirective extends UiGridFilterDirective i return this.value != null && ((!isArray(this.value) && this.value?.value !== undefined) || (isArray(this.value) && this.value.length)); } + + private _addNoFilterOption() { + const allOption = { + id: -1, + text: this.intl.noFilterPlaceholder, + }; + if (!this.multi && this.showAllOption) { + if (this.suggestItems[0]?.id !== allOption.id) { + this.suggestItems = [allOption, ...this.suggestItems]; + } + } else { + this.suggestItems = this.suggestItems.filter(item => item.id !== allOption.id); + } + } } diff --git a/projects/angular/components/ui-grid/src/test/column.ts b/projects/angular/components/ui-grid/src/test/column.ts index 388371352..30c03d788 100644 --- a/projects/angular/components/ui-grid/src/test/column.ts +++ b/projects/angular/components/ui-grid/src/test/column.ts @@ -1,3 +1,4 @@ +import { TestBed } from '@angular/core/testing'; import * as faker from 'faker'; import { of } from 'rxjs'; @@ -20,7 +21,10 @@ export const generateColumn = () => { }; export const generateDropdownFilter = () => { - const dropdown = new UiGridDropdownFilterDirective(); + let dropdown!: UiGridDropdownFilterDirective; + TestBed.runInInjectionContext(() => { + dropdown = new UiGridDropdownFilterDirective(); + }); const items = faker.random.words(15) .split(' ') diff --git a/projects/angular/components/ui-grid/src/ui-grid.component.html b/projects/angular/components/ui-grid/src/ui-grid.component.html index 4baa801bb..b8d3c5696 100644 --- a/projects/angular/components/ui-grid/src/ui-grid.component.html +++ b/projects/angular/components/ui-grid/src/ui-grid.component.html @@ -749,7 +749,7 @@ [searchable]="false" [value]="suggestValue" [placeholder]="column.title ?? ''" - [items]="mapFilterOptions(column.dropdown!.suggestItems, column)" + [items]="column.dropdown!.suggestItems" [compactSummaryTemplate]="displayedFilterName" [maxSelectionConfig]="{ count: (disableFilterSelection$ | async) && (column.dropdown!.multi || selectedFilters === undefined) ? suggestValue.length : Infinity, @@ -768,10 +768,9 @@
{{ (column.dropdown!.multi && selectedFilters?.length > 1) - ? intl.translateMultiDropdownOptions(value?.[0]?.text, selectedFilters.length) - : value?.[0] - ? intl.translateDropdownOption(column.dropdown!.findDropDownOptionBySuggestValue(value[0])!) - : intl.noFilterPlaceholder }} + ? intl.translateMultiDropdownOptions(value[0].text!, selectedFilters.length) + : value?.[0]?.text ?? intl.noFilterPlaceholder + }}
diff --git a/projects/angular/components/ui-grid/src/ui-grid.component.spec.ts b/projects/angular/components/ui-grid/src/ui-grid.component.spec.ts index 2e2e51987..f6734b315 100644 --- a/projects/angular/components/ui-grid/src/ui-grid.component.spec.ts +++ b/projects/angular/components/ui-grid/src/ui-grid.component.spec.ts @@ -1488,14 +1488,21 @@ describe('Component: UiGrid', () => { let fixture: ComponentFixture; let component: TestFixtureGridHeaderWithFilterComponent; let grid: UiGridComponent; - + let intl: UiGridIntl; beforeEach(() => { + intl = new UiGridIntl(); TestBed.configureTestingModule({ imports: [ UiGridModule, NoopAnimationsModule, ], declarations: [TestFixtureGridHeaderWithFilterComponent], + providers: [ + { + provide: UiGridIntl, + useValue: intl, + }, + ], }); fixture = TestBed.createComponent(TestFixtureGridHeaderWithFilterComponent); @@ -1521,6 +1528,28 @@ describe('Component: UiGrid', () => { fixture.detectChanges(); }); + it('should update translation for filter options when language changes', fakeAsync(() => { + intl.translateDropdownOption = () => 'voila la traduction'; + intl.changes.next(); + fixture.detectChanges(); + + const filterContainer = fixture.debugElement.query(By.css('.ui-grid-dropdown-filter-container')); + const filterButton = filterContainer.query(By.css(selectors.filterDropdown)); + filterButton.nativeElement.dispatchEvent(EventGenerator.click); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + + const filterCheckboxes = fixture.debugElement.queryAll(By.css('mat-list-item mat-checkbox')); + filterCheckboxes[0].nativeElement.dispatchEvent(EventGenerator.click); + filterCheckboxes[1].nativeElement.dispatchEvent(EventGenerator.click); + fixture.detectChanges(); + + const filterSpan = fixture.debugElement.query(By.css('.ui-grid-filter-suggest span.text-ellipsis')); + expect(filterSpan.nativeElement.innerText).toEqual('voila la traduction (+1 other)'); + flush(); + })); + it('should emit all selected filter options', fakeAsync(() => { const filterContainer = fixture.debugElement.query(By.css('.ui-grid-dropdown-filter-container')); const filterButton = filterContainer.query(By.css(selectors.filterDropdown)); diff --git a/projects/angular/components/ui-grid/src/ui-grid.component.ts b/projects/angular/components/ui-grid/src/ui-grid.component.ts index 9c3a5979a..fd7cfcadd 100644 --- a/projects/angular/components/ui-grid/src/ui-grid.component.ts +++ b/projects/angular/components/ui-grid/src/ui-grid.component.ts @@ -65,10 +65,7 @@ import { } from '@angular/material/checkbox'; import { MatTooltip } from '@angular/material/tooltip'; import { QueuedAnnouncer } from '@uipath/angular/a11y'; -import { - ISuggestValue, - ISuggestValueData, -} from '@uipath/angular/components/ui-suggest'; +import { ISuggestValue } from '@uipath/angular/components/ui-suggest'; import { UiGridColumnDirective } from './body/ui-grid-column.directive'; import { UiGridExpandedRowDirective } from './body/ui-grid-expanded-row.directive'; @@ -78,7 +75,6 @@ import { UiGridRowActionDirective } from './body/ui-grid-row-action.directive'; import { UiGridRowCardViewDirective } from './body/ui-grid-row-card-view.directive'; import { UiGridRowConfigDirective } from './body/ui-grid-row-config.directive'; import { UiGridCustomSearchDirective } from './components/ui-grid-search/ui-grid-custom-search.directive'; -import { ISuggestDropdownValueData } from './filters/ui-grid-dropdown-filter.directive'; import { UiGridSearchFilterDirective } from './filters/ui-grid-search-filter.directive'; import { UiGridFooterDirective } from './footer/ui-grid-footer.directive'; import { UiGridHeaderDirective } from './header/ui-grid-header.directive'; @@ -1286,26 +1282,6 @@ export class UiGridComponent return dropdownHasValue || searchableHasValue; } - mapFilterOptions(items: ISuggestDropdownValueData[], column: UiGridColumnDirective) { - items = items - .filter(item => !!column.dropdown!.findDropDownOptionBySuggestValue(item)) - .map(item => { - const translatedText = this.intl.translateDropdownOption(column.dropdown!.findDropDownOptionBySuggestValue(item)!); - return { - ...item, - text: translatedText, - }; - }); - - if (column.dropdown?.multi || !column.dropdown?.showAllOption) { return items; } - - const allOption: ISuggestValueData = { - id: -1, - text: this.intl.noFilterPlaceholder, - }; - return items.some(v => v.data === undefined) ? items : [allOption, ...items]; - } - triggerColumnHeaderTooltip(event: FocusOrigin, tooltip: MatTooltip) { if (event === 'keyboard') { this.focusedColumnHeader = true; From bf76a2fa8ce8daddb6c9060e1451ce6f4bf99603 Mon Sep 17 00:00:00 2001 From: Catalin Date: Fri, 16 Feb 2024 15:16:52 +0200 Subject: [PATCH 2/4] fix(suggest): change default forceDisplayDropdownOverInput --- .../angular/components/ui-suggest/src/ui-suggest.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/angular/components/ui-suggest/src/ui-suggest.component.ts b/projects/angular/components/ui-suggest/src/ui-suggest.component.ts index 53a84540c..a6f6c407d 100644 --- a/projects/angular/components/ui-suggest/src/ui-suggest.component.ts +++ b/projects/angular/components/ui-suggest/src/ui-suggest.component.ts @@ -244,7 +244,7 @@ export class UiSuggestComponent extends UiSuggestMatFormFieldDirective * If true, component wil place the dropdown over the input */ @Input() - forceDisplayDropdownOverInput = false; + forceDisplayDropdownOverInput = true; /** * Configure if the component allows multi-selection. From c8fe01ba4dccb79090c604b7a863350ccc33546e Mon Sep 17 00:00:00 2001 From: Catalin Date: Fri, 16 Feb 2024 15:19:19 +0200 Subject: [PATCH 3/4] feat(grid): display margin shadow if scrollable --- .../ui-grid/src/_ui-grid-variables.scss | 1 + .../ui-grid/src/_ui-grid.theme.scss | 12 ++++++++++ .../ui-grid/src/ui-grid.component.html | 5 ++++ .../ui-grid/src/ui-grid.component.scss | 4 ++++ .../ui-grid/src/ui-grid.component.spec.ts | 12 ++++++++-- .../ui-grid/src/ui-grid.component.ts | 23 ++++++++++++++++--- 6 files changed, 52 insertions(+), 5 deletions(-) diff --git a/projects/angular/components/ui-grid/src/_ui-grid-variables.scss b/projects/angular/components/ui-grid/src/_ui-grid-variables.scss index cda37795f..3e404f5df 100644 --- a/projects/angular/components/ui-grid/src/_ui-grid-variables.scss +++ b/projects/angular/components/ui-grid/src/_ui-grid-variables.scss @@ -6,3 +6,4 @@ $ui-grid-header-pressed-color: var(--color-data-grid-pressed, #EAECED); $header-background-color: var(--color-background-secondary, #f4f5f7); $grid-actions-box-shadow: var(--color-background, #ffffff); $highlighted-entity-color: var(--color-primary, #0067df); +$scroll-margin-shadow-color: var(--color-background-inverse, #182027); diff --git a/projects/angular/components/ui-grid/src/_ui-grid.theme.scss b/projects/angular/components/ui-grid/src/_ui-grid.theme.scss index 4ad906570..fe22bc395 100644 --- a/projects/angular/components/ui-grid/src/_ui-grid.theme.scss +++ b/projects/angular/components/ui-grid/src/_ui-grid.theme.scss @@ -215,6 +215,18 @@ $ui-grid-opacity-transition: opacity $ui-grid-default-transition; background-color: $ui-grid-row-hover-color; } } + + .grid-margin-shadow { + position: sticky; + right: 0; + height: 100%; + + @if $is-dark { + box-shadow: -7px 20px 20px 4px #000000; + } @else { + box-shadow: -3px 5px 20px 1.6px $scroll-margin-shadow-color; + } + } } .ui-grid-sort-icon { diff --git a/projects/angular/components/ui-grid/src/ui-grid.component.html b/projects/angular/components/ui-grid/src/ui-grid.component.html index b8d3c5696..20f097613 100644 --- a/projects/angular/components/ui-grid/src/ui-grid.component.html +++ b/projects/angular/components/ui-grid/src/ui-grid.component.html @@ -283,6 +283,7 @@ class="ui-grid-header-cell ui-grid-scroll-size-compensation-cell ui-grid-feature-cell">
+
+
{ expect(gridTable.nativeElement.style.minWidth).toBe(columnWidthSum + 'px'); })); + it('should display margin shadow when the grid has horizontal scroll', fakeAsync(() => { + const widths = [250, 1000, 330, 400, 100, 100]; // large enough too cause overflow + beforeConfig({ widths }); + const tableMarginShadowDivs = fixture.debugElement.queryAll(By.css('.grid-margin-shadow')); + expect(tableMarginShadowDivs.length).toBe(widths.length); + })); + it('should increase the width of last column if default column width sum does not fill the table', fakeAsync(() => { const widths = [50, 50, 0, 50, 50, 50]; const lastColumnIdx = 4; // refresh btn & 1 missing column @@ -5028,13 +5035,13 @@ describe('Component: UiGrid', () => { expect(+newMinWidth.replace('px', '')).toBeLessThan(+startingMinWidth.replace('px', '')); })); - it(`should set overflow to visible if total width of columns does not exceed container width`, fakeAsync(() => { + it(`should set overflow to visible and hide margin shadow if total width of columns does not exceed container width`, fakeAsync(() => { const gridTable = fixture.debugElement.query(By.css('.ui-grid-table')); const options = fixture.debugElement .queryAll(By.css('.ui-grid-toggle-panel .mat-mdc-option:not(.mdc-list-item--disabled)')); expect(gridTable.styles.overflow).toEqual('visible'); - + expect(fixture.debugElement.queryAll(By.css('.grid-margin-shadow')).length).toBe(6); options.forEach(o => { const checkbox = o.query(By.css('.mat-pseudo-checkbox')); checkbox.nativeElement.dispatchEvent(EventGenerator.click); @@ -5044,6 +5051,7 @@ describe('Component: UiGrid', () => { fixture.detectChanges(); expect(gridTable.styles.overflow).toEqual('hidden'); + expect(fixture.debugElement.queryAll(By.css('.grid-margin-shadow')).length).toBe(0); })); }); diff --git a/projects/angular/components/ui-grid/src/ui-grid.component.ts b/projects/angular/components/ui-grid/src/ui-grid.component.ts index fd7cfcadd..d8dac4ac3 100644 --- a/projects/angular/components/ui-grid/src/ui-grid.component.ts +++ b/projects/angular/components/ui-grid/src/ui-grid.component.ts @@ -5,6 +5,8 @@ import { BehaviorSubject, combineLatest, defer, + fromEvent, + iif, merge, Observable, of, @@ -25,6 +27,7 @@ import { take, takeUntil, tap, + throttleTime, } from 'rxjs/operators'; import { @@ -110,6 +113,7 @@ const EXCLUDED_ROW_SELECTION_ELEMENTS = ['a', 'button', 'input', 'textarea', 'se const REFRESH_WIDTH = 50; const DEFAULT_VIRTUAL_SCROLL_ITEM_SIZE = 48; const DEFAULT_VIRTUAL_SCROLL_HIGH_DENSITY_ITEM_SIZE = 32; +const SCROLL_LIMIT_FOR_DISPLAYING_SHADOW = 10; @Component({ selector: 'ui-grid', @@ -827,6 +831,20 @@ export class UiGridComponent shareReplay(1), ); + shouldDisplayContainerShadow$ = defer(() => merge( + fromEvent(this._ref.nativeElement.querySelector('.ui-grid-table-container')!, 'scroll').pipe( + throttleTime(50, undefined, { trailing: true }), + map((event: any) => { + const { scrollWidth, scrollLeft, clientWidth } = event.target; + return Math.abs(scrollWidth - clientWidth - scrollLeft) >= SCROLL_LIMIT_FOR_DISPLAYING_SHADOW; + }), + ), + this.isOverflown$, + )).pipe( + distinctUntilChanged(), + shareReplay(), + ); + areFilersCollapsed$: Observable; /** @@ -862,10 +880,10 @@ export class UiGridComponent shareReplay(1), ); - isOverflown$ = this.minWidth$.pipe( + isOverflown$ = iif(() => this.isScrollable, this.minWidth$.pipe( map(minWidth => this._isOverflown(minWidth)), distinctUntilChanged(), - ); + ), of(false)); tableOverflowStyle$ = this.isOverflown$.pipe( map(value => value ? 'visible' : 'hidden'), @@ -1342,7 +1360,6 @@ export class UiGridComponent }, 0); return widthsPxSum + this._otherActionsWidth; - } private get _otherActionsWidth() { From bd52fd6b60ea3cc988e3b0af6d6ef0a77ab8ff2c Mon Sep 17 00:00:00 2001 From: Catalin Date: Fri, 16 Feb 2024 18:12:52 +0200 Subject: [PATCH 4/4] chore: bump version to v15.2.2 --- CHANGELOG.md | 14 ++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- projects/angular/package.json | 2 +- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cae78caf..fe06392ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ +# v15.2.2 (2024-02-16) +* **grid** display margin shadow if scrollable +* **suggest** change default forceDisplayDropdownOverInput +* **grid** update filter options on lang change + # v15.2.1 (2024-02-16) * **tree-select** add accessible props +# v15.2.0 (2024-07-02) +* **grid** add tests for high density mode +* **grid** add support for high density mode +* **grid** add tests for high density mode +* **grid** add support for high density mode + +# v15.1.7 (2024-07-02) +* **grid** add nullish for set items + # v15.1.6 (2024-07-02) * **grid** react on max filters count changes diff --git a/package-lock.json b/package-lock.json index 08423f00a..6b54e958d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "angular-components", - "version": "15.2.1", + "version": "15.2.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "angular-components", - "version": "15.2.1", + "version": "15.2.2", "license": "MIT", "dependencies": { "@angular/animations": "15.2.9", diff --git a/package.json b/package.json index a17f78d37..9a16f7dd1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-components", - "version": "15.2.1", + "version": "15.2.2", "author": { "name": "UiPath Inc", "url": "https://uipath.com" diff --git a/projects/angular/package.json b/projects/angular/package.json index e73ac30a9..bc615f1e0 100644 --- a/projects/angular/package.json +++ b/projects/angular/package.json @@ -1,6 +1,6 @@ { "name": "@uipath/angular", - "version": "15.2.1", + "version": "15.2.2", "license": "MIT", "author": { "name": "UiPath Inc",