From be9629da51394326eee2579597b1b4b922337988 Mon Sep 17 00:00:00 2001 From: Denis Severin Date: Mon, 18 Oct 2021 14:37:47 +0300 Subject: [PATCH 1/3] fix(core): rely on resize observer instead of window resize event --- ...-page-column-layout-example.component.html | 11 ++++ libs/core/src/lib/tabs/tab-list.component.ts | 29 +++++++---- libs/core/src/lib/utils/functions/index.ts | 1 + .../utils/functions/resize-observable.spec.ts | 51 +++++++++++++++++++ .../lib/utils/functions/resize-observable.ts | 27 ++++++++++ 5 files changed, 109 insertions(+), 10 deletions(-) create mode 100644 libs/core/src/lib/utils/functions/resize-observable.spec.ts create mode 100644 libs/core/src/lib/utils/functions/resize-observable.ts diff --git a/apps/docs/src/app/core/component-docs/dynamic-page/dynamic-page-examples/dynamic-page-column-layout-example/dynamic-page-column-layout-example.component.html b/apps/docs/src/app/core/component-docs/dynamic-page/dynamic-page-examples/dynamic-page-column-layout-example/dynamic-page-column-layout-example.component.html index 80abc0cedd8..2060243c08d 100644 --- a/apps/docs/src/app/core/component-docs/dynamic-page/dynamic-page-examples/dynamic-page-column-layout-example/dynamic-page-column-layout-example.component.html +++ b/apps/docs/src/app/core/component-docs/dynamic-page/dynamic-page-examples/dynamic-page-column-layout-example/dynamic-page-column-layout-example.component.html @@ -146,6 +146,17 @@

Mid Column

+ + +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut + labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco + laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in + voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat + cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +
+
+
diff --git a/libs/core/src/lib/tabs/tab-list.component.ts b/libs/core/src/lib/tabs/tab-list.component.ts index ba0a3b16552..fd7025918c1 100644 --- a/libs/core/src/lib/tabs/tab-list.component.ts +++ b/libs/core/src/lib/tabs/tab-list.component.ts @@ -19,7 +19,7 @@ import { } from '@angular/core'; import { fromEvent, merge, Observable, Subject, Subscription } from 'rxjs'; import { debounceTime, delay, filter, first, map, startWith, switchMap, takeUntil } from 'rxjs/operators'; -import { getElementCapacity, getElementWidth, KeyUtil } from '@fundamental-ngx/core/utils'; +import { getElementCapacity, getElementWidth, KeyUtil, resizeObservable } from '@fundamental-ngx/core/utils'; import { TabItemExpandComponent } from './tab-item-expand/tab-item-expand.component'; import { TabLinkDirective } from './tab-link/tab-link.directive'; import { TabItemDirective } from './tab-item/tab-item.directive'; @@ -148,6 +148,7 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest constructor( private _tabsService: TabsService, private _changeDetectorRef: ChangeDetectorRef, + private _elRef: ElementRef, @Optional() private _contentDensityService: ContentDensityService ) {} @@ -227,8 +228,8 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest /** @hidden */ private get _tabPanelsChange$(): Observable { return this.tabPanels.changes.pipe( - startWith(this.tabPanels), takeUntil(this._onDestroy$), + startWith(this.tabPanels), map((tabPanels) => tabPanels.toArray()) ); } @@ -264,7 +265,7 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest private _listenOnTabPanelsAndSetupStackedContent(): void { if (this.stackContent) { this._tabPanelsChange$ - .pipe(delay(0)) + .pipe(takeUntil(this._onDestroy$), delay(0)) .subscribe(() => this._tabArray.filter((tab) => !tab.panel.disabled).forEach((tab) => tab.panel._expand(true)) ); @@ -273,17 +274,23 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest /** @hidden */ private _listenOnTabPanelsAndUpdateStorageStructures(): void { - this._tabPanelsChange$.pipe(map((tabPanels) => tabPanels.map((el) => new TabInfo(el)))).subscribe((tabs) => { - this._tabArray = tabs; - this._numbOfVisibleTabs = tabs.length; - this._resetVisualOrder(); - }); + this._tabPanelsChange$ + .pipe( + takeUntil(this._onDestroy$), + map((tabPanels) => tabPanels.map((el) => new TabInfo(el))) + ) + .subscribe((tabs) => { + this._tabArray = tabs; + this._numbOfVisibleTabs = tabs.length; + this._resetVisualOrder(); + }); } /** @hidden */ private _listenOnTabPanelsExpandedChange(): void { this._tabPanelsChange$ .pipe( + takeUntil(this._onDestroy$), map((tabPanels) => tabPanels.map((el) => el._expandedStateChange.asObservable())), switchMap((tabPanels) => merge(...tabPanels)) ) @@ -294,6 +301,7 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest private _listenOnTabPanelsAndInitiallyExpandTabPanel(): void { this._tabPanelsChange$ .pipe( + takeUntil(this._onDestroy$), filter((_) => !this._tabArray.some((tab) => tab.active)), map((_) => this._tabArray.find((tab) => !tab.disabled)), filter((tab) => !!tab), @@ -321,8 +329,8 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest /** @hidden */ private _listenOnResizeAndHideItems(): void { - fromEvent(window, 'resize') - .pipe(debounceTime(100), takeUntil(this._onDestroy$)) + resizeObservable(this._elRef.nativeElement) + .pipe(takeUntil(this._onDestroy$), debounceTime(20)) .subscribe((_) => { this.refreshOverflow(); this._detectChanges(); @@ -332,6 +340,7 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest /** @hidden */ private _listenOnTabPanelsChangeAndCollapse(): void { const $tabHeadersSource = this.tabHeaders.changes.pipe( + takeUntil(this._onDestroy$), map((tabHeaders) => tabHeaders.toArray()), first() ); diff --git a/libs/core/src/lib/utils/functions/index.ts b/libs/core/src/lib/utils/functions/index.ts index e0e527d1565..d3bea5c3571 100644 --- a/libs/core/src/lib/utils/functions/index.ts +++ b/libs/core/src/lib/utils/functions/index.ts @@ -7,3 +7,4 @@ export * from './parser-file-size'; export * from './scroll'; export * from './random-color-accent'; export * from './clone-deep'; +export * from './resize-observable'; diff --git a/libs/core/src/lib/utils/functions/resize-observable.spec.ts b/libs/core/src/lib/utils/functions/resize-observable.spec.ts new file mode 100644 index 00000000000..4ec23f329a5 --- /dev/null +++ b/libs/core/src/lib/utils/functions/resize-observable.spec.ts @@ -0,0 +1,51 @@ +import { Component, ElementRef } from '@angular/core'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { resizeObservable } from './resize-observable'; + +const ELEMENT_DIMENSIONS = { width: 100, margin: 2, padding: 5 }; + +@Component({ + template: '', + host: { + '[style.display]': '"block"', + '[style.box-sizing]': '"content-box"', + '[style.width]': `elementDimensions.width + 'px'`, + '[style.margin]': `elementDimensions.margin + 'px'`, + '[style.padding]': `elementDimensions.padding + 'px'` + } +}) +class TestComponent { + elementDimensions = ELEMENT_DIMENSIONS; + constructor(public elementRef: ElementRef) {} +} + +describe('Resize Observable utils', () => { + let elementRef: ElementRef; + let fixture: ComponentFixture; + + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [TestComponent] + }).compileComponents(); + }) + ); + + beforeEach(() => { + fixture = TestBed.createComponent(TestComponent); + elementRef = fixture.componentInstance.elementRef; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(elementRef).toBeTruthy(); + }); + + it('should spy on element resize', () => { + resizeObservable(elementRef.nativeElement).subscribe((entries) => { + const entry = entries[0]; + expect(entry.contentRect.width).toEqual(200); + }); + fixture.componentInstance.elementDimensions.width = 200; + }); +}); diff --git a/libs/core/src/lib/utils/functions/resize-observable.ts b/libs/core/src/lib/utils/functions/resize-observable.ts new file mode 100644 index 00000000000..c418962bb1e --- /dev/null +++ b/libs/core/src/lib/utils/functions/resize-observable.ts @@ -0,0 +1,27 @@ +import { fromEvent, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +/** + * RxJS wrapper for ResizeObserver class. + * @param target HTML element to spy on. + * @param options @see {ResizeObserverOptions} + * @returns {Observable} with observer entries. + */ +export function resizeObservable(target: Element, options?: ResizeObserverOptions): Observable { + if ('ResizeObserver' in window) { + return new Observable((subscriber) => { + const ro = new ResizeObserver((entries) => { + subscriber.next(entries); + }); + + ro.observe(target, options); + + return function unsubscribe(): void { + ro.disconnect(); + }; + }); + } else { + // If current browser does not support resizeObserver, rely on window resize and return empty array of items. + return fromEvent(window, 'resize').pipe(map((_) => [])); + } +} From 033bde0cc6636e4b8808791f59d324f9924ccc1e Mon Sep 17 00:00:00 2001 From: Denis Severin Date: Thu, 21 Oct 2021 10:45:53 +0300 Subject: [PATCH 2/3] fix(core): takeUntil correct order --- libs/core/src/lib/tabs/tab-list.component.ts | 33 ++++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/libs/core/src/lib/tabs/tab-list.component.ts b/libs/core/src/lib/tabs/tab-list.component.ts index fd7025918c1..67fb87dcd91 100644 --- a/libs/core/src/lib/tabs/tab-list.component.ts +++ b/libs/core/src/lib/tabs/tab-list.component.ts @@ -228,9 +228,8 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest /** @hidden */ private get _tabPanelsChange$(): Observable { return this.tabPanels.changes.pipe( - takeUntil(this._onDestroy$), startWith(this.tabPanels), - map((tabPanels) => tabPanels.toArray()) + map((tabPanels) => tabPanels.toArray(), takeUntil(this._onDestroy$)) ); } @@ -265,7 +264,7 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest private _listenOnTabPanelsAndSetupStackedContent(): void { if (this.stackContent) { this._tabPanelsChange$ - .pipe(takeUntil(this._onDestroy$), delay(0)) + .pipe(delay(0), takeUntil(this._onDestroy$)) .subscribe(() => this._tabArray.filter((tab) => !tab.panel.disabled).forEach((tab) => tab.panel._expand(true)) ); @@ -276,8 +275,8 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest private _listenOnTabPanelsAndUpdateStorageStructures(): void { this._tabPanelsChange$ .pipe( - takeUntil(this._onDestroy$), - map((tabPanels) => tabPanels.map((el) => new TabInfo(el))) + map((tabPanels) => tabPanels.map((el) => new TabInfo(el))), + takeUntil(this._onDestroy$) ) .subscribe((tabs) => { this._tabArray = tabs; @@ -290,9 +289,9 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest private _listenOnTabPanelsExpandedChange(): void { this._tabPanelsChange$ .pipe( - takeUntil(this._onDestroy$), map((tabPanels) => tabPanels.map((el) => el._expandedStateChange.asObservable())), - switchMap((tabPanels) => merge(...tabPanels)) + switchMap((tabPanels) => merge(...tabPanels)), + takeUntil(this._onDestroy$) ) .subscribe((event) => this._expandTab(event.target, event.state)); } @@ -301,11 +300,11 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest private _listenOnTabPanelsAndInitiallyExpandTabPanel(): void { this._tabPanelsChange$ .pipe( - takeUntil(this._onDestroy$), filter((_) => !this._tabArray.some((tab) => tab.active)), map((_) => this._tabArray.find((tab) => !tab.disabled)), filter((tab) => !!tab), - delay(0) + delay(0), + takeUntil(this._onDestroy$) ) .subscribe((tab) => this._expandTab(tab.panel, true)); } @@ -321,8 +320,8 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest private _listenOnKeyboardTabSelect(): void { this._tabsService.tabSelected .pipe( - takeUntil(this._onDestroy$), - map((index) => this._visualOrder.visible[index].panel) + map((index) => this._visualOrder.visible[index].panel), + takeUntil(this._onDestroy$) ) .subscribe((tabPanel) => this._expandTab(tabPanel, !tabPanel.expanded)); } @@ -330,7 +329,7 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest /** @hidden */ private _listenOnResizeAndHideItems(): void { resizeObservable(this._elRef.nativeElement) - .pipe(takeUntil(this._onDestroy$), debounceTime(20)) + .pipe(debounceTime(20), takeUntil(this._onDestroy$)) .subscribe((_) => { this.refreshOverflow(); this._detectChanges(); @@ -340,15 +339,15 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest /** @hidden */ private _listenOnTabPanelsChangeAndCollapse(): void { const $tabHeadersSource = this.tabHeaders.changes.pipe( - takeUntil(this._onDestroy$), map((tabHeaders) => tabHeaders.toArray()), - first() + first(), + takeUntil(this._onDestroy$) ); this.tabPanels.changes .pipe( - takeUntil(this._onDestroy$), - switchMap(() => $tabHeadersSource) + switchMap(() => $tabHeadersSource), + takeUntil(this._onDestroy$) ) .subscribe((tabHeaders) => { this._cacheTabsWidth(tabHeaders); @@ -489,7 +488,7 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest if (!(currentScrollPosition === maximumScrollTop && distanceToScroll > maximumScrollTop)) { !this._init ? (this._disableScrollSpy = true) : (this._init = false); fromEvent(containerElement, 'scroll') - .pipe(takeUntil(this._onDestroy$), debounceTime(100), first()) + .pipe(debounceTime(100), first(), takeUntil(this._onDestroy$)) .subscribe(() => (this._disableScrollSpy = false)); scrollTop(containerElement, distanceToScroll); } From f7e997d4daadd7bc6be298b679230ad2e0152ac8 Mon Sep 17 00:00:00 2001 From: Denis Severin Date: Thu, 21 Oct 2021 12:04:27 +0300 Subject: [PATCH 3/3] fix(core): fix resize observable unit test --- .../src/lib/utils/functions/resize-observable.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/core/src/lib/utils/functions/resize-observable.spec.ts b/libs/core/src/lib/utils/functions/resize-observable.spec.ts index 4ec23f329a5..82503feabd7 100644 --- a/libs/core/src/lib/utils/functions/resize-observable.spec.ts +++ b/libs/core/src/lib/utils/functions/resize-observable.spec.ts @@ -2,16 +2,14 @@ import { Component, ElementRef } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { resizeObservable } from './resize-observable'; -const ELEMENT_DIMENSIONS = { width: 100, margin: 2, padding: 5 }; +const ELEMENT_DIMENSIONS = { width: 100 }; @Component({ template: '', host: { '[style.display]': '"block"', - '[style.box-sizing]': '"content-box"', - '[style.width]': `elementDimensions.width + 'px'`, - '[style.margin]': `elementDimensions.margin + 'px'`, - '[style.padding]': `elementDimensions.padding + 'px'` + '[style.box-sizing]': '"border-box"', + '[style.width]': `elementDimensions.width + 'px'` } }) class TestComponent { @@ -43,9 +41,11 @@ describe('Resize Observable utils', () => { it('should spy on element resize', () => { resizeObservable(elementRef.nativeElement).subscribe((entries) => { + console.log(entries[0]); const entry = entries[0]; expect(entry.contentRect.width).toEqual(200); }); fixture.componentInstance.elementDimensions.width = 200; + fixture.detectChanges(); }); });