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..67fb87dcd91 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 ) {} @@ -228,8 +229,7 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest private get _tabPanelsChange$(): Observable { return this.tabPanels.changes.pipe( startWith(this.tabPanels), - takeUntil(this._onDestroy$), - map((tabPanels) => tabPanels.toArray()) + map((tabPanels) => tabPanels.toArray(), takeUntil(this._onDestroy$)) ); } @@ -264,7 +264,7 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest private _listenOnTabPanelsAndSetupStackedContent(): void { if (this.stackContent) { this._tabPanelsChange$ - .pipe(delay(0)) + .pipe(delay(0), takeUntil(this._onDestroy$)) .subscribe(() => this._tabArray.filter((tab) => !tab.panel.disabled).forEach((tab) => tab.panel._expand(true)) ); @@ -273,11 +273,16 @@ 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( + map((tabPanels) => tabPanels.map((el) => new TabInfo(el))), + takeUntil(this._onDestroy$) + ) + .subscribe((tabs) => { + this._tabArray = tabs; + this._numbOfVisibleTabs = tabs.length; + this._resetVisualOrder(); + }); } /** @hidden */ @@ -285,7 +290,8 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest this._tabPanelsChange$ .pipe( 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)); } @@ -297,7 +303,8 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest 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)); } @@ -313,16 +320,16 @@ 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)); } /** @hidden */ private _listenOnResizeAndHideItems(): void { - fromEvent(window, 'resize') - .pipe(debounceTime(100), takeUntil(this._onDestroy$)) + resizeObservable(this._elRef.nativeElement) + .pipe(debounceTime(20), takeUntil(this._onDestroy$)) .subscribe((_) => { this.refreshOverflow(); this._detectChanges(); @@ -333,13 +340,14 @@ export class TabListComponent implements AfterContentInit, AfterViewInit, OnDest private _listenOnTabPanelsChangeAndCollapse(): void { const $tabHeadersSource = this.tabHeaders.changes.pipe( 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); @@ -480,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); } 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..82503feabd7 --- /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 }; + +@Component({ + template: '', + host: { + '[style.display]': '"block"', + '[style.box-sizing]': '"border-box"', + '[style.width]': `elementDimensions.width + '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) => { + console.log(entries[0]); + const entry = entries[0]; + expect(entry.contentRect.width).toEqual(200); + }); + fixture.componentInstance.elementDimensions.width = 200; + fixture.detectChanges(); + }); +}); 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((_) => [])); + } +}