From 05027ba88b95b68060124701b6db967f94adc9b1 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Thu, 21 Jun 2018 09:48:47 +0300 Subject: [PATCH 001/129] feat(list): add list components --- .../components/list/list-item.component.scss | 3 ++ .../theme/components/list/list.component.scss | 3 ++ .../theme/components/list/list.component.ts | 33 +++++++++++++++++++ .../theme/components/list/list.module.ts | 14 ++++++++ src/framework/theme/index.ts | 1 + 5 files changed, 54 insertions(+) create mode 100644 src/framework/theme/components/list/list-item.component.scss create mode 100644 src/framework/theme/components/list/list.component.scss create mode 100644 src/framework/theme/components/list/list.component.ts create mode 100644 src/framework/theme/components/list/list.module.ts diff --git a/src/framework/theme/components/list/list-item.component.scss b/src/framework/theme/components/list/list-item.component.scss new file mode 100644 index 0000000000..5d4e87f30f --- /dev/null +++ b/src/framework/theme/components/list/list-item.component.scss @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/src/framework/theme/components/list/list.component.scss b/src/framework/theme/components/list/list.component.scss new file mode 100644 index 0000000000..5d4e87f30f --- /dev/null +++ b/src/framework/theme/components/list/list.component.scss @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/src/framework/theme/components/list/list.component.ts b/src/framework/theme/components/list/list.component.ts new file mode 100644 index 0000000000..4f8f94c544 --- /dev/null +++ b/src/framework/theme/components/list/list.component.ts @@ -0,0 +1,33 @@ +import { Component, Input, HostBinding } from '@angular/core'; + +@Component({ + selector: 'nb-list', + template: ``, + styleUrls: [ './list.component.scss' ], +}) +export class NbListComponent { + @Input() + @HostBinding('attr.role') + role = 'list'; +} + +@Component({ + selector: 'nb-list-item', + template: ``, + styleUrls: [ './list-item.component.scss' ], +}) +export class NbListItemComponent { + @Input() + @HostBinding('attr.role') + role = 'listitem'; +} + +@Component({ + selector: 'nb-list-items-group', + template: ``, +}) +export class NbListItemsGroupComponent { + @Input() + @HostBinding('attr.role') + role = 'group'; +} diff --git a/src/framework/theme/components/list/list.module.ts b/src/framework/theme/components/list/list.module.ts new file mode 100644 index 0000000000..2220c52dfc --- /dev/null +++ b/src/framework/theme/components/list/list.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { NbListComponent, NbListItemComponent, NbListItemsGroupComponent } from './list.component'; + +const components = [ + NbListComponent, + NbListItemComponent, + NbListItemsGroupComponent, +]; + +@NgModule({ + declarations: components, + exports: components, +}) +export class NbListModule {} diff --git a/src/framework/theme/index.ts b/src/framework/theme/index.ts index a630261dd1..b20c597c59 100644 --- a/src/framework/theme/index.ts +++ b/src/framework/theme/index.ts @@ -54,3 +54,4 @@ export * from './components/accordion/accordion-item-header.component'; export * from './components/accordion/accordion.module'; export * from './components/button/button.component'; export * from './components/button/button.module'; +export * from './components/list/list.module'; From fe70051a6eb97f8ee49ce3e2d3d01dba61b68c16 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Thu, 21 Jun 2018 09:59:03 +0300 Subject: [PATCH 002/129] feat(infinite-list): add infinite-list component --- .../components/layout/layout.component.ts | 9 +++ .../infinite-list/infinite-list.component.ts | 45 ++++++++++++++ .../infinite-list/infinite-list.module.ts | 11 ++++ .../scroll-threshold.directive.ts | 62 +++++++++++++++++++ src/framework/theme/index.ts | 1 + 5 files changed, 128 insertions(+) create mode 100644 src/framework/theme/components/list/infinite-list/infinite-list.component.ts create mode 100644 src/framework/theme/components/list/infinite-list/infinite-list.module.ts create mode 100644 src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts diff --git a/src/framework/theme/components/layout/layout.component.ts b/src/framework/theme/components/layout/layout.component.ts index 37dc478898..f512fffaba 100644 --- a/src/framework/theme/components/layout/layout.component.ts +++ b/src/framework/theme/components/layout/layout.component.ts @@ -420,6 +420,15 @@ export class NbLayoutComponent implements AfterViewInit, OnInit, OnDestroy { this.themeService.changeWindowWidth(event.target.innerWidth); } + @HostListener('scroll', ['$event']) + onELementScroll($event) { + const event = new CustomEvent( + 'nbscroll', + { detail: { originalEvent: $event } }, + ); + this.document.dispatchEvent(event); + } + private initScrollTop() { this.router.events .pipe( diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts new file mode 100644 index 0000000000..e0e82279c4 --- /dev/null +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -0,0 +1,45 @@ +import { Component, OnInit, Input, Output, EventEmitter, Inject, ElementRef } from '@angular/core'; +import { NB_DOCUMENT } from '../../../theme.options'; + +@Component({ + selector: 'nb-infinite-list', + template: ` + + + + `, +}) +export class NbInfiniteListComponent implements OnInit { + + private screensCountBeforeTrigger = 3; + + @Input() role = 'feed'; + + @Input() listenWindowScroll = false; + + @Input() loadMoreThreshold; + + @Output() loadNext = new EventEmitter(); + + constructor( + @Inject(NB_DOCUMENT) private document, + private elemntRef: ElementRef, + ) {} + + emitLoadNext() { + this.loadNext.emit(); + } + + ngOnInit() { + if (!this.loadMoreThreshold) { + const element = this.listenWindowScroll + ? this.document.body + : this.elemntRef; + this.loadMoreThreshold = element.clientHeight * this.screensCountBeforeTrigger; + } + } +} diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.module.ts b/src/framework/theme/components/list/infinite-list/infinite-list.module.ts new file mode 100644 index 0000000000..8158067755 --- /dev/null +++ b/src/framework/theme/components/list/infinite-list/infinite-list.module.ts @@ -0,0 +1,11 @@ +import { NgModule } from '@angular/core'; +import { NbListModule } from '../list.module'; +import { NbInfiniteListComponent } from './infinite-list.component'; +import { NbScrollThresholdDirective } from './scroll-threshold.directive'; + +@NgModule({ + imports: [ NbListModule ], + declarations: [ NbInfiniteListComponent, NbScrollThresholdDirective ], + exports: [ NbInfiniteListComponent, NbScrollThresholdDirective ], +}) +export class NbInifiniteListModule {} diff --git a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts new file mode 100644 index 0000000000..6a29fd490d --- /dev/null +++ b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts @@ -0,0 +1,62 @@ +import { Directive, Input, HostListener, Inject, ElementRef, EventEmitter, Output } from '@angular/core'; +import { NB_DOCUMENT } from '../../../theme.options'; +import { getElementHeight } from '../../helpers'; + +@Directive({ + selector: '[nbScrollThreshold]', +}) +export class NbScrollThresholdDirective { + + @Input('nbScrollThreshold') + threshold: number; + + @Input() + listenWindowScroll = false; + + @Output() + thresholdReached = new EventEmitter(); + + @HostListener('window:resize') + windowResize() { + this.checkPosition(this.getDocumentElement()); + } + + @HostListener('window:scroll') + windowScroll() { + if (this.listenWindowScroll) { + this.checkPosition(this.getDocumentElement()); + } + } + + @HostListener('document:nbscroll', ['$event']) + onLayoutScroll($event) { + if (this.listenWindowScroll) { + this.checkPosition($event.target); + } + } + + @HostListener('scroll') + elementScroll() { + if (!this.listenWindowScroll) { + this.checkPosition(this.elementRef.nativeElement); + } + } + + constructor( + @Inject(NB_DOCUMENT) private document, + private elementRef: ElementRef, + ) {} + + checkPosition(element) { + const height = getElementHeight(element); + + if (height - element.scrollTop <= this.threshold) { + this.thresholdReached.emit(); + } + } + + private getDocumentElement() { + const { body, documentElement } = this.document; + return documentElement.scrollTop ? documentElement : body; + } +} diff --git a/src/framework/theme/index.ts b/src/framework/theme/index.ts index b20c597c59..ce4e816165 100644 --- a/src/framework/theme/index.ts +++ b/src/framework/theme/index.ts @@ -55,3 +55,4 @@ export * from './components/accordion/accordion.module'; export * from './components/button/button.component'; export * from './components/button/button.module'; export * from './components/list/list.module'; +export * from './components/list/infinite-list/infinite-list.module'; From 2b3291c0e13f3be47c43e34b8e3b3e5454c4a644 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Thu, 21 Jun 2018 11:04:42 +0300 Subject: [PATCH 003/129] feat(infinite-scroll): remove default threshold --- .../infinite-list/infinite-list.component.ts | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts index e0e82279c4..6cde9b78df 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -1,5 +1,4 @@ -import { Component, OnInit, Input, Output, EventEmitter, Inject, ElementRef } from '@angular/core'; -import { NB_DOCUMENT } from '../../../theme.options'; +import { Component, Input, Output, EventEmitter, HostBinding } from '@angular/core'; @Component({ selector: 'nb-infinite-list', @@ -13,7 +12,7 @@ import { NB_DOCUMENT } from '../../../theme.options'; `, }) -export class NbInfiniteListComponent implements OnInit { +export class NbInfiniteListComponent { private screensCountBeforeTrigger = 3; @@ -25,21 +24,7 @@ export class NbInfiniteListComponent implements OnInit { @Output() loadNext = new EventEmitter(); - constructor( - @Inject(NB_DOCUMENT) private document, - private elemntRef: ElementRef, - ) {} - emitLoadNext() { this.loadNext.emit(); } - - ngOnInit() { - if (!this.loadMoreThreshold) { - const element = this.listenWindowScroll - ? this.document.body - : this.elemntRef; - this.loadMoreThreshold = element.clientHeight * this.screensCountBeforeTrigger; - } - } } From 7ed8963e8e0d3e6b384638a0c2d3156b372fcabd Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Thu, 21 Jun 2018 11:07:18 +0300 Subject: [PATCH 004/129] fix(infinite-scroll): add missing styles --- .../list/infinite-list/infinite-list.component.ts | 7 ++++--- .../list/infinite-list/infintie-list.component.scss | 13 +++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 src/framework/theme/components/list/infinite-list/infintie-list.component.scss diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts index 6cde9b78df..3bd50972cc 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -11,14 +11,15 @@ import { Component, Input, Output, EventEmitter, HostBinding } from '@angular/co `, + styleUrls: [ './infintie-list.component.scss' ], }) export class NbInfiniteListComponent { - private screensCountBeforeTrigger = 3; - @Input() role = 'feed'; - @Input() listenWindowScroll = false; + @Input() + @HostBinding('class.window-scroll') + listenWindowScroll = false; @Input() loadMoreThreshold; diff --git a/src/framework/theme/components/list/infinite-list/infintie-list.component.scss b/src/framework/theme/components/list/infinite-list/infintie-list.component.scss new file mode 100644 index 0000000000..a803247865 --- /dev/null +++ b/src/framework/theme/components/list/infinite-list/infintie-list.component.scss @@ -0,0 +1,13 @@ +:host { + display: block; +} + +:host(:not(.window-scroll)) { + height: 100%; + overflow: hidden; + + nb-list { + height: 100%; + overflow-y: scroll; + } +} From 0704c79c0506654e670a196a486eb7233701778b Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Thu, 21 Jun 2018 11:09:50 +0300 Subject: [PATCH 005/129] fix(scroll-threshold): check threshold using full width of element --- .../list/infinite-list/scroll-threshold.directive.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts index 6a29fd490d..bdddc4b795 100644 --- a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts +++ b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts @@ -48,9 +48,7 @@ export class NbScrollThresholdDirective { ) {} checkPosition(element) { - const height = getElementHeight(element); - - if (height - element.scrollTop <= this.threshold) { + if (element.scrollHeight - element.scrollTop <= this.threshold) { this.thresholdReached.emit(); } } From a2a9d9f8fb02f50e0d2c7e0f92c7b6b103ced63d Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Thu, 21 Jun 2018 13:32:43 +0300 Subject: [PATCH 006/129] style(scroll-threshold): remove unused imports --- .../components/list/infinite-list/scroll-threshold.directive.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts index bdddc4b795..d7c2001dcc 100644 --- a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts +++ b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts @@ -1,6 +1,5 @@ import { Directive, Input, HostListener, Inject, ElementRef, EventEmitter, Output } from '@angular/core'; import { NB_DOCUMENT } from '../../../theme.options'; -import { getElementHeight } from '../../helpers'; @Directive({ selector: '[nbScrollThreshold]', From 7c199558c3b3cd9bdc547c5bae1447c79b16c025 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Sun, 24 Jun 2018 18:08:05 +0300 Subject: [PATCH 007/129] style(scroll-directive): consistent event handlers names --- .../components/list/infinite-list/scroll-threshold.directive.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts index d7c2001dcc..ec0f959a9c 100644 --- a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts +++ b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts @@ -28,7 +28,7 @@ export class NbScrollThresholdDirective { } @HostListener('document:nbscroll', ['$event']) - onLayoutScroll($event) { + layoutScroll($event) { if (this.listenWindowScroll) { this.checkPosition($event.target); } From 0f17002ba1036b959cc787abbe2e027398a3449b Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Mon, 25 Jun 2018 10:09:09 +0300 Subject: [PATCH 008/129] feat(scroll-directive): add unit tests --- .../scroll-threshold.directive.spec.ts | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts diff --git a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts new file mode 100644 index 0000000000..89ee33695a --- /dev/null +++ b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts @@ -0,0 +1,176 @@ +import { Component, ViewChild, ElementRef } from '@angular/core'; +import { TestBed, ComponentFixture, fakeAsync, tick, async } from '@angular/core/testing'; +import { NbScrollThresholdDirective } from './scroll-threshold.directive'; +import { NbThemeModule } from '../../../theme.module'; +import { NbInifiniteListModule } from './infinite-list.module'; + +const CONTENT_HEIGHT = 10000; +const THRESHOLD = 2000; + +let fixture: ComponentFixture; +let componentInstance: ScrollTestComponent; +let scrollingElement: ElementRef; +let scrollDirective: NbScrollThresholdDirective; + +describe('Directive: NbScrollDirective', () => { + + beforeEach(() => { + fixture = TestBed.configureTestingModule({ + imports: [ NbThemeModule.forRoot({ name: 'default' }), NbInifiniteListModule ], + declarations: [ ScrollTestComponent ], + }) + .createComponent(ScrollTestComponent); + + componentInstance = fixture.componentInstance; + scrollingElement = componentInstance.scrollingElement; + scrollDirective = componentInstance.scrollDirective; + + fixture.detectChanges(); + }); + + afterEach(fakeAsync(() => { + fixture.destroy(); + tick(); + fixture.nativeElement.remove(); + })); + + it('should listen to window scroll', () => { + const windowScrollHandlerSpy = spyOn(scrollDirective, 'windowScroll'); + window.dispatchEvent(new Event('scroll')); + expect(windowScrollHandlerSpy).toHaveBeenCalledTimes(1); + }); + + it('should listen to window resize', () => { + const windowResizeHandlerSpy = spyOn(scrollDirective, 'windowResize'); + window.dispatchEvent(new Event('resize')); + expect(windowResizeHandlerSpy).toHaveBeenCalledTimes(1); + }); + + it('should listen to layout scroll', () => { + const layoutScrollHandlerSpy = spyOn(scrollDirective, 'layoutScroll'); + document.dispatchEvent(new Event('nbscroll')); + expect(layoutScrollHandlerSpy).toHaveBeenCalledTimes(1); + }); + + it('should listen to element scroll', () => { + const elementScrollHandlerSpy = spyOn(scrollDirective, 'elementScroll'); + scrollingElement.nativeElement.dispatchEvent(new Event('scroll')); + expect(elementScrollHandlerSpy).toHaveBeenCalledTimes(1); + }); + + it('should ignore window and layout scroll events when listen to element scroll', () => { + const checkPositionSpy = spyOn(scrollDirective, 'checkPosition'); + + window.dispatchEvent(new Event('scroll')); + expect(checkPositionSpy).toHaveBeenCalledTimes(0); + + document.dispatchEvent(new Event('nbscroll')); + expect(checkPositionSpy).toHaveBeenCalledTimes(0); + + scrollingElement.nativeElement.dispatchEvent(new Event('scroll')); + expect(checkPositionSpy).toHaveBeenCalledTimes(1); + }); + + it('should listen window or layout scroll when not listening to element scroll', () => { + const checkPositionSpy = spyOn(scrollDirective, 'checkPosition'); + + componentInstance.listenWindowScroll = true; + fixture.detectChanges(); + + window.dispatchEvent(new Event('scroll')); + expect(checkPositionSpy).toHaveBeenCalledTimes(1); + + document.dispatchEvent(new Event('nbscroll')); + expect(checkPositionSpy).toHaveBeenCalledTimes(2); + + scrollingElement.nativeElement.dispatchEvent(new Event('scroll')); + expect(checkPositionSpy).toHaveBeenCalledTimes(2); + }); + + it('should listen window resize regardless of window or element scroll mode', () => { + const resizeHandlerSpy = spyOn(scrollDirective, 'windowResize'); + + window.dispatchEvent(new Event('resize')); + expect(resizeHandlerSpy).toHaveBeenCalledTimes(1); + + scrollDirective.listenWindowScroll = true; + fixture.detectChanges(); + + window.dispatchEvent(new Event('resize')); + expect(resizeHandlerSpy).toHaveBeenCalledTimes(2); + }); + + it('should trigger event only when treshold reached (element scroll)', () => { + const scrollingNativeElement = scrollingElement.nativeElement; + const tresholdReachedSpy = spyOn(componentInstance, 'thresholdReached'); + + const reporterHeight = 1000; + const positionUnderThreshold = CONTENT_HEIGHT - THRESHOLD - reporterHeight; + scrollingNativeElement.scrollTop = positionUnderThreshold; + scrollingNativeElement.dispatchEvent(new Event('scroll')); + expect(tresholdReachedSpy).toHaveBeenCalledTimes(0); + + const positionBelowThreshold = CONTENT_HEIGHT - (THRESHOLD / 2); + scrollingNativeElement.scrollTop = positionBelowThreshold; + scrollingNativeElement.dispatchEvent(new Event('scroll')); + expect(tresholdReachedSpy).toHaveBeenCalledTimes(1); + }); + + it('should trigger event only when treshold reached (window scroll)', () => { + componentInstance.listenWindowScroll = true; + fixture.detectChanges(); + + const tresholdReachedSpy = spyOn(componentInstance, 'thresholdReached'); + + const reporterHeight = 1000; + const positionUnderThreshold = CONTENT_HEIGHT - THRESHOLD - reporterHeight; + document.documentElement.scrollTop = positionUnderThreshold; + window.dispatchEvent(new Event('scroll')); + expect(tresholdReachedSpy).toHaveBeenCalledTimes(0); + + const positionBelowThreshold = CONTENT_HEIGHT - (THRESHOLD / 2); + document.documentElement.scrollTop = positionBelowThreshold; + window.dispatchEvent(new Event('scroll')); + expect(tresholdReachedSpy).toHaveBeenCalledTimes(1); + }); + + // TODO + // it('should trigger event only when treshold reached (layout scroll)', () => {}); +}); + +@Component({ + template: ` +
+
+
+ `, + styles: [` + .scroller { + background: lightgray; + padding: 10px; + } + .element-scroll { + height: 500px; + overflow-y: auto; + } + .inner { + background: lightgoldenrodyellow; + height: ${CONTENT_HEIGHT}px; + } + `], +}) +class ScrollTestComponent { + @ViewChild(NbScrollThresholdDirective) scrollDirective: NbScrollThresholdDirective; + @ViewChild('scrollingElement') scrollingElement: ElementRef; + + listenWindowScroll = false; + threshold = THRESHOLD; + + thresholdReached() {} +} From 4aba9654fbb609660eff5870b0a497735ce4ca97 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Mon, 25 Jun 2018 12:20:12 +0300 Subject: [PATCH 009/129] feat(nb-list): add docs --- docs/structure.ts | 6 ++++ .../theme/components/list/list.component.ts | 29 +++++++++++++++++++ .../list/list-showcase.component.ts | 23 +++++++++++++++ src/playground/playground-routing.module.ts | 10 +++++++ src/playground/playground.module.ts | 4 +++ 5 files changed, 72 insertions(+) create mode 100644 src/playground/list/list-showcase.component.ts diff --git a/docs/structure.ts b/docs/structure.ts index 9fde19a8e6..2e274f7921 100644 --- a/docs/structure.ts +++ b/docs/structure.ts @@ -327,6 +327,12 @@ export const structure = [ 'NbAccordionItemBodyComponent', ], }, + { + type: 'tabs', + name: 'List', + icon: '', + source: [ 'NbListComponent', 'NbListItemComponent', 'NbListItemsGroupComponent' ], + }, ], }, { diff --git a/src/framework/theme/components/list/list.component.ts b/src/framework/theme/components/list/list.component.ts index 4f8f94c544..999475e4bf 100644 --- a/src/framework/theme/components/list/list.component.ts +++ b/src/framework/theme/components/list/list.component.ts @@ -1,32 +1,61 @@ import { Component, Input, HostBinding } from '@angular/core'; +/** + * List component is a container component that wraps `nb-list-item` or `nb-list-items-group` components. + * + * Basic example: + * @stacked-example(Showcase, list/list-showcase.component) + */ @Component({ selector: 'nb-list', template: ``, styleUrls: [ './list.component.scss' ], }) export class NbListComponent { + /** + * Role attribute value + * + * @type {string} + */ @Input() @HostBinding('attr.role') role = 'list'; } +/** + * List item component is a grouping component that accepts arbitrary content. + * It should be direct child of `nb-list` or `nb-list-items-group` componets. + */ @Component({ selector: 'nb-list-item', template: ``, styleUrls: [ './list-item.component.scss' ], }) export class NbListItemComponent { + /** + * Role attribute value + * + * @type {string} + */ @Input() @HostBinding('attr.role') role = 'listitem'; } +/** + * List item group is a grouping component for `nb-list-item`. + * It should be direct child of `nb-list` component. + */ @Component({ selector: 'nb-list-items-group', template: ``, }) export class NbListItemsGroupComponent { + /** + * Role attribute value + * + * @type {string} + */ @Input() @HostBinding('attr.role') role = 'group'; diff --git a/src/playground/list/list-showcase.component.ts b/src/playground/list/list-showcase.component.ts new file mode 100644 index 0000000000..1aed354e43 --- /dev/null +++ b/src/playground/list/list-showcase.component.ts @@ -0,0 +1,23 @@ +import { Component } from '@angular/core'; + +@Component({ + template: ` + + + item {{ item.index + 1 }} + + + `, + styleUrls: ['./list-showcase.component.scss'], +}) +export class NbListShowcaseComponent { + + private pageSize = 10; + items = []; + + constructor() { + for (let i = 0; i < this.pageSize; i++) { + this.items.push({ index: i }); + } + } +} diff --git a/src/playground/playground-routing.module.ts b/src/playground/playground-routing.module.ts index f4396ae8ae..2a135de09a 100644 --- a/src/playground/playground-routing.module.ts +++ b/src/playground/playground-routing.module.ts @@ -132,6 +132,7 @@ import { NbButtonHeroComponent } from './button/button-hero.component'; import { NbButtonOutlineComponent } from './button/button-outline.component'; import { NbButtonSizesComponent } from './button/button-sizes.component'; import { NbButtonTypesComponent } from './button/button-types.component'; +import { NbListShowcaseComponent } from './list/list-showcase.component'; export const routes: Routes = [ { @@ -558,6 +559,15 @@ export const routes: Routes = [ }, ], }, + { + path: 'list', + children: [ + { + path: 'list-showcase.component', + component: NbListShowcaseComponent, + }, + ], + }, ], }, { diff --git a/src/playground/playground.module.ts b/src/playground/playground.module.ts index ff042ca4c7..3c8ffc7d67 100644 --- a/src/playground/playground.module.ts +++ b/src/playground/playground.module.ts @@ -29,6 +29,7 @@ import { NbSpinnerModule, NbStepperModule, NbAccordionModule, + NbListModule, } from '@nebular/theme'; import { NbPlaygroundRoutingModule } from './playground-routing.module'; @@ -161,6 +162,7 @@ import { NbButtonHeroComponent } from './button/button-hero.component'; import { NbButtonOutlineComponent } from './button/button-outline.component'; import { NbButtonSizesComponent } from './button/button-sizes.component'; import { NbButtonTypesComponent } from './button/button-types.component'; +import { NbListShowcaseComponent } from './list/list-showcase.component'; export const NB_MODULES = [ NbCardModule, @@ -189,6 +191,7 @@ export const NB_MODULES = [ NbSpinnerModule, NbAccordionModule, NbButtonModule, + NbListModule, ]; export const NB_EXAMPLE_COMPONENTS = [ @@ -312,6 +315,7 @@ export const NB_EXAMPLE_COMPONENTS = [ NbButtonOutlineComponent, NbButtonSizesComponent, NbButtonTypesComponent, + NbListShowcaseComponent, ]; From 8bce7dadf2f367b85e4c5d6223c32db2e3293ad7 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Mon, 25 Jun 2018 16:51:29 +0300 Subject: [PATCH 010/129] feat(infinite-list): add docs and showcase --- docs/structure.ts | 6 ++ .../infinite-list/infinite-list.component.ts | 5 ++ .../scroll-threshold.directive.ts | 18 ++++++ .../infinite-list-showcase.component.scss | 3 + .../infinite-list-showcase.component.ts | 60 +++++++++++++++++++ ...infinite-list-window-showcase.component.ts | 55 +++++++++++++++++ .../list/list-showcase.component.scss | 14 +++++ src/playground/playground-routing.module.ts | 10 ++++ src/playground/playground.module.ts | 6 ++ 9 files changed, 177 insertions(+) create mode 100644 src/playground/list/infinite-list/infinite-list-showcase.component.scss create mode 100644 src/playground/list/infinite-list/infinite-list-showcase.component.ts create mode 100644 src/playground/list/infinite-list/infinite-list-window-showcase.component.ts create mode 100644 src/playground/list/list-showcase.component.scss diff --git a/docs/structure.ts b/docs/structure.ts index 2e274f7921..633e192ea3 100644 --- a/docs/structure.ts +++ b/docs/structure.ts @@ -333,6 +333,12 @@ export const structure = [ icon: '', source: [ 'NbListComponent', 'NbListItemComponent', 'NbListItemsGroupComponent' ], }, + { + type: 'tabs', + name: 'Infinite List', + icon: '', + source: [ 'NbInfiniteListComponent', 'NbScrollThresholdDirective' ], + }, ], }, { diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts index 3bd50972cc..970bd96647 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -1,5 +1,10 @@ import { Component, Input, Output, EventEmitter, HostBinding } from '@angular/core'; +/** + * Infinite list component. + * + * @stacked-example(Basic example:, list/infinite-list/infinite-list-showcase.component) + */ @Component({ selector: 'nb-infinite-list', template: ` diff --git a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts index ec0f959a9c..db6f85a6be 100644 --- a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts +++ b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts @@ -1,17 +1,35 @@ import { Directive, Input, HostListener, Inject, ElementRef, EventEmitter, Output } from '@angular/core'; import { NB_DOCUMENT } from '../../../theme.options'; +/** + * Scroll Threshold Directive + * + * Directive will notify when element scrolled down to given a threshold. + * By default listen to scroll on element to which it applied. + */ @Directive({ selector: '[nbScrollThreshold]', }) export class NbScrollThresholdDirective { + /** + * Threshold value. + * Please ensure that its height is greater than element height, otherwise threshold will never be reached. + */ @Input('nbScrollThreshold') threshold: number; + /** + * Listen window scroll. + * Also enables listening to layout scroll, for those who has `nb-layout` set to `withScroll` mode, + * which remove scroll on body. + */ @Input() listenWindowScroll = false; + /** + * Emits when threshold reached. + */ @Output() thresholdReached = new EventEmitter(); diff --git a/src/playground/list/infinite-list/infinite-list-showcase.component.scss b/src/playground/list/infinite-list/infinite-list-showcase.component.scss new file mode 100644 index 0000000000..3a1d81ec7e --- /dev/null +++ b/src/playground/list/infinite-list/infinite-list-showcase.component.scss @@ -0,0 +1,3 @@ +.infinite-list-container { + height: 80vh; +} diff --git a/src/playground/list/infinite-list/infinite-list-showcase.component.ts b/src/playground/list/infinite-list/infinite-list-showcase.component.ts new file mode 100644 index 0000000000..ed07d732b4 --- /dev/null +++ b/src/playground/list/infinite-list/infinite-list-showcase.component.ts @@ -0,0 +1,60 @@ +import { Component } from '@angular/core'; + +@Component({ + template: ` +
+ + + {{ item.isPlaceholder ? 'placeholder' : 'item' }} {{ item.humanNumber }} + + +
+ `, + styleUrls: [ + '../list-showcase.component.scss', + './infinite-list-showcase.component.scss', + ], +}) +export class NbInfiniteListShowcaseComponent { + + private pageSize = 20; + private maxLength = 100000; + items = []; + + constructor() { + for (let i = 0; i < this.pageSize; i++) { + this.items.push({ index: i, humanNumber: i + 1 }); + } + } + + getIndex(_, { index }) { + return index; + } + + loadNext() { + if (this.items.length >= this.maxLength) { + return; + } + + const placeholders = []; + const nextItem = this.items[this.items.length - 1].index + 1; + const maxItem = nextItem + this.pageSize; + for (let i = nextItem; i < maxItem; i++) { + placeholders.push({ index: i, humanNumber: i + 1, isPlaceholder: true }); + } + + this.items = this.items.concat(placeholders); + + setTimeout(() => { + for (let i = nextItem; i < maxItem; i++) { + this.items[i].isPlaceholder = false; + } + + // this.chageDetection.detectChanges(); + }, 1000); + } +} diff --git a/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts b/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts new file mode 100644 index 0000000000..ef580c8a27 --- /dev/null +++ b/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts @@ -0,0 +1,55 @@ +import { Component } from '@angular/core'; + +@Component({ + template: ` + + + {{ item.isPlaceholder ? 'placeholder' : 'item' }} {{ item.humanNumber }} + + + `, + styleUrls: [ '../list-showcase.component.scss' ], +}) +export class NbInfiniteListWindowShowcaseComponent { + + private pageSize = 20; + private maxLength = 100000; + items = []; + + constructor() { + for (let i = 0; i < this.pageSize; i++) { + this.items.push({ index: i, humanNumber: i + 1 }); + } + } + + getIndex(_, { index }) { + return index; + } + + loadNext() { + if (this.items.length >= this.maxLength) { + return; + } + + const placeholders = []; + const nextItem = this.items[this.items.length - 1].index + 1; + const maxItem = nextItem + this.pageSize; + for (let i = nextItem; i < maxItem; i++) { + placeholders.push({ index: i, humanNumber: i + 1, isPlaceholder: true }); + } + + this.items = this.items.concat(placeholders); + + setTimeout(() => { + for (let i = nextItem; i < maxItem; i++) { + this.items[i].isPlaceholder = false; + } + // this.chageDetection.detectChanges(); + }, 1000); + } +} diff --git a/src/playground/list/list-showcase.component.scss b/src/playground/list/list-showcase.component.scss new file mode 100644 index 0000000000..f77a43b6dc --- /dev/null +++ b/src/playground/list/list-showcase.component.scss @@ -0,0 +1,14 @@ +:host { + nb-list-item { + background-color: lightblue; + padding: 1rem; + height: 3rem; + width: 50%; + min-width: 6rem; + margin: 1rem auto; + + &.placeholder { + background-color: lightgray; + } + } +} diff --git a/src/playground/playground-routing.module.ts b/src/playground/playground-routing.module.ts index 2a135de09a..4d57f2f558 100644 --- a/src/playground/playground-routing.module.ts +++ b/src/playground/playground-routing.module.ts @@ -133,6 +133,8 @@ import { NbButtonOutlineComponent } from './button/button-outline.component'; import { NbButtonSizesComponent } from './button/button-sizes.component'; import { NbButtonTypesComponent } from './button/button-types.component'; import { NbListShowcaseComponent } from './list/list-showcase.component'; +import { NbInfiniteListShowcaseComponent } from './list/infinite-list/infinite-list-showcase.component'; +import { NbInfiniteListWindowShowcaseComponent } from './list/infinite-list/infinite-list-window-showcase.component'; export const routes: Routes = [ { @@ -566,6 +568,14 @@ export const routes: Routes = [ path: 'list-showcase.component', component: NbListShowcaseComponent, }, + { + path: 'infinite-list/infinite-list-showcase.component', + component: NbInfiniteListShowcaseComponent, + }, + { + path: 'infinite-list/infinite-list-window-showcase.component', + component: NbInfiniteListWindowShowcaseComponent, + }, ], }, ], diff --git a/src/playground/playground.module.ts b/src/playground/playground.module.ts index 3c8ffc7d67..28c2e14b9a 100644 --- a/src/playground/playground.module.ts +++ b/src/playground/playground.module.ts @@ -30,6 +30,7 @@ import { NbStepperModule, NbAccordionModule, NbListModule, + NbInifiniteListModule, } from '@nebular/theme'; import { NbPlaygroundRoutingModule } from './playground-routing.module'; @@ -163,6 +164,8 @@ import { NbButtonOutlineComponent } from './button/button-outline.component'; import { NbButtonSizesComponent } from './button/button-sizes.component'; import { NbButtonTypesComponent } from './button/button-types.component'; import { NbListShowcaseComponent } from './list/list-showcase.component'; +import { NbInfiniteListShowcaseComponent } from './list/infinite-list/infinite-list-showcase.component'; +import { NbInfiniteListWindowShowcaseComponent } from './list/infinite-list/infinite-list-window-showcase.component'; export const NB_MODULES = [ NbCardModule, @@ -192,6 +195,7 @@ export const NB_MODULES = [ NbAccordionModule, NbButtonModule, NbListModule, + NbInifiniteListModule, ]; export const NB_EXAMPLE_COMPONENTS = [ @@ -316,6 +320,8 @@ export const NB_EXAMPLE_COMPONENTS = [ NbButtonSizesComponent, NbButtonTypesComponent, NbListShowcaseComponent, + NbInfiniteListShowcaseComponent, + NbInfiniteListWindowShowcaseComponent, ]; From 32ca889123bcc0dd0a84a88feecdede88dd3c420 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Wed, 27 Jun 2018 09:54:54 +0300 Subject: [PATCH 011/129] fix(scroll-directive.spec): move component declaration to top --- .../scroll-threshold.directive.spec.ts | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts index 89ee33695a..21f82f5e6d 100644 --- a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts +++ b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts @@ -1,5 +1,5 @@ import { Component, ViewChild, ElementRef } from '@angular/core'; -import { TestBed, ComponentFixture, fakeAsync, tick, async } from '@angular/core/testing'; +import { TestBed, ComponentFixture, fakeAsync, tick } from '@angular/core/testing'; import { NbScrollThresholdDirective } from './scroll-threshold.directive'; import { NbThemeModule } from '../../../theme.module'; import { NbInifiniteListModule } from './infinite-list.module'; @@ -12,6 +12,43 @@ let componentInstance: ScrollTestComponent; let scrollingElement: ElementRef; let scrollDirective: NbScrollThresholdDirective; +@Component({ + template: ` +
+
+
+ `, + styles: [` + .scroller { + background: lightgray; + padding: 10px; + } + .element-scroll { + height: 500px; + overflow-y: auto; + } + .inner { + background: lightgoldenrodyellow; + height: ${CONTENT_HEIGHT}px; + } + `], +}) +class ScrollTestComponent { + @ViewChild(NbScrollThresholdDirective) scrollDirective: NbScrollThresholdDirective; + @ViewChild('scrollingElement') scrollingElement: ElementRef; + + listenWindowScroll = false; + threshold = THRESHOLD; + + thresholdReached() { } +} + describe('Directive: NbScrollDirective', () => { beforeEach(() => { @@ -137,40 +174,3 @@ describe('Directive: NbScrollDirective', () => { // TODO // it('should trigger event only when treshold reached (layout scroll)', () => {}); }); - -@Component({ - template: ` -
-
-
- `, - styles: [` - .scroller { - background: lightgray; - padding: 10px; - } - .element-scroll { - height: 500px; - overflow-y: auto; - } - .inner { - background: lightgoldenrodyellow; - height: ${CONTENT_HEIGHT}px; - } - `], -}) -class ScrollTestComponent { - @ViewChild(NbScrollThresholdDirective) scrollDirective: NbScrollThresholdDirective; - @ViewChild('scrollingElement') scrollingElement: ElementRef; - - listenWindowScroll = false; - threshold = THRESHOLD; - - thresholdReached() {} -} From d59ee1a6c61031f29a6236e2d5a9406c076d5654 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Wed, 27 Jun 2018 09:56:59 +0300 Subject: [PATCH 012/129] feat(unit-tests): suppress output for skipped tests --- karma.conf.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/karma.conf.js b/karma.conf.js index aa66be13bb..65127c18de 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -27,6 +27,9 @@ module.exports = function (config) { environment: 'dev' }, reporters: ['spec', 'kjhtml'], + specReporter: { + suppressSkipped: true, + }, port: 9876, colors: true, logLevel: config.LOG_INFO, From f7a21e9b95d373e79b80c4b1d5b8df3c780adc24 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Thu, 28 Jun 2018 17:29:22 +0300 Subject: [PATCH 013/129] feat(infinite-list): app paging in url --- .../infinite-list/infinite-list.component.ts | 162 +++++++++++++++++- .../infinite-list/infinite-list.module.ts | 15 +- ...infinite-list-window-showcase.component.ts | 25 ++- .../list/infinite-list/infinite-window.scss | 5 + 4 files changed, 193 insertions(+), 14 deletions(-) create mode 100644 src/playground/list/infinite-list/infinite-window.scss diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts index 970bd96647..9a72bc09b0 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -1,4 +1,45 @@ -import { Component, Input, Output, EventEmitter, HostBinding } from '@angular/core'; +import { + Component, + Input, + Output, + EventEmitter, + HostBinding, + ElementRef, + ContentChildren, + AfterViewInit, + OnDestroy, + QueryList, + ContentChild, + Directive, + HostListener, + OnInit, + ViewChild, +} from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Subject, Observable } from 'rxjs'; +import { takeWhile } from 'rxjs/operators'; +import { NbListComponent, NbListItemComponent } from '../list.component'; + +@Directive({ + selector: 'button[nbDisableAutoLoadButton]', +}) +export class NbDisableAutoLoadButtonDirective { + private clicked = new Subject(); + + get click(): Observable { + return this.clicked.asObservable(); + } + + @Input() + @HostBinding('attr.aria-pressed') + isPressed = false; + + @HostListener('click') + emitClick() { + this.isPressed = !this.isPressed; + this.clicked.next(); + } +} /** * Infinite list component. @@ -8,29 +49,138 @@ import { Component, Input, Output, EventEmitter, HostBinding } from '@angular/co @Component({ selector: 'nb-infinite-list', template: ` + + - + (thresholdReached)="onThresholdReached()"> + + + `, styleUrls: [ './infintie-list.component.scss' ], }) -export class NbInfiniteListComponent { +export class NbInfiniteListComponent implements OnInit, AfterViewInit, OnDestroy { + + private alive = true; @Input() role = 'feed'; + @Input() autoLoading = true; + + @Input() tag: string; + @Input() @HostBinding('class.window-scroll') listenWindowScroll = false; @Input() loadMoreThreshold; - @Output() loadNext = new EventEmitter(); + @Output() loadNext = new EventEmitter(); + + @ViewChild(NbListComponent, { read: ElementRef }) listElement: ElementRef; + + @ContentChildren(NbListItemComponent, { read: ElementRef }) listItems: QueryList; + + @ContentChild(NbDisableAutoLoadButtonDirective) disableButton: NbDisableAutoLoadButtonDirective; + + @ContentChild('loadMoreButton') loadMoreButton: ElementRef; + + private elementPageMap = new Map(); + + private lastReqestedPage: number; + private lastLength: number = 0; + private intersectionObserver; + + constructor( + private route: ActivatedRoute, + private router: Router, + ) {} + + ngOnInit() { + this.disableButton.isPressed = !this.autoLoading; + + const rootElement = this.listenWindowScroll + ? null + : this.listElement.nativeElement; + this.intersectionObserver = new IntersectionObserver( + this.updatePage.bind(this), + { root: rootElement }, + ); + } + + ngAfterViewInit() { + this.disableButton.click + .pipe(takeWhile(() => this.alive)) + .subscribe(() => this.autoLoading = !this.autoLoading); + + this.route.params.subscribe(params => { + this.lastReqestedPage = params[this.tag] || 1; + this.addNewItems(); + }); + + this.listItems.changes + .pipe(takeWhile(() => this.alive)) + .subscribe(() => this.addNewItems()); + } + + ngOnDestroy() { + this.alive = false; + this.intersectionObserver.disconnect(); + this.elementPageMap.clear(); + } + + onThresholdReached() { + if (this.autoLoading) { + this.emitLoadNext(); + } + } emitLoadNext() { - this.loadNext.emit(); + this.lastReqestedPage++; + this.loadNext.emit(this.lastReqestedPage); + } + + disableAutoLoading() { + this.autoLoading = false; + } + + addNewItems() { + const firstNewItemElement = this.listItems.toArray()[this.lastLength].nativeElement; + const lastNewItemElement = this.listItems.last.nativeElement; + + if (this.elementPageMap.has(firstNewItemElement) || this.elementPageMap.has(lastNewItemElement)) { + return; + } + + this.lastLength = this.listItems.length; + + this.elementPageMap.set(firstNewItemElement, this.lastReqestedPage); + this.elementPageMap.set(lastNewItemElement, this.lastReqestedPage); + + for (const element of [ firstNewItemElement, lastNewItemElement ]) { + this.intersectionObserver.observe(element); + } + } + + updatePage(entries) { + const lastEntry = entries[entries.length - 1]; + + if (!lastEntry.isIntersecting) { + return; + } + + const currentPage = this.elementPageMap.get(lastEntry.target); + this.router.navigate( + ['.'], + { + queryParams: { [this.tag]: currentPage }, + replaceUrl: true, + relativeTo: this.route, + }, + ); } } diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.module.ts b/src/framework/theme/components/list/infinite-list/infinite-list.module.ts index 8158067755..b77fa1dfb5 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.module.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.module.ts @@ -1,11 +1,18 @@ import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; import { NbListModule } from '../list.module'; -import { NbInfiniteListComponent } from './infinite-list.component'; +import { NbInfiniteListComponent, NbDisableAutoLoadButtonDirective } from './infinite-list.component'; import { NbScrollThresholdDirective } from './scroll-threshold.directive'; +const componets = [ + NbInfiniteListComponent, + NbScrollThresholdDirective, + NbDisableAutoLoadButtonDirective, +]; + @NgModule({ - imports: [ NbListModule ], - declarations: [ NbInfiniteListComponent, NbScrollThresholdDirective ], - exports: [ NbInfiniteListComponent, NbScrollThresholdDirective ], + imports: [ CommonModule, NbListModule ], + declarations: componets, + exports: componets, }) export class NbInifiniteListModule {} diff --git a/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts b/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts index ef580c8a27..bce15922a3 100644 --- a/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts +++ b/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts @@ -5,15 +5,33 @@ import { Component } from '@angular/core'; + (loadNext)="loadNext()" + tag="fullPageScroll"> + {{ item.isPlaceholder ? 'placeholder' : 'item' }} {{ item.humanNumber }} + + + + `, - styleUrls: [ '../list-showcase.component.scss' ], + styleUrls: [ + '../list-showcase.component.scss', + './infinite-window.scss', + ], }) export class NbInfiniteListWindowShowcaseComponent { @@ -31,7 +49,7 @@ export class NbInfiniteListWindowShowcaseComponent { return index; } - loadNext() { + loadNext(page) { if (this.items.length >= this.maxLength) { return; } @@ -49,7 +67,6 @@ export class NbInfiniteListWindowShowcaseComponent { for (let i = nextItem; i < maxItem; i++) { this.items[i].isPlaceholder = false; } - // this.chageDetection.detectChanges(); }, 1000); } } diff --git a/src/playground/list/infinite-list/infinite-window.scss b/src/playground/list/infinite-list/infinite-window.scss new file mode 100644 index 0000000000..0c160d2e8c --- /dev/null +++ b/src/playground/list/infinite-list/infinite-window.scss @@ -0,0 +1,5 @@ +:host { + /deep/ nb-list nb-list-item { + height: 10rem !important; + } +} From 00631776ce912dfa26c130b24067da21deee7742 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Thu, 28 Jun 2018 20:57:17 +0300 Subject: [PATCH 014/129] feat(infinite-list): handle start from any page --- .../infinite-list/infinite-list.component.ts | 122 +++++++++++++----- .../scroll-threshold.directive.spec.ts | 8 +- .../scroll-threshold.directive.ts | 18 ++- ...infinite-list-window-showcase.component.ts | 48 ++++--- 4 files changed, 138 insertions(+), 58 deletions(-) diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts index 9a72bc09b0..96637895ec 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -17,7 +17,7 @@ import { } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { Subject, Observable } from 'rxjs'; -import { takeWhile } from 'rxjs/operators'; +import { take, takeWhile } from 'rxjs/operators'; import { NbListComponent, NbListItemComponent } from '../list.component'; @Directive({ @@ -55,7 +55,8 @@ export class NbDisableAutoLoadButtonDirective { [role]="role" [nbScrollThreshold]="loadMoreThreshold" [listenWindowScroll]="listenWindowScroll" - (thresholdReached)="onThresholdReached()"> + (bottomThresholdReached)="onBottomThresholdReached()" + (topThresholdReached)="onTopThresholdReached()"> @@ -80,19 +81,22 @@ export class NbInfiniteListComponent implements OnInit, AfterViewInit, OnDestroy @Input() loadMoreThreshold; @Output() loadNext = new EventEmitter(); + @Output() loadPrev = new EventEmitter(); @ViewChild(NbListComponent, { read: ElementRef }) listElement: ElementRef; @ContentChildren(NbListItemComponent, { read: ElementRef }) listItems: QueryList; - @ContentChild(NbDisableAutoLoadButtonDirective) disableButton: NbDisableAutoLoadButtonDirective; + @ContentChild(NbDisableAutoLoadButtonDirective) disableAutoLoadButton: NbDisableAutoLoadButtonDirective; @ContentChild('loadMoreButton') loadMoreButton: ElementRef; private elementPageMap = new Map(); - private lastReqestedPage: number; - private lastLength: number = 0; + private firstItem: Element; + private lastItem: Element; + private lastRequestedStartPage: number; + private lastRequestedEndPage: number; private intersectionObserver; constructor( @@ -101,30 +105,54 @@ export class NbInfiniteListComponent implements OnInit, AfterViewInit, OnDestroy ) {} ngOnInit() { - this.disableButton.isPressed = !this.autoLoading; + this.disableAutoLoadButton.isPressed = !this.autoLoading; const rootElement = this.listenWindowScroll ? null : this.listElement.nativeElement; this.intersectionObserver = new IntersectionObserver( this.updatePage.bind(this), - { root: rootElement }, + { root: rootElement, threshold: 1 }, ); } ngAfterViewInit() { - this.disableButton.click + this.disableAutoLoadButton.click .pipe(takeWhile(() => this.alive)) .subscribe(() => this.autoLoading = !this.autoLoading); - this.route.params.subscribe(params => { - this.lastReqestedPage = params[this.tag] || 1; - this.addNewItems(); - }); + let hasInitialData = this.listItems.length !== 0; + + this.route.queryParams + .pipe(take(1)) + .subscribe(params => { + const currentPage = params[this.tag] + ? parseInt(params[this.tag], 10) + : 1; + this.lastRequestedStartPage = currentPage; + this.lastRequestedEndPage = currentPage; + + if (hasInitialData) { + this.initializeFirstPage(currentPage); + } else { + // should I emit 'loadPrev' if it's not a first page? + if (this.lastRequestedStartPage > 1) { + this.emitLoadPrev(--this.lastRequestedStartPage); + } + this.emitLoadNext(this.lastRequestedEndPage); + } + }); this.listItems.changes .pipe(takeWhile(() => this.alive)) - .subscribe(() => this.addNewItems()); + .subscribe(() => { + if (hasInitialData) { + this.processNewItems(); + } else { + hasInitialData = true; + this.initializeFirstPage(this.lastRequestedEndPage); + } + }); } ngOnDestroy() { @@ -133,40 +161,76 @@ export class NbInfiniteListComponent implements OnInit, AfterViewInit, OnDestroy this.elementPageMap.clear(); } - onThresholdReached() { + onTopThresholdReached() { + if (this.autoLoading && this.lastRequestedStartPage > 1) { + this.emitLoadPrev(--this.lastRequestedStartPage); + } + } + + onBottomThresholdReached() { if (this.autoLoading) { - this.emitLoadNext(); + this.emitLoadNext(++this.lastRequestedEndPage); } } - emitLoadNext() { - this.lastReqestedPage++; - this.loadNext.emit(this.lastReqestedPage); + emitLoadPrev(page: number) { + this.loadPrev.emit(this.lastRequestedStartPage); + } + + emitLoadNext(page: number) { + this.loadNext.emit(this.lastRequestedEndPage); } disableAutoLoading() { this.autoLoading = false; } - addNewItems() { - const firstNewItemElement = this.listItems.toArray()[this.lastLength].nativeElement; - const lastNewItemElement = this.listItems.last.nativeElement; + private initializeFirstPage(page) { + this.firstItem = this.listItems.first.nativeElement; + this.lastItem = this.listItems.last.nativeElement; - if (this.elementPageMap.has(firstNewItemElement) || this.elementPageMap.has(lastNewItemElement)) { - return; + this.assignItemsToPage(page, this.firstItem, this.lastItem); + } + + private processNewItems() { + const hasNewItemsAtStart = this.listItems.first.nativeElement !== this.firstItem; + const hasNewItemsAtEnd = this.listItems.last.nativeElement !== this.lastItem; + + const itemsArray: ElementRef[] = this.listItems.toArray(); + + if (hasNewItemsAtStart) { + const lastStartNewItemIndex = itemsArray.findIndex(i => i.nativeElement === this.firstItem) - 1; + this.assignItemsToPage( + this.lastRequestedStartPage, + this.listItems.first.nativeElement, + itemsArray[lastStartNewItemIndex].nativeElement, + ); + // change scrollTop to height of all new items to preserve scroll position } - this.lastLength = this.listItems.length; + if (hasNewItemsAtEnd) { + const firstNewItemAtEndIndex = itemsArray.findIndex(i => i.nativeElement === this.lastItem) + 1; + this.assignItemsToPage( + this.lastRequestedEndPage, + itemsArray[firstNewItemAtEndIndex].nativeElement, + this.listItems.last.nativeElement, + ); + } - this.elementPageMap.set(firstNewItemElement, this.lastReqestedPage); - this.elementPageMap.set(lastNewItemElement, this.lastReqestedPage); + this.firstItem = this.listItems.first.nativeElement; + this.lastItem = this.listItems.last.nativeElement; + } - for (const element of [ firstNewItemElement, lastNewItemElement ]) { - this.intersectionObserver.observe(element); + private assignItemsToPage(page: number, ...items: Element[]) { + for (const item of items) { + if (!this.elementPageMap.has(item)) { + this.elementPageMap.set(item, page); + this.intersectionObserver.observe(item); + } } } - updatePage(entries) { + private updatePage(entries) { const lastEntry = entries[entries.length - 1]; if (!lastEntry.isIntersecting) { diff --git a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts index 21f82f5e6d..3ff3df353f 100644 --- a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts +++ b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts @@ -20,7 +20,7 @@ let scrollDirective: NbScrollThresholdDirective; [class.element-scroll]="!listenWindowScroll" [nbScrollThreshold]="threshold" [listenWindowScroll]="listenWindowScroll" - (thresholdReached)="thresholdReached()"> + (bottomThresholdReached)="bottomThresholdReached()">
`, @@ -46,7 +46,7 @@ class ScrollTestComponent { listenWindowScroll = false; threshold = THRESHOLD; - thresholdReached() { } + bottomThresholdReached() { } } describe('Directive: NbScrollDirective', () => { @@ -139,7 +139,7 @@ describe('Directive: NbScrollDirective', () => { it('should trigger event only when treshold reached (element scroll)', () => { const scrollingNativeElement = scrollingElement.nativeElement; - const tresholdReachedSpy = spyOn(componentInstance, 'thresholdReached'); + const tresholdReachedSpy = spyOn(componentInstance, 'bottomThresholdReached'); const reporterHeight = 1000; const positionUnderThreshold = CONTENT_HEIGHT - THRESHOLD - reporterHeight; @@ -157,7 +157,7 @@ describe('Directive: NbScrollDirective', () => { componentInstance.listenWindowScroll = true; fixture.detectChanges(); - const tresholdReachedSpy = spyOn(componentInstance, 'thresholdReached'); + const tresholdReachedSpy = spyOn(componentInstance, 'bottomThresholdReached'); const reporterHeight = 1000; const positionUnderThreshold = CONTENT_HEIGHT - THRESHOLD - reporterHeight; diff --git a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts index db6f85a6be..7a7eba83de 100644 --- a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts +++ b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts @@ -28,10 +28,16 @@ export class NbScrollThresholdDirective { listenWindowScroll = false; /** - * Emits when threshold reached. + * Emits when bottom threshold reached. */ @Output() - thresholdReached = new EventEmitter(); + bottomThresholdReached = new EventEmitter(); + + /** + * Emits when top threshold reached. + */ + @Output() + topThresholdReached = new EventEmitter(); @HostListener('window:resize') windowResize() { @@ -64,9 +70,13 @@ export class NbScrollThresholdDirective { private elementRef: ElementRef, ) {} - checkPosition(element) { + checkPosition(element: Element) { if (element.scrollHeight - element.scrollTop <= this.threshold) { - this.thresholdReached.emit(); + this.bottomThresholdReached.emit(); + } + + if (element.scrollTop <= this.threshold) { + this.topThresholdReached.emit(); } } diff --git a/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts b/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts index bce15922a3..12e4c45fef 100644 --- a/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts +++ b/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts @@ -5,7 +5,8 @@ import { Component } from '@angular/core'; newItems.forEach(i => i.isPlaceholder = false), + this.timeout, + ); + + return newItems; } - getIndex(_, { index }) { - return index; + loadPrev(page) { + this.items.unshift(...this.createNewPage(page)); } loadNext(page) { @@ -54,19 +73,6 @@ export class NbInfiniteListWindowShowcaseComponent { return; } - const placeholders = []; - const nextItem = this.items[this.items.length - 1].index + 1; - const maxItem = nextItem + this.pageSize; - for (let i = nextItem; i < maxItem; i++) { - placeholders.push({ index: i, humanNumber: i + 1, isPlaceholder: true }); - } - - this.items = this.items.concat(placeholders); - - setTimeout(() => { - for (let i = nextItem; i < maxItem; i++) { - this.items[i].isPlaceholder = false; - } - }, 1000); + this.items.push(...this.createNewPage(page)); } } From 5ba37910f458532c3c3fbc1c88718fe37a7b8af9 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Thu, 28 Jun 2018 21:35:25 +0300 Subject: [PATCH 015/129] fix(infinite-scroll): update page only when element is fully visible --- .../components/list/infinite-list/infinite-list.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts index 96637895ec..78d2353e09 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -230,10 +230,10 @@ export class NbInfiniteListComponent implements OnInit, AfterViewInit, OnDestroy } } - private updatePage(entries) { + private updatePage(entries: IntersectionObserverEntry[]) { const lastEntry = entries[entries.length - 1]; - if (!lastEntry.isIntersecting) { + if (lastEntry.intersectionRatio < 1) { return; } From c5ad39132e6deb039e8d2af628e35b64e2afe8cb Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Fri, 29 Jun 2018 15:35:17 +0300 Subject: [PATCH 016/129] refactor(infinite-scroll): remove paging --- .../infinite-list/infinite-list.component.ts | 153 ++---------------- ...infinite-list-window-showcase.component.ts | 13 +- 2 files changed, 15 insertions(+), 151 deletions(-) diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts index 78d2353e09..66a8e97032 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -5,20 +5,15 @@ import { EventEmitter, HostBinding, ElementRef, - ContentChildren, AfterViewInit, OnDestroy, - QueryList, ContentChild, Directive, HostListener, OnInit, - ViewChild, } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; import { Subject, Observable } from 'rxjs'; -import { take, takeWhile } from 'rxjs/operators'; -import { NbListComponent, NbListItemComponent } from '../list.component'; +import { takeWhile } from 'rxjs/operators'; @Directive({ selector: 'button[nbDisableAutoLoadButton]', @@ -52,7 +47,6 @@ export class NbDisableAutoLoadButtonDirective { (); - @Output() loadPrev = new EventEmitter(); - - @ViewChild(NbListComponent, { read: ElementRef }) listElement: ElementRef; - - @ContentChildren(NbListItemComponent, { read: ElementRef }) listItems: QueryList; + @Output() loadNext = new EventEmitter(); + @Output() loadPrev = new EventEmitter(); @ContentChild(NbDisableAutoLoadButtonDirective) disableAutoLoadButton: NbDisableAutoLoadButtonDirective; @ContentChild('loadMoreButton') loadMoreButton: ElementRef; - private elementPageMap = new Map(); - - private firstItem: Element; - private lastItem: Element; - private lastRequestedStartPage: number; - private lastRequestedEndPage: number; - private intersectionObserver; - - constructor( - private route: ActivatedRoute, - private router: Router, - ) {} - ngOnInit() { this.disableAutoLoadButton.isPressed = !this.autoLoading; - - const rootElement = this.listenWindowScroll - ? null - : this.listElement.nativeElement; - this.intersectionObserver = new IntersectionObserver( - this.updatePage.bind(this), - { root: rootElement, threshold: 1 }, - ); } ngAfterViewInit() { this.disableAutoLoadButton.click .pipe(takeWhile(() => this.alive)) .subscribe(() => this.autoLoading = !this.autoLoading); - - let hasInitialData = this.listItems.length !== 0; - - this.route.queryParams - .pipe(take(1)) - .subscribe(params => { - const currentPage = params[this.tag] - ? parseInt(params[this.tag], 10) - : 1; - this.lastRequestedStartPage = currentPage; - this.lastRequestedEndPage = currentPage; - - if (hasInitialData) { - this.initializeFirstPage(currentPage); - } else { - // should I emit 'loadPrev' if it's not a first page? - if (this.lastRequestedStartPage > 1) { - this.emitLoadPrev(--this.lastRequestedStartPage); - } - this.emitLoadNext(this.lastRequestedEndPage); - } - }); - - this.listItems.changes - .pipe(takeWhile(() => this.alive)) - .subscribe(() => { - if (hasInitialData) { - this.processNewItems(); - } else { - hasInitialData = true; - this.initializeFirstPage(this.lastRequestedEndPage); - } - }); } ngOnDestroy() { this.alive = false; - this.intersectionObserver.disconnect(); - this.elementPageMap.clear(); } onTopThresholdReached() { - if (this.autoLoading && this.lastRequestedStartPage > 1) { - this.emitLoadPrev(--this.lastRequestedStartPage); + if (this.autoLoading) { + this.emitLoadPrev(); } } onBottomThresholdReached() { if (this.autoLoading) { - this.emitLoadNext(++this.lastRequestedEndPage); + this.emitLoadNext(); } } - emitLoadPrev(page: number) { - this.loadPrev.emit(this.lastRequestedStartPage); + emitLoadPrev() { + this.loadPrev.emit(); } - emitLoadNext(page: number) { - this.loadNext.emit(this.lastRequestedEndPage); + emitLoadNext() { + this.loadNext.emit(); } disableAutoLoading() { this.autoLoading = false; } - - private initializeFirstPage(page) { - this.firstItem = this.listItems.first.nativeElement; - this.lastItem = this.listItems.last.nativeElement; - - this.assignItemsToPage(page, this.firstItem, this.lastItem); - } - - private processNewItems() { - const hasNewItemsAtStart = this.listItems.first.nativeElement !== this.firstItem; - const hasNewItemsAtEnd = this.listItems.last.nativeElement !== this.lastItem; - - const itemsArray: ElementRef[] = this.listItems.toArray(); - - if (hasNewItemsAtStart) { - const lastStartNewItemIndex = itemsArray.findIndex(i => i.nativeElement === this.firstItem) - 1; - this.assignItemsToPage( - this.lastRequestedStartPage, - this.listItems.first.nativeElement, - itemsArray[lastStartNewItemIndex].nativeElement, - ); - // change scrollTop to height of all new items to preserve scroll position - } - - if (hasNewItemsAtEnd) { - const firstNewItemAtEndIndex = itemsArray.findIndex(i => i.nativeElement === this.lastItem) + 1; - this.assignItemsToPage( - this.lastRequestedEndPage, - itemsArray[firstNewItemAtEndIndex].nativeElement, - this.listItems.last.nativeElement, - ); - } - - this.firstItem = this.listItems.first.nativeElement; - this.lastItem = this.listItems.last.nativeElement; - } - - private assignItemsToPage(page: number, ...items: Element[]) { - for (const item of items) { - if (!this.elementPageMap.has(item)) { - this.elementPageMap.set(item, page); - this.intersectionObserver.observe(item); - } - } - } - - private updatePage(entries: IntersectionObserverEntry[]) { - const lastEntry = entries[entries.length - 1]; - - if (lastEntry.intersectionRatio < 1) { - return; - } - - const currentPage = this.elementPageMap.get(lastEntry.target); - this.router.navigate( - ['.'], - { - queryParams: { [this.tag]: currentPage }, - replaceUrl: true, - relativeTo: this.route, - }, - ); - } } diff --git a/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts b/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts index 12e4c45fef..e9bc57dcff 100644 --- a/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts +++ b/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts @@ -5,9 +5,7 @@ import { Component } from '@angular/core'; + (loadNext)="loadNext($event)"> = this.maxLength) { return; } - this.items.push(...this.createNewPage(page)); + this.items.push(...this.createNewPage(this.page++)); } } From 602bfa17617fce327796de80afe6b4a7ed1d4d26 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Fri, 29 Jun 2018 20:31:45 +0300 Subject: [PATCH 017/129] feat(card): ability to pass arbitrary content directly into card --- src/framework/theme/components/card/card.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/framework/theme/components/card/card.component.ts b/src/framework/theme/components/card/card.component.ts index 5be9ee7e05..a02bfbdf9c 100644 --- a/src/framework/theme/components/card/card.component.ts +++ b/src/framework/theme/components/card/card.component.ts @@ -109,6 +109,7 @@ export class NbCardFooterComponent { + `, }) From 9b1e7550f4b83c7de6a184af74e7a95a154ea0dd Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Fri, 29 Jun 2018 20:33:04 +0300 Subject: [PATCH 018/129] feat(nb-list): add basic styles --- .../list/_list.component.theme.scss | 10 +++++++ .../theme/components/list/list.component.scss | 4 ++- .../theme/components/list/list.component.ts | 2 +- .../theme/styles/global/_components.scss | 2 ++ .../theme/styles/themes/_default.scss | 2 ++ .../infinite-list-showcase.component.ts | 2 +- ...infinite-list-window-showcase.component.ts | 5 +--- .../list/infinite-list/infinite-window.scss | 13 +++++++-- .../list/list-showcase.component.scss | 14 ---------- .../list/list-showcase.component.ts | 23 --------------- .../list/simple-list-showcase.component.scss | 4 +++ .../list/simple-list-showcase.component.ts | 28 +++++++++++++++++++ src/playground/playground-routing.module.ts | 3 +- src/playground/playground.module.ts | 2 ++ 14 files changed, 67 insertions(+), 47 deletions(-) create mode 100644 src/framework/theme/components/list/_list.component.theme.scss delete mode 100644 src/playground/list/list-showcase.component.scss delete mode 100644 src/playground/list/list-showcase.component.ts create mode 100644 src/playground/list/simple-list-showcase.component.scss create mode 100644 src/playground/list/simple-list-showcase.component.ts diff --git a/src/framework/theme/components/list/_list.component.theme.scss b/src/framework/theme/components/list/_list.component.theme.scss new file mode 100644 index 0000000000..a11f908489 --- /dev/null +++ b/src/framework/theme/components/list/_list.component.theme.scss @@ -0,0 +1,10 @@ +@mixin nb-list-theme() { + nb-list-item { + border-bottom: 1px solid nb-theme(list-item-border-color); + padding: nb-theme(list-item-padding); + + &:first-child { + border-top: 1px solid nb-theme(list-item-border-color); + } + } +} diff --git a/src/framework/theme/components/list/list.component.scss b/src/framework/theme/components/list/list.component.scss index 5d4e87f30f..895364c416 100644 --- a/src/framework/theme/components/list/list.component.scss +++ b/src/framework/theme/components/list/list.component.scss @@ -1,3 +1,5 @@ :host { - display: block; + display: flex; + flex-direction: column; + overflow: auto; } diff --git a/src/framework/theme/components/list/list.component.ts b/src/framework/theme/components/list/list.component.ts index 999475e4bf..10406f23e8 100644 --- a/src/framework/theme/components/list/list.component.ts +++ b/src/framework/theme/components/list/list.component.ts @@ -4,7 +4,7 @@ import { Component, Input, HostBinding } from '@angular/core'; * List component is a container component that wraps `nb-list-item` or `nb-list-items-group` components. * * Basic example: - * @stacked-example(Showcase, list/list-showcase.component) + * @stacked-example(Showcase, list/simple-list-showcase.component) */ @Component({ selector: 'nb-list', diff --git a/src/framework/theme/styles/global/_components.scss b/src/framework/theme/styles/global/_components.scss index dcecc85b34..b7b8b958a7 100644 --- a/src/framework/theme/styles/global/_components.scss +++ b/src/framework/theme/styles/global/_components.scss @@ -26,6 +26,7 @@ @import '../../components/stepper/stepper.component.theme'; @import '../../components/accordion/accordion.component.theme'; @import '../../components/button/button.component.theme'; +@import '../../components/list/list.component.theme'; @mixin nb-theme-components() { @@ -51,4 +52,5 @@ @include nb-chat-theme(); @include nb-accordion-theme(); @include nb-buttons-theme(); + @include nb-list-theme(); } diff --git a/src/framework/theme/styles/themes/_default.scss b/src/framework/theme/styles/themes/_default.scss index 36a82bde59..63c94dbe84 100644 --- a/src/framework/theme/styles/themes/_default.scss +++ b/src/framework/theme/styles/themes/_default.scss @@ -579,6 +579,8 @@ $theme: ( accordion-item-fg-text: color-fg-text, accordion-item-shadow: shadow, + list-item-border-color: separator, + list-item-padding: 1rem, ); // register the theme diff --git a/src/playground/list/infinite-list/infinite-list-showcase.component.ts b/src/playground/list/infinite-list/infinite-list-showcase.component.ts index ed07d732b4..4a71c8dc11 100644 --- a/src/playground/list/infinite-list/infinite-list-showcase.component.ts +++ b/src/playground/list/infinite-list/infinite-list-showcase.component.ts @@ -15,7 +15,7 @@ import { Component } from '@angular/core'; `, styleUrls: [ - '../list-showcase.component.scss', + './infinite-window.scss', './infinite-list-showcase.component.scss', ], }) diff --git a/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts b/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts index e9bc57dcff..12064c5e64 100644 --- a/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts +++ b/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts @@ -27,10 +27,7 @@ import { Component } from '@angular/core'; `, - styleUrls: [ - '../list-showcase.component.scss', - './infinite-window.scss', - ], + styleUrls: [ './infinite-window.scss' ], }) export class NbInfiniteListWindowShowcaseComponent { diff --git a/src/playground/list/infinite-list/infinite-window.scss b/src/playground/list/infinite-list/infinite-window.scss index 0c160d2e8c..ed53106bee 100644 --- a/src/playground/list/infinite-list/infinite-window.scss +++ b/src/playground/list/infinite-list/infinite-window.scss @@ -1,5 +1,14 @@ :host { - /deep/ nb-list nb-list-item { - height: 10rem !important; + nb-list-item { + background-color: lightblue; + padding: 1rem; + height: 10rem; + width: 50%; + min-width: 6rem; + margin: 1rem auto; + + &.placeholder { + background-color: lightgray; + } } } diff --git a/src/playground/list/list-showcase.component.scss b/src/playground/list/list-showcase.component.scss deleted file mode 100644 index f77a43b6dc..0000000000 --- a/src/playground/list/list-showcase.component.scss +++ /dev/null @@ -1,14 +0,0 @@ -:host { - nb-list-item { - background-color: lightblue; - padding: 1rem; - height: 3rem; - width: 50%; - min-width: 6rem; - margin: 1rem auto; - - &.placeholder { - background-color: lightgray; - } - } -} diff --git a/src/playground/list/list-showcase.component.ts b/src/playground/list/list-showcase.component.ts deleted file mode 100644 index 1aed354e43..0000000000 --- a/src/playground/list/list-showcase.component.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - template: ` - - - item {{ item.index + 1 }} - - - `, - styleUrls: ['./list-showcase.component.scss'], -}) -export class NbListShowcaseComponent { - - private pageSize = 10; - items = []; - - constructor() { - for (let i = 0; i < this.pageSize; i++) { - this.items.push({ index: i }); - } - } -} diff --git a/src/playground/list/simple-list-showcase.component.scss b/src/playground/list/simple-list-showcase.component.scss new file mode 100644 index 0000000000..c897e948a5 --- /dev/null +++ b/src/playground/list/simple-list-showcase.component.scss @@ -0,0 +1,4 @@ +nb-card { + max-width: 20rem; + margin: 0 auto; +} diff --git a/src/playground/list/simple-list-showcase.component.ts b/src/playground/list/simple-list-showcase.component.ts new file mode 100644 index 0000000000..e1bdda7e4a --- /dev/null +++ b/src/playground/list/simple-list-showcase.component.ts @@ -0,0 +1,28 @@ +import { Component } from '@angular/core'; + +@Component({ + template: ` + + + Simple list + + + + item {{ item.index + 1 }} + + + + `, + styleUrls: [ './simple-list-showcase.component.scss' ], +}) +export class NbSimpleListShowcaseComponent { + + private pageSize = 10; + items = []; + + constructor() { + for (let i = 0; i < this.pageSize; i++) { + this.items.push({ index: i }); + } + } +} diff --git a/src/playground/playground-routing.module.ts b/src/playground/playground-routing.module.ts index 4d57f2f558..4cb5eebf34 100644 --- a/src/playground/playground-routing.module.ts +++ b/src/playground/playground-routing.module.ts @@ -133,6 +133,7 @@ import { NbButtonOutlineComponent } from './button/button-outline.component'; import { NbButtonSizesComponent } from './button/button-sizes.component'; import { NbButtonTypesComponent } from './button/button-types.component'; import { NbListShowcaseComponent } from './list/list-showcase.component'; +import { NbSimpleListShowcaseComponent } from './list/simple-list-showcase.component'; import { NbInfiniteListShowcaseComponent } from './list/infinite-list/infinite-list-showcase.component'; import { NbInfiniteListWindowShowcaseComponent } from './list/infinite-list/infinite-list-window-showcase.component'; @@ -566,7 +567,7 @@ export const routes: Routes = [ children: [ { path: 'list-showcase.component', - component: NbListShowcaseComponent, + component: NbSimpleListShowcaseComponent, }, { path: 'infinite-list/infinite-list-showcase.component', diff --git a/src/playground/playground.module.ts b/src/playground/playground.module.ts index 28c2e14b9a..e31e43af91 100644 --- a/src/playground/playground.module.ts +++ b/src/playground/playground.module.ts @@ -164,6 +164,7 @@ import { NbButtonOutlineComponent } from './button/button-outline.component'; import { NbButtonSizesComponent } from './button/button-sizes.component'; import { NbButtonTypesComponent } from './button/button-types.component'; import { NbListShowcaseComponent } from './list/list-showcase.component'; +import { NbSimpleListShowcaseComponent } from './list/simple-list-showcase.component'; import { NbInfiniteListShowcaseComponent } from './list/infinite-list/infinite-list-showcase.component'; import { NbInfiniteListWindowShowcaseComponent } from './list/infinite-list/infinite-list-window-showcase.component'; @@ -320,6 +321,7 @@ export const NB_EXAMPLE_COMPONENTS = [ NbButtonSizesComponent, NbButtonTypesComponent, NbListShowcaseComponent, + NbSimpleListShowcaseComponent, NbInfiniteListShowcaseComponent, NbInfiniteListWindowShowcaseComponent, ]; From c80be9e1f40002aa4c1ded065cae1c7a1ad48ea3 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Sat, 30 Jun 2018 10:53:53 +0300 Subject: [PATCH 019/129] feat(nb-list): better example --- .../list/simple-list-showcase.component.ts | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/playground/list/simple-list-showcase.component.ts b/src/playground/list/simple-list-showcase.component.ts index e1bdda7e4a..d9ad151839 100644 --- a/src/playground/list/simple-list-showcase.component.ts +++ b/src/playground/list/simple-list-showcase.component.ts @@ -4,11 +4,11 @@ import { Component } from '@angular/core'; template: ` - Simple list + Simple list of some fruits - - item {{ item.index + 1 }} + + {{ fruit }} @@ -16,13 +16,17 @@ import { Component } from '@angular/core'; styleUrls: [ './simple-list-showcase.component.scss' ], }) export class NbSimpleListShowcaseComponent { - - private pageSize = 10; - items = []; - - constructor() { - for (let i = 0; i < this.pageSize; i++) { - this.items.push({ index: i }); - } - } + fruits = [ + 'Lemons', + 'Raspberries', + 'Strawberries', + 'Blackberries', + 'Kiwis', + 'Grapefruit', + 'Avocado', + 'Watermelon', + 'Cantaloupe', + 'Oranges', + 'Peaches', + ]; } From 73d78bd55605a241deefa7c0708e067e3a1a2815 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Sat, 30 Jun 2018 11:08:13 +0300 Subject: [PATCH 020/129] fix(nb-list docs): update route to match file name --- src/playground/playground-routing.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/playground/playground-routing.module.ts b/src/playground/playground-routing.module.ts index 4cb5eebf34..c3eb98395c 100644 --- a/src/playground/playground-routing.module.ts +++ b/src/playground/playground-routing.module.ts @@ -566,7 +566,7 @@ export const routes: Routes = [ path: 'list', children: [ { - path: 'list-showcase.component', + path: 'simple-list-showcase.component', component: NbSimpleListShowcaseComponent, }, { From fafc8cff70348cfe516bd181247196f22cd70ff4 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Sat, 30 Jun 2018 11:08:52 +0300 Subject: [PATCH 021/129] style(nb-list docs): better example text --- src/framework/theme/components/list/list.component.ts | 2 +- src/playground/list/simple-list-showcase.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/framework/theme/components/list/list.component.ts b/src/framework/theme/components/list/list.component.ts index 10406f23e8..cf55612880 100644 --- a/src/framework/theme/components/list/list.component.ts +++ b/src/framework/theme/components/list/list.component.ts @@ -4,7 +4,7 @@ import { Component, Input, HostBinding } from '@angular/core'; * List component is a container component that wraps `nb-list-item` or `nb-list-items-group` components. * * Basic example: - * @stacked-example(Showcase, list/simple-list-showcase.component) + * @stacked-example(Simple list, list/simple-list-showcase.component) */ @Component({ selector: 'nb-list', diff --git a/src/playground/list/simple-list-showcase.component.ts b/src/playground/list/simple-list-showcase.component.ts index d9ad151839..11a2751093 100644 --- a/src/playground/list/simple-list-showcase.component.ts +++ b/src/playground/list/simple-list-showcase.component.ts @@ -4,7 +4,7 @@ import { Component } from '@angular/core'; template: ` - Simple list of some fruits + Some fruits From b7af60e8e083c2828b1b13e9ed727857fde69395 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Sat, 30 Jun 2018 13:14:55 +0300 Subject: [PATCH 022/129] fix(playground): remove unused import --- src/playground/playground-routing.module.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/playground/playground-routing.module.ts b/src/playground/playground-routing.module.ts index c3eb98395c..a21abc4f3f 100644 --- a/src/playground/playground-routing.module.ts +++ b/src/playground/playground-routing.module.ts @@ -132,7 +132,6 @@ import { NbButtonHeroComponent } from './button/button-hero.component'; import { NbButtonOutlineComponent } from './button/button-outline.component'; import { NbButtonSizesComponent } from './button/button-sizes.component'; import { NbButtonTypesComponent } from './button/button-types.component'; -import { NbListShowcaseComponent } from './list/list-showcase.component'; import { NbSimpleListShowcaseComponent } from './list/simple-list-showcase.component'; import { NbInfiniteListShowcaseComponent } from './list/infinite-list/infinite-list-showcase.component'; import { NbInfiniteListWindowShowcaseComponent } from './list/infinite-list/infinite-list-window-showcase.component'; From 616822d4d3c33f5ede514c97682d4b08104ba163 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Sat, 30 Jun 2018 13:19:28 +0300 Subject: [PATCH 023/129] fix(playground): remove usage of deleted component --- src/playground/playground.module.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/playground/playground.module.ts b/src/playground/playground.module.ts index e31e43af91..33e16dd29c 100644 --- a/src/playground/playground.module.ts +++ b/src/playground/playground.module.ts @@ -163,7 +163,6 @@ import { NbButtonHeroComponent } from './button/button-hero.component'; import { NbButtonOutlineComponent } from './button/button-outline.component'; import { NbButtonSizesComponent } from './button/button-sizes.component'; import { NbButtonTypesComponent } from './button/button-types.component'; -import { NbListShowcaseComponent } from './list/list-showcase.component'; import { NbSimpleListShowcaseComponent } from './list/simple-list-showcase.component'; import { NbInfiniteListShowcaseComponent } from './list/infinite-list/infinite-list-showcase.component'; import { NbInfiniteListWindowShowcaseComponent } from './list/infinite-list/infinite-list-window-showcase.component'; From 1a6cd6f0aa0773cb92a390c4005fc3a3820a0fe6 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Sat, 30 Jun 2018 15:31:56 +0300 Subject: [PATCH 024/129] docs(nb-list): add users list example --- .../theme/components/list/list.component.ts | 7 +++++- .../list/users-list-showcase.component.ts | 24 +++++++++++++++++++ src/playground/playground-routing.module.ts | 5 ++++ src/playground/playground.module.ts | 2 ++ 4 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 src/playground/list/users-list-showcase.component.ts diff --git a/src/framework/theme/components/list/list.component.ts b/src/framework/theme/components/list/list.component.ts index cf55612880..735db2d04d 100644 --- a/src/framework/theme/components/list/list.component.ts +++ b/src/framework/theme/components/list/list.component.ts @@ -1,10 +1,15 @@ import { Component, Input, HostBinding } from '@angular/core'; /** - * List component is a container component that wraps `nb-list-item` or `nb-list-items-group` components. + * List is a container component that wraps `nb-list-item` or `nb-list-items-group` components. * * Basic example: * @stacked-example(Simple list, list/simple-list-showcase.component) + * + * `nb-list-item` accepts arbitrary content, so you can create list of any components. + * + * List of users: + * @stacked-example(Users list, list/users-list-showcase.component) */ @Component({ selector: 'nb-list', diff --git a/src/playground/list/users-list-showcase.component.ts b/src/playground/list/users-list-showcase.component.ts new file mode 100644 index 0000000000..e0439c34c3 --- /dev/null +++ b/src/playground/list/users-list-showcase.component.ts @@ -0,0 +1,24 @@ +import { Component } from '@angular/core'; + +@Component({ + template: ` + + + + + + + + + `, + styleUrls: ['./simple-list-showcase.component.scss'], +}) +export class NbUsersListShowcaseComponent { + users: { name: string, title: string }[] = [ + { name: 'Carla Espinosa', title: 'Nurse' }, + { name: 'Bob Kelso', title: 'Doctor of Medicine' }, + { name: 'Janitor', title: 'Janitor' }, + { name: 'Perry Cox', title: 'Doctor of Medicine' }, + { name: 'Ben Sullivan', title: 'Carpenter and photographer' }, + ]; +} diff --git a/src/playground/playground-routing.module.ts b/src/playground/playground-routing.module.ts index a21abc4f3f..5569e2fa23 100644 --- a/src/playground/playground-routing.module.ts +++ b/src/playground/playground-routing.module.ts @@ -135,6 +135,7 @@ import { NbButtonTypesComponent } from './button/button-types.component'; import { NbSimpleListShowcaseComponent } from './list/simple-list-showcase.component'; import { NbInfiniteListShowcaseComponent } from './list/infinite-list/infinite-list-showcase.component'; import { NbInfiniteListWindowShowcaseComponent } from './list/infinite-list/infinite-list-window-showcase.component'; +import { NbUsersListShowcaseComponent } from './list/users-list-showcase.component'; export const routes: Routes = [ { @@ -568,6 +569,10 @@ export const routes: Routes = [ path: 'simple-list-showcase.component', component: NbSimpleListShowcaseComponent, }, + { + path: 'users-list-showcase.component', + component: NbUsersListShowcaseComponent, + }, { path: 'infinite-list/infinite-list-showcase.component', component: NbInfiniteListShowcaseComponent, diff --git a/src/playground/playground.module.ts b/src/playground/playground.module.ts index 33e16dd29c..203e581dbe 100644 --- a/src/playground/playground.module.ts +++ b/src/playground/playground.module.ts @@ -166,6 +166,7 @@ import { NbButtonTypesComponent } from './button/button-types.component'; import { NbSimpleListShowcaseComponent } from './list/simple-list-showcase.component'; import { NbInfiniteListShowcaseComponent } from './list/infinite-list/infinite-list-showcase.component'; import { NbInfiniteListWindowShowcaseComponent } from './list/infinite-list/infinite-list-window-showcase.component'; +import { NbUsersListShowcaseComponent } from './list/users-list-showcase.component'; export const NB_MODULES = [ NbCardModule, @@ -321,6 +322,7 @@ export const NB_EXAMPLE_COMPONENTS = [ NbButtonTypesComponent, NbListShowcaseComponent, NbSimpleListShowcaseComponent, + NbUsersListShowcaseComponent, NbInfiniteListShowcaseComponent, NbInfiniteListWindowShowcaseComponent, ]; From a1a6cc96698a50cae995b155e73068430a002702 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Sat, 30 Jun 2018 16:31:42 +0300 Subject: [PATCH 025/129] refactor(playground): move infinite list in a separate directory --- .../list/infinite-list/infinite-list.component.ts | 2 +- .../infinite-list-showcase.component.scss | 0 .../infinite-list-showcase.component.ts | 0 .../infinite-list-window-showcase.component.ts | 0 .../{list => }/infinite-list/infinite-window.scss | 0 src/playground/playground-routing.module.ts | 13 +++++++++---- src/playground/playground.module.ts | 4 ++-- 7 files changed, 12 insertions(+), 7 deletions(-) rename src/playground/{list => }/infinite-list/infinite-list-showcase.component.scss (100%) rename src/playground/{list => }/infinite-list/infinite-list-showcase.component.ts (100%) rename src/playground/{list => }/infinite-list/infinite-list-window-showcase.component.ts (100%) rename src/playground/{list => }/infinite-list/infinite-window.scss (100%) diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts index 66a8e97032..b74fab5820 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -39,7 +39,7 @@ export class NbDisableAutoLoadButtonDirective { /** * Infinite list component. * - * @stacked-example(Basic example:, list/infinite-list/infinite-list-showcase.component) + * @stacked-example(Basic example:, /infinite-list/infinite-list-showcase.component) */ @Component({ selector: 'nb-infinite-list', diff --git a/src/playground/list/infinite-list/infinite-list-showcase.component.scss b/src/playground/infinite-list/infinite-list-showcase.component.scss similarity index 100% rename from src/playground/list/infinite-list/infinite-list-showcase.component.scss rename to src/playground/infinite-list/infinite-list-showcase.component.scss diff --git a/src/playground/list/infinite-list/infinite-list-showcase.component.ts b/src/playground/infinite-list/infinite-list-showcase.component.ts similarity index 100% rename from src/playground/list/infinite-list/infinite-list-showcase.component.ts rename to src/playground/infinite-list/infinite-list-showcase.component.ts diff --git a/src/playground/list/infinite-list/infinite-list-window-showcase.component.ts b/src/playground/infinite-list/infinite-list-window-showcase.component.ts similarity index 100% rename from src/playground/list/infinite-list/infinite-list-window-showcase.component.ts rename to src/playground/infinite-list/infinite-list-window-showcase.component.ts diff --git a/src/playground/list/infinite-list/infinite-window.scss b/src/playground/infinite-list/infinite-window.scss similarity index 100% rename from src/playground/list/infinite-list/infinite-window.scss rename to src/playground/infinite-list/infinite-window.scss diff --git a/src/playground/playground-routing.module.ts b/src/playground/playground-routing.module.ts index 5569e2fa23..99e478b18c 100644 --- a/src/playground/playground-routing.module.ts +++ b/src/playground/playground-routing.module.ts @@ -133,8 +133,8 @@ import { NbButtonOutlineComponent } from './button/button-outline.component'; import { NbButtonSizesComponent } from './button/button-sizes.component'; import { NbButtonTypesComponent } from './button/button-types.component'; import { NbSimpleListShowcaseComponent } from './list/simple-list-showcase.component'; -import { NbInfiniteListShowcaseComponent } from './list/infinite-list/infinite-list-showcase.component'; -import { NbInfiniteListWindowShowcaseComponent } from './list/infinite-list/infinite-list-window-showcase.component'; +import { NbInfiniteListShowcaseComponent } from './infinite-list/infinite-list-showcase.component'; +import { NbInfiniteListWindowShowcaseComponent } from './infinite-list/infinite-list-window-showcase.component'; import { NbUsersListShowcaseComponent } from './list/users-list-showcase.component'; export const routes: Routes = [ @@ -573,12 +573,17 @@ export const routes: Routes = [ path: 'users-list-showcase.component', component: NbUsersListShowcaseComponent, }, + ], + }, + { + path: 'infinite-list', + children: [ { - path: 'infinite-list/infinite-list-showcase.component', + path: 'infinite-list-showcase.component', component: NbInfiniteListShowcaseComponent, }, { - path: 'infinite-list/infinite-list-window-showcase.component', + path: 'infinite-list-window-showcase.component', component: NbInfiniteListWindowShowcaseComponent, }, ], diff --git a/src/playground/playground.module.ts b/src/playground/playground.module.ts index 203e581dbe..226602e1de 100644 --- a/src/playground/playground.module.ts +++ b/src/playground/playground.module.ts @@ -164,8 +164,8 @@ import { NbButtonOutlineComponent } from './button/button-outline.component'; import { NbButtonSizesComponent } from './button/button-sizes.component'; import { NbButtonTypesComponent } from './button/button-types.component'; import { NbSimpleListShowcaseComponent } from './list/simple-list-showcase.component'; -import { NbInfiniteListShowcaseComponent } from './list/infinite-list/infinite-list-showcase.component'; -import { NbInfiniteListWindowShowcaseComponent } from './list/infinite-list/infinite-list-window-showcase.component'; +import { NbInfiniteListShowcaseComponent } from './infinite-list/infinite-list-showcase.component'; +import { NbInfiniteListWindowShowcaseComponent } from './infinite-list/infinite-list-window-showcase.component'; import { NbUsersListShowcaseComponent } from './list/users-list-showcase.component'; export const NB_MODULES = [ From 0c7de99a3c74528041ccad6d2647826ab35d2f8f Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Sat, 30 Jun 2018 16:51:37 +0300 Subject: [PATCH 026/129] refactor(infinite-list): simplify styles for element scroll mode --- .../components/list/infinite-list/infinite-list.component.ts | 5 +++++ .../list/infinite-list/infintie-list.component.scss | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts index b74fab5820..beefb1a56d 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -68,6 +68,11 @@ export class NbInfiniteListComponent implements OnInit, AfterViewInit, OnDestroy @HostBinding('class.window-scroll') listenWindowScroll = false; + @HostBinding('class.element-scroll') + get elementScroll() { + return !this.listenWindowScroll; + } + @Input() loadMoreThreshold; @Output() loadNext = new EventEmitter(); diff --git a/src/framework/theme/components/list/infinite-list/infintie-list.component.scss b/src/framework/theme/components/list/infinite-list/infintie-list.component.scss index a803247865..4d5c9c3cac 100644 --- a/src/framework/theme/components/list/infinite-list/infintie-list.component.scss +++ b/src/framework/theme/components/list/infinite-list/infintie-list.component.scss @@ -2,7 +2,7 @@ display: block; } -:host(:not(.window-scroll)) { +:host(.element-scroll) { height: 100%; overflow: hidden; From 1b544fc1e33e27ecebad551ee9eb96ff776dfc9a Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Sat, 30 Jun 2018 19:55:19 +0300 Subject: [PATCH 027/129] fix(infinite-list-docs): correct example path --- .../list/infinite-list/infinite-list.component.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts index beefb1a56d..74e24f5458 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -39,7 +39,7 @@ export class NbDisableAutoLoadButtonDirective { /** * Infinite list component. * - * @stacked-example(Basic example:, /infinite-list/infinite-list-showcase.component) + * @stacked-example(Basic example:, infinite-list/infinite-list-showcase.component) */ @Component({ selector: 'nb-infinite-list', @@ -83,13 +83,17 @@ export class NbInfiniteListComponent implements OnInit, AfterViewInit, OnDestroy @ContentChild('loadMoreButton') loadMoreButton: ElementRef; ngOnInit() { - this.disableAutoLoadButton.isPressed = !this.autoLoading; + if (this.disableAutoLoadButton) { + this.disableAutoLoadButton.isPressed = !this.autoLoading; + } } ngAfterViewInit() { - this.disableAutoLoadButton.click - .pipe(takeWhile(() => this.alive)) - .subscribe(() => this.autoLoading = !this.autoLoading); + if (this.disableAutoLoadButton) { + this.disableAutoLoadButton.click + .pipe(takeWhile(() => this.alive)) + .subscribe(() => this.autoLoading = !this.autoLoading); + } } ngOnDestroy() { From 35b1144bbf8954c0d5fa72784616545e68b30af1 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Wed, 4 Jul 2018 17:15:59 +0300 Subject: [PATCH 028/129] refactor(scroll-directive): use single event for document and layout scroll --- .../components/layout/layout.component.ts | 22 ++++++- .../scroll-threshold.directive.spec.ts | 65 +++++++------------ .../scroll-threshold.directive.ts | 34 +++------- 3 files changed, 50 insertions(+), 71 deletions(-) diff --git a/src/framework/theme/components/layout/layout.component.ts b/src/framework/theme/components/layout/layout.component.ts index f512fffaba..e62c4b13a0 100644 --- a/src/framework/theme/components/layout/layout.component.ts +++ b/src/framework/theme/components/layout/layout.component.ts @@ -420,11 +420,12 @@ export class NbLayoutComponent implements AfterViewInit, OnInit, OnDestroy { this.themeService.changeWindowWidth(event.target.innerWidth); } + @HostListener('window:scroll', ['$event']) @HostListener('scroll', ['$event']) - onELementScroll($event) { + onScroll($event) { const event = new CustomEvent( 'nbscroll', - { detail: { originalEvent: $event } }, + { detail: { originalEvent: $event, ...this.getScrollInfo() } }, ); this.document.dispatchEvent(event); } @@ -439,4 +440,21 @@ export class NbLayoutComponent implements AfterViewInit, OnInit, OnDestroy { this.scrollableContainerRef.nativeElement.scrollTo && this.scrollableContainerRef.nativeElement.scrollTo(0, 0); }); } + + private getScrollInfo(): { scrollTop: number, scrollHeight: number } { + let scrollHeight; + let scrollTop; + + if (this.withScroll) { + scrollHeight = this.elementRef.nativeElement.scrollHeight; + scrollTop = this.elementRef.nativeElement.scrollTop; + } else { + const { body, documentElement } = this.document; + const { pageYOffset } = this.window; + scrollHeight = Math.max(body.scrollHeight, documentElement.scrollHeight); + scrollTop = pageYOffset || documentElement.scrollTop || body.scrollTop || 0; + } + + return { scrollHeight, scrollTop }; + } } diff --git a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts index 3ff3df353f..5bba97eebe 100644 --- a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts +++ b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts @@ -7,6 +7,13 @@ import { NbInifiniteListModule } from './infinite-list.module'; const CONTENT_HEIGHT = 10000; const THRESHOLD = 2000; +function nbscrollEvent({ scrollHeight, scrollTop } = { scrollHeight: 0, scrollTop: 0 }) { + return new CustomEvent( + 'nbscroll', + { detail: { scrollHeight, scrollTop } }, + ); +} + let fixture: ComponentFixture; let componentInstance: ScrollTestComponent; let scrollingElement: ElementRef; @@ -71,21 +78,9 @@ describe('Directive: NbScrollDirective', () => { fixture.nativeElement.remove(); })); - it('should listen to window scroll', () => { - const windowScrollHandlerSpy = spyOn(scrollDirective, 'windowScroll'); - window.dispatchEvent(new Event('scroll')); - expect(windowScrollHandlerSpy).toHaveBeenCalledTimes(1); - }); - - it('should listen to window resize', () => { - const windowResizeHandlerSpy = spyOn(scrollDirective, 'windowResize'); - window.dispatchEvent(new Event('resize')); - expect(windowResizeHandlerSpy).toHaveBeenCalledTimes(1); - }); - - it('should listen to layout scroll', () => { + it('should listen to nbscroll event', () => { const layoutScrollHandlerSpy = spyOn(scrollDirective, 'layoutScroll'); - document.dispatchEvent(new Event('nbscroll')); + document.dispatchEvent(nbscrollEvent()); expect(layoutScrollHandlerSpy).toHaveBeenCalledTimes(1); }); @@ -95,46 +90,27 @@ describe('Directive: NbScrollDirective', () => { expect(elementScrollHandlerSpy).toHaveBeenCalledTimes(1); }); - it('should ignore window and layout scroll events when listen to element scroll', () => { + it('should ignore nbscroll event when listen to element scroll', () => { const checkPositionSpy = spyOn(scrollDirective, 'checkPosition'); - window.dispatchEvent(new Event('scroll')); - expect(checkPositionSpy).toHaveBeenCalledTimes(0); - - document.dispatchEvent(new Event('nbscroll')); + document.dispatchEvent(nbscrollEvent()); expect(checkPositionSpy).toHaveBeenCalledTimes(0); scrollingElement.nativeElement.dispatchEvent(new Event('scroll')); expect(checkPositionSpy).toHaveBeenCalledTimes(1); }); - it('should listen window or layout scroll when not listening to element scroll', () => { + it('should listen nbscroll when not listening to element scroll', () => { const checkPositionSpy = spyOn(scrollDirective, 'checkPosition'); componentInstance.listenWindowScroll = true; fixture.detectChanges(); - window.dispatchEvent(new Event('scroll')); + document.dispatchEvent(nbscrollEvent()); expect(checkPositionSpy).toHaveBeenCalledTimes(1); - document.dispatchEvent(new Event('nbscroll')); - expect(checkPositionSpy).toHaveBeenCalledTimes(2); - scrollingElement.nativeElement.dispatchEvent(new Event('scroll')); - expect(checkPositionSpy).toHaveBeenCalledTimes(2); - }); - - it('should listen window resize regardless of window or element scroll mode', () => { - const resizeHandlerSpy = spyOn(scrollDirective, 'windowResize'); - - window.dispatchEvent(new Event('resize')); - expect(resizeHandlerSpy).toHaveBeenCalledTimes(1); - - scrollDirective.listenWindowScroll = true; - fixture.detectChanges(); - - window.dispatchEvent(new Event('resize')); - expect(resizeHandlerSpy).toHaveBeenCalledTimes(2); + expect(checkPositionSpy).toHaveBeenCalledTimes(1); }); it('should trigger event only when treshold reached (element scroll)', () => { @@ -153,7 +129,9 @@ describe('Directive: NbScrollDirective', () => { expect(tresholdReachedSpy).toHaveBeenCalledTimes(1); }); - it('should trigger event only when treshold reached (window scroll)', () => { + it('should trigger event only when treshold reached (nbscroll)', () => { + const { documentElement } = document; + componentInstance.listenWindowScroll = true; fixture.detectChanges(); @@ -161,16 +139,17 @@ describe('Directive: NbScrollDirective', () => { const reporterHeight = 1000; const positionUnderThreshold = CONTENT_HEIGHT - THRESHOLD - reporterHeight; - document.documentElement.scrollTop = positionUnderThreshold; - window.dispatchEvent(new Event('scroll')); + documentElement.scrollTop = positionUnderThreshold; + document.dispatchEvent(nbscrollEvent(documentElement)); expect(tresholdReachedSpy).toHaveBeenCalledTimes(0); const positionBelowThreshold = CONTENT_HEIGHT - (THRESHOLD / 2); - document.documentElement.scrollTop = positionBelowThreshold; - window.dispatchEvent(new Event('scroll')); + documentElement.scrollTop = positionBelowThreshold; + document.dispatchEvent(nbscrollEvent(documentElement)); expect(tresholdReachedSpy).toHaveBeenCalledTimes(1); }); // TODO // it('should trigger event only when treshold reached (layout scroll)', () => {}); + // it('should trigger topThresholdReached when treshold reached (elment, window, layout scroll)', () => {}); }); diff --git a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts index 7a7eba83de..6c37c59b5e 100644 --- a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts +++ b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts @@ -39,49 +39,31 @@ export class NbScrollThresholdDirective { @Output() topThresholdReached = new EventEmitter(); - @HostListener('window:resize') - windowResize() { - this.checkPosition(this.getDocumentElement()); - } - - @HostListener('window:scroll') - windowScroll() { - if (this.listenWindowScroll) { - this.checkPosition(this.getDocumentElement()); - } - } - @HostListener('document:nbscroll', ['$event']) layoutScroll($event) { if (this.listenWindowScroll) { - this.checkPosition($event.target); + const { scrollHeight, scrollTop } = $event.detail; + this.checkPosition(scrollHeight, scrollTop); } } @HostListener('scroll') elementScroll() { if (!this.listenWindowScroll) { - this.checkPosition(this.elementRef.nativeElement); + const { scrollTop, scrollHeight } = this.elementRef.nativeElement; + this.checkPosition(scrollHeight, scrollTop); } } - constructor( - @Inject(NB_DOCUMENT) private document, - private elementRef: ElementRef, - ) {} + constructor(private elementRef: ElementRef) {} - checkPosition(element: Element) { - if (element.scrollHeight - element.scrollTop <= this.threshold) { + checkPosition(scrollHeight: number, scrollTop: number) { + if (scrollHeight - scrollTop <= this.threshold) { this.bottomThresholdReached.emit(); } - if (element.scrollTop <= this.threshold) { + if (scrollTop <= this.threshold) { this.topThresholdReached.emit(); } } - - private getDocumentElement() { - const { body, documentElement } = this.document; - return documentElement.scrollTop ? documentElement : body; - } } From 79e267a89e4ee5e28f0ced06a32d26e97db91ce9 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Wed, 4 Jul 2018 17:54:43 +0300 Subject: [PATCH 029/129] docs(nb-card): document ability to pass content directly to card --- .../theme/components/card/card.component.ts | 9 ++++- .../card/card-without-body.component.ts | 38 +++++++++++++++++++ src/playground/list/fruits-list.ts | 13 +++++++ .../list/simple-list-showcase.component.ts | 15 +------- src/playground/playground-routing.module.ts | 5 +++ src/playground/playground.module.ts | 2 + 6 files changed, 68 insertions(+), 14 deletions(-) create mode 100644 src/playground/card/card-without-body.component.ts create mode 100644 src/playground/list/fruits-list.ts diff --git a/src/framework/theme/components/card/card.component.ts b/src/framework/theme/components/card/card.component.ts index a02bfbdf9c..7af1a3e43e 100644 --- a/src/framework/theme/components/card/card.component.ts +++ b/src/framework/theme/components/card/card.component.ts @@ -7,7 +7,7 @@ import { Component, Input, HostBinding } from '@angular/core'; /** - * Component intended to be used within the `` component. + * Component intended to be used within the `` component. * It adds styles for a preset header section. * * @styles @@ -73,6 +73,13 @@ export class NbCardFooterComponent { * Card with header and footer: * @stacked-example(With Header & Footer, card/card-full.component) * + * Most of the time main card content goes to `nb-card-body`, so it styled and aligned according to header and footer. + * But in case you need a higher level of control, you can pass contend directly to `nb-card`, + * so `nb-card-body` stylings will not be applied. + * + * Consider an example with `nb-list` component: + * @stacked-example(Showcase, card/card-without-body.component) + * * Colored cards could be simply configured by providing a `status` property: * @stacked-example(Colored Card, card/card-colors.component) * diff --git a/src/playground/card/card-without-body.component.ts b/src/playground/card/card-without-body.component.ts new file mode 100644 index 0000000000..a23708209e --- /dev/null +++ b/src/playground/card/card-without-body.component.ts @@ -0,0 +1,38 @@ +import { Component } from '@angular/core'; +import { fruits } from '../list/fruits-list'; + +@Component({ + template: ` + + List inside nb-card-body + + + + {{ fruit }} + + + + + + List inside nb-card + + + {{ fruit }} + + + + `, + styles: [` + :host { + display: flex; + flex-wrap: wrap; + justify-content: space-around; + } + nb-card { + min-width: 18rem; + } + `], +}) +export class NbCardWithoutBodyComponent { + fruits = fruits; +} diff --git a/src/playground/list/fruits-list.ts b/src/playground/list/fruits-list.ts new file mode 100644 index 0000000000..7bae676a28 --- /dev/null +++ b/src/playground/list/fruits-list.ts @@ -0,0 +1,13 @@ +export const fruits: string[] = [ + 'Lemons', + 'Raspberries', + 'Strawberries', + 'Blackberries', + 'Kiwis', + 'Grapefruit', + 'Avocado', + 'Watermelon', + 'Cantaloupe', + 'Oranges', + 'Peaches', +]; diff --git a/src/playground/list/simple-list-showcase.component.ts b/src/playground/list/simple-list-showcase.component.ts index 11a2751093..be921cb81a 100644 --- a/src/playground/list/simple-list-showcase.component.ts +++ b/src/playground/list/simple-list-showcase.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { fruits } from './fruits-list'; @Component({ template: ` @@ -16,17 +17,5 @@ import { Component } from '@angular/core'; styleUrls: [ './simple-list-showcase.component.scss' ], }) export class NbSimpleListShowcaseComponent { - fruits = [ - 'Lemons', - 'Raspberries', - 'Strawberries', - 'Blackberries', - 'Kiwis', - 'Grapefruit', - 'Avocado', - 'Watermelon', - 'Cantaloupe', - 'Oranges', - 'Peaches', - ]; + fruits = fruits; } diff --git a/src/playground/playground-routing.module.ts b/src/playground/playground-routing.module.ts index 99e478b18c..4d7ce9df8d 100644 --- a/src/playground/playground-routing.module.ts +++ b/src/playground/playground-routing.module.ts @@ -136,6 +136,7 @@ import { NbSimpleListShowcaseComponent } from './list/simple-list-showcase.compo import { NbInfiniteListShowcaseComponent } from './infinite-list/infinite-list-showcase.component'; import { NbInfiniteListWindowShowcaseComponent } from './infinite-list/infinite-list-window-showcase.component'; import { NbUsersListShowcaseComponent } from './list/users-list-showcase.component'; +import { NbCardWithoutBodyComponent } from './card/card-without-body.component'; export const routes: Routes = [ { @@ -277,6 +278,10 @@ export const routes: Routes = [ path: 'card-sizes.component', component: NbCardSizesComponent, }, + { + path: 'card-without-body.component', + component: NbCardWithoutBodyComponent, + }, ], }, { diff --git a/src/playground/playground.module.ts b/src/playground/playground.module.ts index 226602e1de..e31275c8a8 100644 --- a/src/playground/playground.module.ts +++ b/src/playground/playground.module.ts @@ -167,6 +167,7 @@ import { NbSimpleListShowcaseComponent } from './list/simple-list-showcase.compo import { NbInfiniteListShowcaseComponent } from './infinite-list/infinite-list-showcase.component'; import { NbInfiniteListWindowShowcaseComponent } from './infinite-list/infinite-list-window-showcase.component'; import { NbUsersListShowcaseComponent } from './list/users-list-showcase.component'; +import { NbCardWithoutBodyComponent } from './card/card-without-body.component'; export const NB_MODULES = [ NbCardModule, @@ -240,6 +241,7 @@ export const NB_EXAMPLE_COMPONENTS = [ NbCardFullComponent, NbCardColorsComponent, NbCardAccentsComponent, + NbCardWithoutBodyComponent, NbCardSizesComponent, NbCardTestComponent, NbFlipCardShowcaseComponent, From d4a275cbe97146c91821b01c758929f8d272e1ea Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Wed, 4 Jul 2018 18:08:32 +0300 Subject: [PATCH 030/129] feat(nb-list): use tabs separator color --- src/framework/theme/styles/themes/_default.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/framework/theme/styles/themes/_default.scss b/src/framework/theme/styles/themes/_default.scss index 63c94dbe84..c82ea3ca04 100644 --- a/src/framework/theme/styles/themes/_default.scss +++ b/src/framework/theme/styles/themes/_default.scss @@ -579,7 +579,7 @@ $theme: ( accordion-item-fg-text: color-fg-text, accordion-item-shadow: shadow, - list-item-border-color: separator, + list-item-border-color: tabs-separator, list-item-padding: 1rem, ); From 9ffe7bb90b4ecb3b587164f30c01afc2de445883 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Wed, 4 Jul 2018 18:09:10 +0300 Subject: [PATCH 031/129] refactor(nb-list): remove unnecessary flex --- src/framework/theme/components/list/list.component.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/framework/theme/components/list/list.component.scss b/src/framework/theme/components/list/list.component.scss index 895364c416..78f70120f0 100644 --- a/src/framework/theme/components/list/list.component.scss +++ b/src/framework/theme/components/list/list.component.scss @@ -1,5 +1,4 @@ :host { - display: flex; - flex-direction: column; + flex: 1 1 auto; overflow: auto; } From f4b9ea3c16369999e08d6c1d1f43febaeb6bea4c Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Wed, 4 Jul 2018 19:12:04 +0300 Subject: [PATCH 032/129] feat(nb-lists): add components icons --- .../images/components/infinite-scroll.svg | 46 +++++++++++++++ docs/assets/images/components/list.svg | 57 +++++++++++++++++++ docs/structure.ts | 4 +- 3 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 docs/assets/images/components/infinite-scroll.svg create mode 100644 docs/assets/images/components/list.svg diff --git a/docs/assets/images/components/infinite-scroll.svg b/docs/assets/images/components/infinite-scroll.svg new file mode 100644 index 0000000000..5cd635b59d --- /dev/null +++ b/docs/assets/images/components/infinite-scroll.svg @@ -0,0 +1,46 @@ + + + + 723B61EE-8CCA-4B08-B6F3-0F08A814E117 + Created with sketchtool. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/assets/images/components/list.svg b/docs/assets/images/components/list.svg new file mode 100644 index 0000000000..319a9169c3 --- /dev/null +++ b/docs/assets/images/components/list.svg @@ -0,0 +1,57 @@ + + + + 80874A9B-A6FA-4172-A8E0-A6FCD6FC91D8 + Created with sketchtool. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/structure.ts b/docs/structure.ts index 633e192ea3..c0a81fc322 100644 --- a/docs/structure.ts +++ b/docs/structure.ts @@ -330,13 +330,13 @@ export const structure = [ { type: 'tabs', name: 'List', - icon: '', + icon: 'list.svg', source: [ 'NbListComponent', 'NbListItemComponent', 'NbListItemsGroupComponent' ], }, { type: 'tabs', name: 'Infinite List', - icon: '', + icon: 'infinite-scroll.svg', source: [ 'NbInfiniteListComponent', 'NbScrollThresholdDirective' ], }, ], From b6eb5e1d393c723b1d51ddb7c7ac0711cb908ac4 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Wed, 4 Jul 2018 19:23:23 +0300 Subject: [PATCH 033/129] style(infinite-list): remove unused imports --- .../components/list/infinite-list/infinite-list.component.ts | 1 - .../list/infinite-list/scroll-threshold.directive.ts | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts index 74e24f5458..827115391f 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -4,7 +4,6 @@ import { Output, EventEmitter, HostBinding, - ElementRef, AfterViewInit, OnDestroy, ContentChild, diff --git a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts index 6c37c59b5e..aee063b8f4 100644 --- a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts +++ b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts @@ -1,5 +1,4 @@ -import { Directive, Input, HostListener, Inject, ElementRef, EventEmitter, Output } from '@angular/core'; -import { NB_DOCUMENT } from '../../../theme.options'; +import { Directive, Input, HostListener, ElementRef, EventEmitter, Output } from '@angular/core'; /** * Scroll Threshold Directive From 79c24df580b10624af848218444af6020425c9a7 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Wed, 4 Jul 2018 19:25:41 +0300 Subject: [PATCH 034/129] style(nb-infinite-list): remove unnecessary colon --- .../components/list/infinite-list/infinite-list.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts index 827115391f..b2299a2663 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -38,7 +38,7 @@ export class NbDisableAutoLoadButtonDirective { /** * Infinite list component. * - * @stacked-example(Basic example:, infinite-list/infinite-list-showcase.component) + * @stacked-example(Basic example, infinite-list/infinite-list-showcase.component) */ @Component({ selector: 'nb-infinite-list', From 9672633a25afe38f2f443f891b02770103eeb416 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Wed, 4 Jul 2018 20:00:34 +0300 Subject: [PATCH 035/129] refactor(infinite list examples): extract common code into base class --- .../infinite-list-showcase.component.ts | 36 +++------------ ...infinite-list-window-showcase.component.ts | 45 +++---------------- src/playground/infinite-list/list-base.ts | 38 ++++++++++++++++ 3 files changed, 49 insertions(+), 70 deletions(-) create mode 100644 src/playground/infinite-list/list-base.ts diff --git a/src/playground/infinite-list/infinite-list-showcase.component.ts b/src/playground/infinite-list/infinite-list-showcase.component.ts index 4a71c8dc11..8eabf7f31f 100644 --- a/src/playground/infinite-list/infinite-list-showcase.component.ts +++ b/src/playground/infinite-list/infinite-list-showcase.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { ListBase } from './list-base'; @Component({ template: ` @@ -6,11 +7,13 @@ import { Component } from '@angular/core'; + {{ item.isPlaceholder ? 'placeholder' : 'item' }} {{ item.humanNumber }} + `, @@ -19,42 +22,13 @@ import { Component } from '@angular/core'; './infinite-list-showcase.component.scss', ], }) -export class NbInfiniteListShowcaseComponent { - - private pageSize = 20; - private maxLength = 100000; - items = []; - - constructor() { - for (let i = 0; i < this.pageSize; i++) { - this.items.push({ index: i, humanNumber: i + 1 }); - } - } +export class NbInfiniteListShowcaseComponent extends ListBase { getIndex(_, { index }) { return index; } loadNext() { - if (this.items.length >= this.maxLength) { - return; - } - - const placeholders = []; - const nextItem = this.items[this.items.length - 1].index + 1; - const maxItem = nextItem + this.pageSize; - for (let i = nextItem; i < maxItem; i++) { - placeholders.push({ index: i, humanNumber: i + 1, isPlaceholder: true }); - } - - this.items = this.items.concat(placeholders); - - setTimeout(() => { - for (let i = nextItem; i < maxItem; i++) { - this.items[i].isPlaceholder = false; - } - - // this.chageDetection.detectChanges(); - }, 1000); + this.addPage(); } } diff --git a/src/playground/infinite-list/infinite-list-window-showcase.component.ts b/src/playground/infinite-list/infinite-list-window-showcase.component.ts index 12064c5e64..0185c6a248 100644 --- a/src/playground/infinite-list/infinite-list-window-showcase.component.ts +++ b/src/playground/infinite-list/infinite-list-window-showcase.component.ts @@ -1,11 +1,12 @@ import { Component } from '@angular/core'; +import { ListBase } from './list-base'; @Component({ template: ` + (loadNext)="loadNext()"> - - `, styleUrls: [ './infinite-window.scss' ], }) -export class NbInfiniteListWindowShowcaseComponent { - - private timeout = 1000; - - private page = 1; - private pageSize = 10; - private maxLength = 100000; - items = []; +export class NbInfiniteListWindowShowcaseComponent extends ListBase { getIndex(_, { index }) { return index; } - createNewPage(page: number): any[] { - const pageIndex = page - 1; - const firstItemIndex = pageIndex * this.pageSize; - const lastItemIndex = firstItemIndex + this.pageSize; - const newItems = []; - - for (let i = firstItemIndex; i < lastItemIndex; i++) { - newItems.push({ index: i, humanNumber: i + 1, isPlaceholder: true }) - } - - setTimeout( - () => newItems.forEach(i => i.isPlaceholder = false), - this.timeout, - ); - - return newItems; - } - loadPrev() {} loadNext() { - if (this.items.length >= this.maxLength) { - return; - } - - this.items.push(...this.createNewPage(this.page++)); + this.addPage(); } } diff --git a/src/playground/infinite-list/list-base.ts b/src/playground/infinite-list/list-base.ts new file mode 100644 index 0000000000..6dfb1fa14d --- /dev/null +++ b/src/playground/infinite-list/list-base.ts @@ -0,0 +1,38 @@ +export class ListBase { + private timeout = 1000; + + private page = 1; + private pageSize = 10; + private maxLength = 100000; + items = []; + + constructor() { + this.addPage(); + } + + createNewPage(page: number): any[] { + const pageIndex = page - 1; + const firstItemIndex = pageIndex * this.pageSize; + const lastItemIndex = firstItemIndex + this.pageSize; + const newItems = []; + + for (let i = firstItemIndex; i < lastItemIndex; i++) { + newItems.push({ index: i, humanNumber: i + 1, isPlaceholder: true }) + } + + setTimeout( + () => newItems.forEach(i => i.isPlaceholder = false), + this.timeout, + ); + + return newItems; + } + + addPage() { + if (this.items.length >= this.maxLength) { + return; + } + + this.items.push(...this.createNewPage(this.page++)); + } +} From 5561317d07266dac4f9d1d6b75b728aed30a9cf5 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Wed, 4 Jul 2018 20:03:16 +0300 Subject: [PATCH 036/129] fix(infinite scroll): use auto load button only when available, change input name --- .../list/infinite-list/infinite-list.component.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts index b2299a2663..41869ff4e2 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -26,11 +26,11 @@ export class NbDisableAutoLoadButtonDirective { @Input() @HostBinding('attr.aria-pressed') - isPressed = false; + pressed = false; @HostListener('click') emitClick() { - this.isPressed = !this.isPressed; + this.pressed = !this.pressed; this.clicked.next(); } } @@ -81,14 +81,9 @@ export class NbInfiniteListComponent implements OnInit, AfterViewInit, OnDestroy @ContentChild('loadMoreButton') loadMoreButton: ElementRef; - ngOnInit() { - if (this.disableAutoLoadButton) { - this.disableAutoLoadButton.isPressed = !this.autoLoading; - } - } - ngAfterViewInit() { if (this.disableAutoLoadButton) { + this.disableAutoLoadButton.pressed = !this.autoLoading; this.disableAutoLoadButton.click .pipe(takeWhile(() => this.alive)) .subscribe(() => this.autoLoading = !this.autoLoading); From 19473f7a7999249649a04c794972587272748b90 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Wed, 4 Jul 2018 20:03:42 +0300 Subject: [PATCH 037/129] feat(infinite-list): add load more button directive --- .../list/infinite-list/infinite-list.component.ts | 12 ++++++++---- .../list/infinite-list/infinite-list.module.ts | 7 ++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts index 41869ff4e2..4c49f37672 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -35,6 +35,11 @@ export class NbDisableAutoLoadButtonDirective { } } +@Directive({ + selector: 'button[nbLoadMoreButtonDirective]', +}) +export class NbLoadMoreButtonDirective {} + /** * Infinite list component. * @@ -53,11 +58,11 @@ export class NbDisableAutoLoadButtonDirective { - + `, styleUrls: [ './infintie-list.component.scss' ], }) -export class NbInfiniteListComponent implements OnInit, AfterViewInit, OnDestroy { +export class NbInfiniteListComponent implements AfterViewInit, OnDestroy { private alive = true; @@ -78,8 +83,7 @@ export class NbInfiniteListComponent implements OnInit, AfterViewInit, OnDestroy @Output() loadPrev = new EventEmitter(); @ContentChild(NbDisableAutoLoadButtonDirective) disableAutoLoadButton: NbDisableAutoLoadButtonDirective; - - @ContentChild('loadMoreButton') loadMoreButton: ElementRef; + @ContentChild(NbLoadMoreButtonDirective) loadMoreButton: NbLoadMoreButtonDirective; ngAfterViewInit() { if (this.disableAutoLoadButton) { diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.module.ts b/src/framework/theme/components/list/infinite-list/infinite-list.module.ts index b77fa1dfb5..4636130dbe 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.module.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.module.ts @@ -1,13 +1,18 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { NbListModule } from '../list.module'; -import { NbInfiniteListComponent, NbDisableAutoLoadButtonDirective } from './infinite-list.component'; +import { + NbInfiniteListComponent, + NbDisableAutoLoadButtonDirective, + NbLoadMoreButtonDirective, +} from './infinite-list.component'; import { NbScrollThresholdDirective } from './scroll-threshold.directive'; const componets = [ NbInfiniteListComponent, NbScrollThresholdDirective, NbDisableAutoLoadButtonDirective, + NbLoadMoreButtonDirective, ]; @NgModule({ From bb2275a5181e3dc572febcb203d272a66a13f7f0 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Wed, 4 Jul 2018 20:05:53 +0300 Subject: [PATCH 038/129] style(infinite-list example): remove unnucessary bindings --- .../infinite-list-window-showcase.component.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/playground/infinite-list/infinite-list-window-showcase.component.ts b/src/playground/infinite-list/infinite-list-window-showcase.component.ts index 0185c6a248..f816a30539 100644 --- a/src/playground/infinite-list/infinite-list-window-showcase.component.ts +++ b/src/playground/infinite-list/infinite-list-window-showcase.component.ts @@ -14,13 +14,9 @@ import { ListBase } from './list-base'; {{ item.isPlaceholder ? 'placeholder' : 'item' }} {{ item.humanNumber }} - + - + `, styleUrls: [ './infinite-window.scss' ], From 69b6927f18db1e8b1005fe82a97f55859e47eb70 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Thu, 5 Jul 2018 12:50:54 +0300 Subject: [PATCH 039/129] fix(infinite-list): trigger change detection after changing button state --- .../components/list/infinite-list/infinite-list.component.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts index 4c49f37672..9d38a7d25a 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -9,7 +9,7 @@ import { ContentChild, Directive, HostListener, - OnInit, + ChangeDetectorRef, } from '@angular/core'; import { Subject, Observable } from 'rxjs'; import { takeWhile } from 'rxjs/operators'; @@ -85,6 +85,8 @@ export class NbInfiniteListComponent implements AfterViewInit, OnDestroy { @ContentChild(NbDisableAutoLoadButtonDirective) disableAutoLoadButton: NbDisableAutoLoadButtonDirective; @ContentChild(NbLoadMoreButtonDirective) loadMoreButton: NbLoadMoreButtonDirective; + constructor(private changeDetection: ChangeDetectorRef) {} + ngAfterViewInit() { if (this.disableAutoLoadButton) { this.disableAutoLoadButton.pressed = !this.autoLoading; @@ -92,6 +94,7 @@ export class NbInfiniteListComponent implements AfterViewInit, OnDestroy { .pipe(takeWhile(() => this.alive)) .subscribe(() => this.autoLoading = !this.autoLoading); } + this.changeDetection.detectChanges(); } ngOnDestroy() { From 40557e305bbc684268062b398959323b6d88d447 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Thu, 5 Jul 2018 12:53:33 +0300 Subject: [PATCH 040/129] feat(nb-list): make list block level --- src/framework/theme/components/list/list.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/framework/theme/components/list/list.component.scss b/src/framework/theme/components/list/list.component.scss index 78f70120f0..55e4c28542 100644 --- a/src/framework/theme/components/list/list.component.scss +++ b/src/framework/theme/components/list/list.component.scss @@ -1,4 +1,5 @@ :host { + display: block; flex: 1 1 auto; overflow: auto; } From 6e635eb6e957ccc343eb9b02a131fd0313707a1f Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Thu, 5 Jul 2018 12:56:12 +0300 Subject: [PATCH 041/129] fix(nb-infinite-list): nb-list occupy all remaining space --- .../list/infinite-list/infintie-list.component.scss | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/framework/theme/components/list/infinite-list/infintie-list.component.scss b/src/framework/theme/components/list/infinite-list/infintie-list.component.scss index 4d5c9c3cac..dd52c92808 100644 --- a/src/framework/theme/components/list/infinite-list/infintie-list.component.scss +++ b/src/framework/theme/components/list/infinite-list/infintie-list.component.scss @@ -1,13 +1,19 @@ :host { - display: block; + display: flex; + flex-direction: column; } :host(.element-scroll) { height: 100%; - overflow: hidden; nb-list { - height: 100%; overflow-y: scroll; } + + /deep/ { + [nbDisableAutoLoadButton], + [nbLoadMoreButtonDirective] { + flex-shrink: 0; + } + } } From 9798dd543fe119a5b92cbb37798a1dc8a172be42 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Thu, 5 Jul 2018 12:56:38 +0300 Subject: [PATCH 042/129] style(nb-infinite-list): add buttons to examples --- .../infinite-list/infinite-list-showcase.component.ts | 3 +++ .../infinite-list/infinite-list-window-showcase.component.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/playground/infinite-list/infinite-list-showcase.component.ts b/src/playground/infinite-list/infinite-list-showcase.component.ts index 8eabf7f31f..7bccb00665 100644 --- a/src/playground/infinite-list/infinite-list-showcase.component.ts +++ b/src/playground/infinite-list/infinite-list-showcase.component.ts @@ -14,6 +14,9 @@ import { ListBase } from './list-base'; {{ item.isPlaceholder ? 'placeholder' : 'item' }} {{ item.humanNumber }} + + + `, diff --git a/src/playground/infinite-list/infinite-list-window-showcase.component.ts b/src/playground/infinite-list/infinite-list-window-showcase.component.ts index f816a30539..81d73b48c2 100644 --- a/src/playground/infinite-list/infinite-list-window-showcase.component.ts +++ b/src/playground/infinite-list/infinite-list-window-showcase.component.ts @@ -15,8 +15,8 @@ import { ListBase } from './list-base'; - + `, styleUrls: [ './infinite-window.scss' ], From 1f78aa9e08953040f186344ba6420c8ae6fa5ea7 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Thu, 5 Jul 2018 13:29:15 +0300 Subject: [PATCH 043/129] feat(lists): add exports of list components --- src/framework/theme/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/framework/theme/index.ts b/src/framework/theme/index.ts index ce4e816165..5b1a503b4e 100644 --- a/src/framework/theme/index.ts +++ b/src/framework/theme/index.ts @@ -54,5 +54,7 @@ export * from './components/accordion/accordion-item-header.component'; export * from './components/accordion/accordion.module'; export * from './components/button/button.component'; export * from './components/button/button.module'; +export * from './components/list/list.component'; export * from './components/list/list.module'; +export * from './components/list/infinite-list/infinite-list.component'; export * from './components/list/infinite-list/infinite-list.module'; From 730cfb268f65059dbd2f9b39eb1b6bcdd7f518e9 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Thu, 5 Jul 2018 13:37:49 +0300 Subject: [PATCH 044/129] feat(nb-infinite-list): hide auto load button when auto loading enabled --- .../infinite-list-showcase.component.ts | 9 +++++++-- .../infinite-list-window-showcase.component.ts | 13 +++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/playground/infinite-list/infinite-list-showcase.component.ts b/src/playground/infinite-list/infinite-list-showcase.component.ts index 7bccb00665..c5de53d754 100644 --- a/src/playground/infinite-list/infinite-list-showcase.component.ts +++ b/src/playground/infinite-list/infinite-list-showcase.component.ts @@ -1,10 +1,12 @@ -import { Component } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; import { ListBase } from './list-base'; +import { NbInfiniteListComponent } from '@nebular/theme'; @Component({ template: `
@@ -15,7 +17,7 @@ import { ListBase } from './list-base'; - +
@@ -27,6 +29,9 @@ import { ListBase } from './list-base'; }) export class NbInfiniteListShowcaseComponent extends ListBase { + @ViewChild('infiniteList') + infiniteListComponent: NbInfiniteListComponent; + getIndex(_, { index }) { return index; } diff --git a/src/playground/infinite-list/infinite-list-window-showcase.component.ts b/src/playground/infinite-list/infinite-list-window-showcase.component.ts index 81d73b48c2..46ef9ce443 100644 --- a/src/playground/infinite-list/infinite-list-window-showcase.component.ts +++ b/src/playground/infinite-list/infinite-list-window-showcase.component.ts @@ -1,9 +1,11 @@ -import { Component } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; import { ListBase } from './list-base'; +import { NbInfiniteListComponent } from '@nebular/theme'; @Component({ template: ` @@ -15,7 +17,11 @@ import { ListBase } from './list-base'; - + `, @@ -23,6 +29,9 @@ import { ListBase } from './list-base'; }) export class NbInfiniteListWindowShowcaseComponent extends ListBase { + @ViewChild('infiniteList') + infiniteListComponent: NbInfiniteListComponent; + getIndex(_, { index }) { return index; } From a4888c332a07af8e0c3736153b5ca933f44d4f42 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Thu, 5 Jul 2018 14:18:50 +0300 Subject: [PATCH 045/129] fix(nb-infinite-list): resubscribe to clicks if buttons was changed --- .../infinite-list/infinite-list.component.ts | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts index 9d38a7d25a..02aa1553e1 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -4,14 +4,14 @@ import { Output, EventEmitter, HostBinding, - AfterViewInit, OnDestroy, ContentChild, Directive, HostListener, ChangeDetectorRef, + AfterViewChecked, } from '@angular/core'; -import { Subject, Observable } from 'rxjs'; +import { Subject, Observable, Subscription } from 'rxjs'; import { takeWhile } from 'rxjs/operators'; @Directive({ @@ -24,7 +24,6 @@ export class NbDisableAutoLoadButtonDirective { return this.clicked.asObservable(); } - @Input() @HostBinding('attr.aria-pressed') pressed = false; @@ -38,7 +37,18 @@ export class NbDisableAutoLoadButtonDirective { @Directive({ selector: 'button[nbLoadMoreButtonDirective]', }) -export class NbLoadMoreButtonDirective {} +export class NbLoadMoreButtonDirective { + private clicked = new Subject(); + + get click() { + return this.clicked.asObservable(); + } + + @HostListener('click') + emitClick() { + this.clicked.next(); + } +} /** * Infinite list component. @@ -62,7 +72,7 @@ export class NbLoadMoreButtonDirective {} `, styleUrls: [ './infintie-list.component.scss' ], }) -export class NbInfiniteListComponent implements AfterViewInit, OnDestroy { +export class NbInfiniteListComponent implements AfterViewChecked, OnDestroy { private alive = true; @@ -85,15 +95,31 @@ export class NbInfiniteListComponent implements AfterViewInit, OnDestroy { @ContentChild(NbDisableAutoLoadButtonDirective) disableAutoLoadButton: NbDisableAutoLoadButtonDirective; @ContentChild(NbLoadMoreButtonDirective) loadMoreButton: NbLoadMoreButtonDirective; + private disableClickSubscription: Subscription; + private loadMoreClickSubscription: Subscription; + constructor(private changeDetection: ChangeDetectorRef) {} - ngAfterViewInit() { + ngAfterViewChecked() { + if (this.disableClickSubscription) { + this.disableClickSubscription.unsubscribe(); + } + if (this.loadMoreClickSubscription) { + this.loadMoreClickSubscription.unsubscribe(); + } + if (this.disableAutoLoadButton) { this.disableAutoLoadButton.pressed = !this.autoLoading; - this.disableAutoLoadButton.click + this.disableClickSubscription = this.disableAutoLoadButton.click .pipe(takeWhile(() => this.alive)) .subscribe(() => this.autoLoading = !this.autoLoading); } + if (this.loadMoreButton) { + this.loadMoreClickSubscription = this.loadMoreButton.click + .pipe(takeWhile(() => this.alive)) + .subscribe(() => this.emitLoadNext()) + } + this.changeDetection.detectChanges(); } From 3385428ec1d49ac9725081f7ed106f707ddac4c5 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Thu, 5 Jul 2018 16:13:13 +0300 Subject: [PATCH 046/129] docs(infinite-list): document public api --- .../infinite-list/infinite-list.component.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts index 02aa1553e1..9c89039d27 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -76,8 +76,16 @@ export class NbInfiniteListComponent implements AfterViewChecked, OnDestroy { private alive = true; + /** + * Enables emiting of load more event when treshold reached. + * When disabled load more event will be fired only after clicking on load more button. + */ @Input() autoLoading = true; + /** + * By default component observes self scroll position. + * If set to `true`, component will observe position of window scroll. + */ @Input() @HostBinding('class.window-scroll') listenWindowScroll = false; @@ -87,9 +95,19 @@ export class NbInfiniteListComponent implements AfterViewChecked, OnDestroy { return !this.listenWindowScroll; } - @Input() loadMoreThreshold; + /** + * Threshold after which event load more event will be emited. + * In pixels. + */ + @Input() loadMoreThreshold: number; + /** + * Emits when distance between bottom of the observed element and current scroll position is less than threshold. + */ @Output() loadNext = new EventEmitter(); + /** + * Emits when distance between top of the observed element and current scroll position is less than threshold. + */ @Output() loadPrev = new EventEmitter(); @ContentChild(NbDisableAutoLoadButtonDirective) disableAutoLoadButton: NbDisableAutoLoadButtonDirective; From 6f53ccd6a3985cc8eed1f66e8b2b8bcc0f7e51ea Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Fri, 6 Jul 2018 15:10:31 +0300 Subject: [PATCH 047/129] docs(infinite-list): add images list --- .../infinite-images-list.component.ts | 25 +++ .../infinite-list-showcase.component.ts | 6 +- .../infinite-list/random-svg.component.ts | 177 ++++++++++++++++++ src/playground/playground-routing.module.ts | 5 + src/playground/playground.module.ts | 4 + 5 files changed, 212 insertions(+), 5 deletions(-) create mode 100644 src/playground/infinite-list/infinite-images-list.component.ts create mode 100644 src/playground/infinite-list/random-svg.component.ts diff --git a/src/playground/infinite-list/infinite-images-list.component.ts b/src/playground/infinite-list/infinite-images-list.component.ts new file mode 100644 index 0000000000..ca7c308fe8 --- /dev/null +++ b/src/playground/infinite-list/infinite-images-list.component.ts @@ -0,0 +1,25 @@ +import { Component } from '@angular/core'; +import { ListBase } from './list-base'; + +@Component({ + selector: 'nb-infinite-images-list', + template: ` + + + + + + `, + styles: [` + + `], +}) +export class NbInfiniteImagesListComponent extends ListBase { + + newItem(index: number) { + return this.simpleNewItem(index); + } +} diff --git a/src/playground/infinite-list/infinite-list-showcase.component.ts b/src/playground/infinite-list/infinite-list-showcase.component.ts index c5de53d754..b0ef835153 100644 --- a/src/playground/infinite-list/infinite-list-showcase.component.ts +++ b/src/playground/infinite-list/infinite-list-showcase.component.ts @@ -8,7 +8,7 @@ import { NbInfiniteListComponent } from '@nebular/theme'; + (loadNext)="addPage()"> + Loading + + + + + + + `, + styles: [ ` + :host { + display: block; + } + `], +}) +export class NbRandomSvgComponent { + + goldenRatio = 0.618033988749895; + hexWidth = 2; + + @Input() + width = 200; + + @Input() + height = 100; + + color: string; + + lines = []; + circles = []; + polygons = []; + + @Input() + minCountForShape = 2; + + @Input() + maxCountForShape = 10; + + @Input() isLoading; + + constructor() { + const { width, height } = this; + + this.color = this.randomColor(); + + let count = this.randomNumber(this.minCountForShape, this.maxCountForShape); + for (let i = 0; i < count; i++) { + this.lines.push({ + strokeColor: this.randomColor(), + strokeWidth: this.randomNumber(1, 5), + opacity: this.randomNumber(), + x1: this.randomNumber(width / 10, width), + y1: this.randomNumber(height / 10, height), + x2: this.randomNumber(width / 10, width), + y2: this.randomNumber(height / 10, height), + }); + } + count = 0; + count = this.randomNumber(this.minCountForShape, this.maxCountForShape); + for (let i = 0; i < count; i++) { + this.circles.push({ + cx: this.randomNumber(0, width), + cy: this.randomNumber(0, height), + r: this.randomNumber(1, Math.min(width, height) / 2), + opacity: this.randomNumber(0.1, 0.5), + fill: this.randomColor(), + }); + } + count = 0; + count = this.randomNumber(this.minCountForShape, this.maxCountForShape); + for (let i = 0; i < count; i++) { + this.polygons.push({ + fill: this.randomColor(), + points: this.randomPoints(width, height).join(' '), + opacity: this.randomNumber(0.1, 0.3), + }); + } + } + + get currentColor() { + return this.isLoading ? 'lightgray' : this.color; + } + + get viewbox() { + return `0 0 ${this.width} ${this.height}`; + } + + randomNumber(min: number = 0, max: number = 1): number { + return Math.random() * (max - min) + min; + } + + randomPoints(width: number, height: number): string[] { + const points = []; + + for (let i = 0; i < 3; i++) { + points.push(`${this.randomNumber(0, width)},${this.randomNumber(0, height)}`); + } + + return points; + } + + randomColor() { + return this.rgbToHex(this.getRgb()); + } + + getRgb() { + let hue = Math.random(); + hue += this.goldenRatio; + hue %= 1; + + const saturation = 0.5; + const value = 0.95; + + return this.hsvToRgb(hue, saturation, value); + } + + padHex(str) { + if (str.length > this.hexWidth) { + return str; + } + return new Array(this.hexWidth - str.length + 1).join('0') + str; + } + + rgbToHex(rgb) { + const parts = rgb + .map(val => this.padHex(val.toString(16))) + .join(''); + + return `#${parts}`; + } + + hsvToRgb(h, s, v) { + const hi = Math.floor(h * 6); + const f = h * 6 - hi; + const p = v * (1 - s); + const q = v * (1 - f * s); + const t = v * (1 - (1 - f) * s); + let r = 255; + let g = 255; + let b = 255; + + switch (hi) { + case 0: r = v; g = t; b = p; break; + case 1: r = q; g = v; b = p; break; + case 2: r = p; g = v; b = t; break; + case 3: r = p; g = q; b = v; break; + case 4: r = t; g = p; b = v; break; + case 5: r = v; g = p; b = q; break; + } + + return [Math.floor(r * 255), Math.floor(g * 255), Math.floor(b * 255)]; + } +} diff --git a/src/playground/playground-routing.module.ts b/src/playground/playground-routing.module.ts index 4d7ce9df8d..ab811b7582 100644 --- a/src/playground/playground-routing.module.ts +++ b/src/playground/playground-routing.module.ts @@ -137,6 +137,7 @@ import { NbInfiniteListShowcaseComponent } from './infinite-list/infinite-list-s import { NbInfiniteListWindowShowcaseComponent } from './infinite-list/infinite-list-window-showcase.component'; import { NbUsersListShowcaseComponent } from './list/users-list-showcase.component'; import { NbCardWithoutBodyComponent } from './card/card-without-body.component'; +import { NbInfiniteImagesListComponent } from './infinite-list/infinite-images-list.component'; export const routes: Routes = [ { @@ -591,6 +592,10 @@ export const routes: Routes = [ path: 'infinite-list-window-showcase.component', component: NbInfiniteListWindowShowcaseComponent, }, + { + path: 'infinite-images-list.component', + component: NbInfiniteImagesListComponent, + }, ], }, ], diff --git a/src/playground/playground.module.ts b/src/playground/playground.module.ts index e31275c8a8..28330b94e6 100644 --- a/src/playground/playground.module.ts +++ b/src/playground/playground.module.ts @@ -168,6 +168,8 @@ import { NbInfiniteListShowcaseComponent } from './infinite-list/infinite-list-s import { NbInfiniteListWindowShowcaseComponent } from './infinite-list/infinite-list-window-showcase.component'; import { NbUsersListShowcaseComponent } from './list/users-list-showcase.component'; import { NbCardWithoutBodyComponent } from './card/card-without-body.component'; +import { NbInfiniteImagesListComponent } from './infinite-list/infinite-images-list.component'; +import { NbRandomSvgComponent } from './infinite-list/random-svg.component'; export const NB_MODULES = [ NbCardModule, @@ -327,6 +329,8 @@ export const NB_EXAMPLE_COMPONENTS = [ NbUsersListShowcaseComponent, NbInfiniteListShowcaseComponent, NbInfiniteListWindowShowcaseComponent, + NbInfiniteImagesListComponent, + NbRandomSvgComponent, ]; From e74843da20a7be5158a1329df077416e8edac762 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Fri, 6 Jul 2018 15:15:54 +0300 Subject: [PATCH 048/129] fix(image-list): remove unused method --- .../infinite-list/infinite-images-list.component.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/playground/infinite-list/infinite-images-list.component.ts b/src/playground/infinite-list/infinite-images-list.component.ts index ca7c308fe8..6660b34ee8 100644 --- a/src/playground/infinite-list/infinite-images-list.component.ts +++ b/src/playground/infinite-list/infinite-images-list.component.ts @@ -17,9 +17,4 @@ import { ListBase } from './list-base'; `], }) -export class NbInfiniteImagesListComponent extends ListBase { - - newItem(index: number) { - return this.simpleNewItem(index); - } -} +export class NbInfiniteImagesListComponent extends ListBase {} From 86893214bb181e804c702c1013c2e6e84e578072 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Fri, 6 Jul 2018 15:18:42 +0300 Subject: [PATCH 049/129] docs(infinite-list): initial news list example --- .../infinite-news-list.component.scss | 4 + .../infinite-news-list.component.ts | 154 ++++++++++++++++++ src/playground/infinite-list/list-base.ts | 20 ++- src/playground/playground-routing.module.ts | 5 + src/playground/playground.module.ts | 2 + 5 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 src/playground/infinite-list/infinite-news-list.component.scss create mode 100644 src/playground/infinite-list/infinite-news-list.component.ts diff --git a/src/playground/infinite-list/infinite-news-list.component.scss b/src/playground/infinite-list/infinite-news-list.component.scss new file mode 100644 index 0000000000..bdeae3128c --- /dev/null +++ b/src/playground/infinite-list/infinite-news-list.component.scss @@ -0,0 +1,4 @@ +nb-infinite-list { + max-width: 50rem; + margin: 0 auto; +} diff --git a/src/playground/infinite-list/infinite-news-list.component.ts b/src/playground/infinite-list/infinite-news-list.component.ts new file mode 100644 index 0000000000..8cb3469fb7 --- /dev/null +++ b/src/playground/infinite-list/infinite-news-list.component.ts @@ -0,0 +1,154 @@ +import { Component, AfterViewInit, OnDestroy, ViewChildren, QueryList, ElementRef } from '@angular/core'; +import { Router, ActivatedRoute } from '@angular/router'; +import { NbListItemComponent } from '@nebular/theme'; +import { takeWhile, take } from 'rxjs/operators'; +import { ListBase } from './list-base'; + +@Component({ + selector: 'nb-infinite-news-list', + template: ` + + + + + + `, + styleUrls: [ `infinite-news-list.component.scss` ], +}) +export class NbInfiniteNewsListComponent extends ListBase implements AfterViewInit, OnDestroy { + + alive = true; + listenWindowScroll = true; + + intersectionObserver: IntersectionObserver; + postIdsToObserve: number[] = []; + pageByPostId = new Map(); + visibleItems = new Map(); + + @ViewChildren(NbListItemComponent, { read: ElementRef }) postsList: QueryList; + + constructor( + private router: Router, + private route: ActivatedRoute, + ) { + super(); + + this.timeout = 3000; + } + + ngAfterViewInit() { + this.intersectionObserver = new IntersectionObserver( + this.updateUrl.bind(this), + { threshold: [ 0, 0.25, 0.75, 1 ] }, + ); + + this.postsList.changes + .pipe(takeWhile(() => this.alive)) + .subscribe(() => this.observeNewItems()); + + this.route.queryParams + .pipe(take(1)) + .subscribe((params => { + const currentPage = Number.parseInt(params.page, 10) || 1; + const firstItemId = this.items[0].id; + const lastItemId = this.items[this.items.length - 1].id; + + this.pageByPostId.set(firstItemId, currentPage); + this.pageByPostId.set(lastItemId, currentPage); + + this.postIdsToObserve.push(firstItemId, lastItemId); + this.observeNewItems(); + })); + } + + ngOnDestroy() { + this.alive = false; + this.intersectionObserver.disconnect(); + } + + loadNext() { + const { page, items } = this.addPage(); + const firstAndLastItemsIds = [items[0].id, items[items.length - 1].id]; + for (const id of firstAndLastItemsIds) { + this.pageByPostId.set(id, page); + } + this.postIdsToObserve.push(...firstAndLastItemsIds); + } + + newItem(index: number) { + return { + index, + id: index, + title: `Post ${index + 1}`, + text: `Post placeholder text.`, + }; + } + + private updateUrl(entries: IntersectionObserverEntry[]) { + for (const entry of entries) { + const postId = this.getPostId(entry.target); + + if (entry.intersectionRatio === 0) { + this.visibleItems.delete(postId); + } else { + this.visibleItems.set(postId, entry.intersectionRatio); + } + } + + if (this.visibleItems.size === 0) { + return; + } + + let mostVisibleItemId; + let maxIntersection = 0; + this.visibleItems.forEach((intersectionRatio, id) => { + if (intersectionRatio > maxIntersection) { + maxIntersection = intersectionRatio; + mostVisibleItemId = id; + } + }); + + // TODO + // navigate only if not already on current page + this.router.navigate( + ['.'], + { + queryParams: { page: this.pageByPostId.get(mostVisibleItemId) }, + replaceUrl: true, + relativeTo: this.route, + }, + ); + } + + private observeNewItems() { + const postsArray = this.postsList.toArray(); + const lastPostIndex = postsArray.length - 1; + + for (let i = lastPostIndex; i >= 0; i--) { + const postElement = postsArray[i].nativeElement; + const postId = this.getPostId(postElement); + + const index = this.postIdsToObserve.indexOf(postId); + if (index !== -1) { + this.intersectionObserver.observe(postElement); + this.postIdsToObserve.splice(index, 1); + } + + if (this.postIdsToObserve.length === 0) { + return; + } + } + } + + private getPostId(element: Element): number { + return Number.parseInt(element.getAttribute('data-post-id'), 10); + } +} diff --git a/src/playground/infinite-list/list-base.ts b/src/playground/infinite-list/list-base.ts index 6dfb1fa14d..c932fca2a7 100644 --- a/src/playground/infinite-list/list-base.ts +++ b/src/playground/infinite-list/list-base.ts @@ -1,5 +1,5 @@ -export class ListBase { - private timeout = 1000; +export abstract class ListBase { + protected timeout = 1000 private page = 1; private pageSize = 10; @@ -17,7 +17,11 @@ export class ListBase { const newItems = []; for (let i = firstItemIndex; i < lastItemIndex; i++) { - newItems.push({ index: i, humanNumber: i + 1, isPlaceholder: true }) + if (this.newItem) { + newItems.push({ isPlaceholder: true, ...this.newItem(i) }); + } else { + newItems.push({ index: i, humanNumber: i + 1, isPlaceholder: true }); + } } setTimeout( @@ -28,11 +32,17 @@ export class ListBase { return newItems; } - addPage() { + addPage(): { page: number, items: any[] } { if (this.items.length >= this.maxLength) { return; } - this.items.push(...this.createNewPage(this.page++)); + const page = this.page; + const items = this.createNewPage(this.page++); + this.items.push(...items); + + return { page, items }; } + + abstract newItem(index: number); } diff --git a/src/playground/playground-routing.module.ts b/src/playground/playground-routing.module.ts index ab811b7582..42b537cc37 100644 --- a/src/playground/playground-routing.module.ts +++ b/src/playground/playground-routing.module.ts @@ -138,6 +138,7 @@ import { NbInfiniteListWindowShowcaseComponent } from './infinite-list/infinite- import { NbUsersListShowcaseComponent } from './list/users-list-showcase.component'; import { NbCardWithoutBodyComponent } from './card/card-without-body.component'; import { NbInfiniteImagesListComponent } from './infinite-list/infinite-images-list.component'; +import { NbInfiniteNewsListComponent } from './infinite-list/infinite-news-list.component'; export const routes: Routes = [ { @@ -596,6 +597,10 @@ export const routes: Routes = [ path: 'infinite-images-list.component', component: NbInfiniteImagesListComponent, }, + { + path: 'infinite-news-list.component', + component: NbInfiniteNewsListComponent, + }, ], }, ], diff --git a/src/playground/playground.module.ts b/src/playground/playground.module.ts index 28330b94e6..e78fc9214d 100644 --- a/src/playground/playground.module.ts +++ b/src/playground/playground.module.ts @@ -170,6 +170,7 @@ import { NbUsersListShowcaseComponent } from './list/users-list-showcase.compone import { NbCardWithoutBodyComponent } from './card/card-without-body.component'; import { NbInfiniteImagesListComponent } from './infinite-list/infinite-images-list.component'; import { NbRandomSvgComponent } from './infinite-list/random-svg.component'; +import { NbInfiniteNewsListComponent } from './infinite-list/infinite-news-list.component'; export const NB_MODULES = [ NbCardModule, @@ -331,6 +332,7 @@ export const NB_EXAMPLE_COMPONENTS = [ NbInfiniteListWindowShowcaseComponent, NbInfiniteImagesListComponent, NbRandomSvgComponent, + NbInfiniteNewsListComponent, ]; From a61894ec4d9f44ac1259dd65975494b77e617a79 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Fri, 6 Jul 2018 22:04:44 +0300 Subject: [PATCH 050/129] docs(infinite-list): add paging --- .../infinite-news-list.component.ts | 199 ++++++++++++++---- src/playground/infinite-list/list-base.ts | 10 +- src/playground/playground-routing.module.ts | 8 +- src/playground/playground.module.ts | 8 +- 4 files changed, 171 insertions(+), 54 deletions(-) diff --git a/src/playground/infinite-list/infinite-news-list.component.ts b/src/playground/infinite-list/infinite-news-list.component.ts index 8cb3469fb7..ad616606be 100644 --- a/src/playground/infinite-list/infinite-news-list.component.ts +++ b/src/playground/infinite-list/infinite-news-list.component.ts @@ -1,47 +1,125 @@ -import { Component, AfterViewInit, OnDestroy, ViewChildren, QueryList, ElementRef } from '@angular/core'; +import { + Component, + AfterViewInit, + OnDestroy, + ViewChildren, + QueryList, + ElementRef, + Input, + OnInit, +} from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; import { NbListItemComponent } from '@nebular/theme'; import { takeWhile, take } from 'rxjs/operators'; -import { ListBase } from './list-base'; + +class TrackingIdWithPost { + id: number; + isLoading: boolean; + post: Post; +} + +export class Post { + id: number; + title: string; + text: string; + link: string; +} + +@Component({ + selector: 'nb-news-post-placeholder', + template: ` +
+
+
+
+
+
+ `, + styles: [` + :host { + background: rgba(216, 216, 216, 0.8705882352941177); + display: block; + height: 30rem; + } + `], +}) +export class NbNewsPostPlaceholderComponent {} + +@Component({ + selector: 'nb-news-post', + template: ` + + `, +}) +export class NbNewsPostComponent { + @Input() + post: Post; +} @Component({ selector: 'nb-infinite-news-list', template: ` - - + + + + + `, styleUrls: [ `infinite-news-list.component.scss` ], }) -export class NbInfiniteNewsListComponent extends ListBase implements AfterViewInit, OnDestroy { +export class NbInfiniteNewsListComponent implements OnInit, AfterViewInit, OnDestroy { alive = true; listenWindowScroll = true; + threshold = 2000; + + pageSize = 10; + maxPage = 100; + pageLoadingDelay = 1000; + previousPageToLoad: number; + nextPageToLoad: number; + visiblePage: number; intersectionObserver: IntersectionObserver; postIdsToObserve: number[] = []; pageByPostId = new Map(); visibleItems = new Map(); + news: TrackingIdWithPost[] = []; + @ViewChildren(NbListItemComponent, { read: ElementRef }) postsList: QueryList; constructor( private router: Router, private route: ActivatedRoute, - ) { - super(); + ) {} - this.timeout = 3000; + ngOnInit() { + this.route.queryParams + .pipe(take(1)) + .subscribe((params => { + const paramsPage = Number.parseInt(params.page, 10); + const pageToLoad = paramsPage || 1; + this.previousPageToLoad = pageToLoad > 1 ? pageToLoad - 1 : 0 ; + this.nextPageToLoad = pageToLoad; + // if not 1st load prev? + // if this.news not empty do not call load? + this.loadNext(); + })); } ngAfterViewInit() { @@ -53,20 +131,6 @@ export class NbInfiniteNewsListComponent extends ListBase implements AfterViewIn this.postsList.changes .pipe(takeWhile(() => this.alive)) .subscribe(() => this.observeNewItems()); - - this.route.queryParams - .pipe(take(1)) - .subscribe((params => { - const currentPage = Number.parseInt(params.page, 10) || 1; - const firstItemId = this.items[0].id; - const lastItemId = this.items[this.items.length - 1].id; - - this.pageByPostId.set(firstItemId, currentPage); - this.pageByPostId.set(lastItemId, currentPage); - - this.postIdsToObserve.push(firstItemId, lastItemId); - this.observeNewItems(); - })); } ngOnDestroy() { @@ -74,22 +138,24 @@ export class NbInfiniteNewsListComponent extends ListBase implements AfterViewIn this.intersectionObserver.disconnect(); } - loadNext() { - const { page, items } = this.addPage(); - const firstAndLastItemsIds = [items[0].id, items[items.length - 1].id]; - for (const id of firstAndLastItemsIds) { - this.pageByPostId.set(id, page); + loadPrev() { + if (this.previousPageToLoad < 1) { + return; } - this.postIdsToObserve.push(...firstAndLastItemsIds); + + const postsPlaceholders = this.emulateLoading(this.previousPageToLoad); + this.news.unshift(...postsPlaceholders); + this.previousPageToLoad--; } - newItem(index: number) { - return { - index, - id: index, - title: `Post ${index + 1}`, - text: `Post placeholder text.`, - }; + loadNext() { + if (this.nextPageToLoad > this.maxPage) { + return; + } + + const postsPlaceholders = this.emulateLoading(this.nextPageToLoad); + this.news.push(...postsPlaceholders); + this.nextPageToLoad++; } private updateUrl(entries: IntersectionObserverEntry[]) { @@ -116,12 +182,15 @@ export class NbInfiniteNewsListComponent extends ListBase implements AfterViewIn } }); - // TODO - // navigate only if not already on current page + const currentPage = this.pageByPostId.get(mostVisibleItemId); + if (currentPage === this.visiblePage) { + return; + } + this.router.navigate( ['.'], { - queryParams: { page: this.pageByPostId.get(mostVisibleItemId) }, + queryParams: { page: currentPage }, replaceUrl: true, relativeTo: this.route, }, @@ -151,4 +220,46 @@ export class NbInfiniteNewsListComponent extends ListBase implements AfterViewIn private getPostId(element: Element): number { return Number.parseInt(element.getAttribute('data-post-id'), 10); } + + private emulateLoading(page: number): TrackingIdWithPost[] { + const pageIndex = page - 1; + const firstItemIndex = pageIndex * this.pageSize; + const lastItemIndex = firstItemIndex + this.pageSize - 1; + const idsWithPosts: TrackingIdWithPost[] = []; + + for (let i = firstItemIndex; i <= lastItemIndex; i++) { + const idWithPost = new TrackingIdWithPost(); + idWithPost.id = i; + idWithPost.isLoading = true; + idsWithPosts.push(idWithPost); + } + + const firstAndLastPostsIds = [idsWithPosts[0].id, idsWithPosts[idsWithPosts.length - 1].id]; + for (const id of firstAndLastPostsIds) { + this.pageByPostId.set(id, page); + } + this.postIdsToObserve.push(...firstAndLastPostsIds); + + setTimeout( + () => idsWithPosts.forEach(p => { + p.post = this.newPost(p.id + 1); + p.isLoading = false; + }), + this.pageLoadingDelay, + ); + + return idsWithPosts; + } + + private newPost(id: number): Post { + const post = new Post(); + post.id = id; + post.title = `Post ${id}`; + post.text = 'Post placeholder text.'; + return post; + } + + postUniqueId(_, { id }: TrackingIdWithPost) { + return id; + } } diff --git a/src/playground/infinite-list/list-base.ts b/src/playground/infinite-list/list-base.ts index c932fca2a7..d037db4df5 100644 --- a/src/playground/infinite-list/list-base.ts +++ b/src/playground/infinite-list/list-base.ts @@ -1,4 +1,4 @@ -export abstract class ListBase { +export class ListBase { protected timeout = 1000 private page = 1; @@ -17,11 +17,7 @@ export abstract class ListBase { const newItems = []; for (let i = firstItemIndex; i < lastItemIndex; i++) { - if (this.newItem) { - newItems.push({ isPlaceholder: true, ...this.newItem(i) }); - } else { - newItems.push({ index: i, humanNumber: i + 1, isPlaceholder: true }); - } + newItems.push({ index: i, humanNumber: i + 1, isPlaceholder: true }); } setTimeout( @@ -43,6 +39,4 @@ export abstract class ListBase { return { page, items }; } - - abstract newItem(index: number); } diff --git a/src/playground/playground-routing.module.ts b/src/playground/playground-routing.module.ts index 42b537cc37..e6e2b9c8d2 100644 --- a/src/playground/playground-routing.module.ts +++ b/src/playground/playground-routing.module.ts @@ -138,7 +138,7 @@ import { NbInfiniteListWindowShowcaseComponent } from './infinite-list/infinite- import { NbUsersListShowcaseComponent } from './list/users-list-showcase.component'; import { NbCardWithoutBodyComponent } from './card/card-without-body.component'; import { NbInfiniteImagesListComponent } from './infinite-list/infinite-images-list.component'; -import { NbInfiniteNewsListComponent } from './infinite-list/infinite-news-list.component'; +import { NbInfiniteNewsListComponent, NbNewsPostComponent } from './infinite-list/infinite-news-list.component'; export const routes: Routes = [ { @@ -600,6 +600,12 @@ export const routes: Routes = [ { path: 'infinite-news-list.component', component: NbInfiniteNewsListComponent, + children: [ + { + path: 'post/{id}', + component: NbNewsPostComponent, + }, + ], }, ], }, diff --git a/src/playground/playground.module.ts b/src/playground/playground.module.ts index e78fc9214d..d0c9a7788f 100644 --- a/src/playground/playground.module.ts +++ b/src/playground/playground.module.ts @@ -170,7 +170,11 @@ import { NbUsersListShowcaseComponent } from './list/users-list-showcase.compone import { NbCardWithoutBodyComponent } from './card/card-without-body.component'; import { NbInfiniteImagesListComponent } from './infinite-list/infinite-images-list.component'; import { NbRandomSvgComponent } from './infinite-list/random-svg.component'; -import { NbInfiniteNewsListComponent } from './infinite-list/infinite-news-list.component'; +import { + NbInfiniteNewsListComponent, + NbNewsPostComponent, + NbNewsPostPlaceholderComponent, +} from './infinite-list/infinite-news-list.component'; export const NB_MODULES = [ NbCardModule, @@ -333,6 +337,8 @@ export const NB_EXAMPLE_COMPONENTS = [ NbInfiniteImagesListComponent, NbRandomSvgComponent, NbInfiniteNewsListComponent, + NbNewsPostComponent, + NbNewsPostPlaceholderComponent, ]; From 96a2cbaabf1d91cee39fab2e0b5a1fa8c1f6f85c Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Sat, 7 Jul 2018 17:36:23 +0300 Subject: [PATCH 051/129] refactor(id-with-post): convert isLoading to getter --- .../infinite-list/infinite-news-list.component.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/playground/infinite-list/infinite-news-list.component.ts b/src/playground/infinite-list/infinite-news-list.component.ts index ad616606be..b9fd6b0456 100644 --- a/src/playground/infinite-list/infinite-news-list.component.ts +++ b/src/playground/infinite-list/infinite-news-list.component.ts @@ -14,8 +14,11 @@ import { takeWhile, take } from 'rxjs/operators'; class TrackingIdWithPost { id: number; - isLoading: boolean; post: Post; + + get isLoading(): boolean { + return !!this.post; + }; } export class Post { @@ -230,7 +233,6 @@ export class NbInfiniteNewsListComponent implements OnInit, AfterViewInit, OnDes for (let i = firstItemIndex; i <= lastItemIndex; i++) { const idWithPost = new TrackingIdWithPost(); idWithPost.id = i; - idWithPost.isLoading = true; idsWithPosts.push(idWithPost); } @@ -243,7 +245,6 @@ export class NbInfiniteNewsListComponent implements OnInit, AfterViewInit, OnDes setTimeout( () => idsWithPosts.forEach(p => { p.post = this.newPost(p.id + 1); - p.isLoading = false; }), this.pageLoadingDelay, ); From 53b666b132af01d2b5d414a207b988e15092a90a Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Mon, 9 Jul 2018 13:48:02 +0300 Subject: [PATCH 052/129] style(infinite-list): add semicolon --- .../components/list/infinite-list/infinite-list.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts index 9c89039d27..89648238a0 100644 --- a/src/framework/theme/components/list/infinite-list/infinite-list.component.ts +++ b/src/framework/theme/components/list/infinite-list/infinite-list.component.ts @@ -112,7 +112,6 @@ export class NbInfiniteListComponent implements AfterViewChecked, OnDestroy { @ContentChild(NbDisableAutoLoadButtonDirective) disableAutoLoadButton: NbDisableAutoLoadButtonDirective; @ContentChild(NbLoadMoreButtonDirective) loadMoreButton: NbLoadMoreButtonDirective; - private disableClickSubscription: Subscription; private loadMoreClickSubscription: Subscription; @@ -135,7 +134,7 @@ export class NbInfiniteListComponent implements AfterViewChecked, OnDestroy { if (this.loadMoreButton) { this.loadMoreClickSubscription = this.loadMoreButton.click .pipe(takeWhile(() => this.alive)) - .subscribe(() => this.emitLoadNext()) + .subscribe(() => this.emitLoadNext()); } this.changeDetection.detectChanges(); From 4b1e274198c8bdd13b5fc4b4cc228f815877ff80 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Mon, 9 Jul 2018 14:08:38 +0300 Subject: [PATCH 053/129] docs(card): change spelling --- src/framework/theme/components/card/card.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/framework/theme/components/card/card.component.ts b/src/framework/theme/components/card/card.component.ts index 7af1a3e43e..176b68f1c1 100644 --- a/src/framework/theme/components/card/card.component.ts +++ b/src/framework/theme/components/card/card.component.ts @@ -73,8 +73,8 @@ export class NbCardFooterComponent { * Card with header and footer: * @stacked-example(With Header & Footer, card/card-full.component) * - * Most of the time main card content goes to `nb-card-body`, so it styled and aligned according to header and footer. - * But in case you need a higher level of control, you can pass contend directly to `nb-card`, + * Most of the time main card content goes to `nb-card-body`, so it styled and aligned accordingly to header and footer. + * In case you need a higher level of control, you can pass contend directly to `nb-card`, * so `nb-card-body` stylings will not be applied. * * Consider an example with `nb-list` component: From 4cd478498be321e4d0db756a5c5429bee17ba6da Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Mon, 9 Jul 2018 14:18:53 +0300 Subject: [PATCH 054/129] refactor(scroll-directive): dispatch scroll event on window --- .../theme/components/layout/layout.component.ts | 2 +- .../infinite-list/scroll-threshold.directive.spec.ts | 10 +++++----- .../list/infinite-list/scroll-threshold.directive.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/framework/theme/components/layout/layout.component.ts b/src/framework/theme/components/layout/layout.component.ts index e62c4b13a0..7c689375e1 100644 --- a/src/framework/theme/components/layout/layout.component.ts +++ b/src/framework/theme/components/layout/layout.component.ts @@ -427,7 +427,7 @@ export class NbLayoutComponent implements AfterViewInit, OnInit, OnDestroy { 'nbscroll', { detail: { originalEvent: $event, ...this.getScrollInfo() } }, ); - this.document.dispatchEvent(event); + this.window.dispatchEvent(event); } private initScrollTop() { diff --git a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts index 5bba97eebe..74693dc2de 100644 --- a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts +++ b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.spec.ts @@ -80,7 +80,7 @@ describe('Directive: NbScrollDirective', () => { it('should listen to nbscroll event', () => { const layoutScrollHandlerSpy = spyOn(scrollDirective, 'layoutScroll'); - document.dispatchEvent(nbscrollEvent()); + window.dispatchEvent(nbscrollEvent()); expect(layoutScrollHandlerSpy).toHaveBeenCalledTimes(1); }); @@ -93,7 +93,7 @@ describe('Directive: NbScrollDirective', () => { it('should ignore nbscroll event when listen to element scroll', () => { const checkPositionSpy = spyOn(scrollDirective, 'checkPosition'); - document.dispatchEvent(nbscrollEvent()); + window.dispatchEvent(nbscrollEvent()); expect(checkPositionSpy).toHaveBeenCalledTimes(0); scrollingElement.nativeElement.dispatchEvent(new Event('scroll')); @@ -106,7 +106,7 @@ describe('Directive: NbScrollDirective', () => { componentInstance.listenWindowScroll = true; fixture.detectChanges(); - document.dispatchEvent(nbscrollEvent()); + window.dispatchEvent(nbscrollEvent()); expect(checkPositionSpy).toHaveBeenCalledTimes(1); scrollingElement.nativeElement.dispatchEvent(new Event('scroll')); @@ -140,12 +140,12 @@ describe('Directive: NbScrollDirective', () => { const reporterHeight = 1000; const positionUnderThreshold = CONTENT_HEIGHT - THRESHOLD - reporterHeight; documentElement.scrollTop = positionUnderThreshold; - document.dispatchEvent(nbscrollEvent(documentElement)); + window.dispatchEvent(nbscrollEvent(documentElement)); expect(tresholdReachedSpy).toHaveBeenCalledTimes(0); const positionBelowThreshold = CONTENT_HEIGHT - (THRESHOLD / 2); documentElement.scrollTop = positionBelowThreshold; - document.dispatchEvent(nbscrollEvent(documentElement)); + window.dispatchEvent(nbscrollEvent(documentElement)); expect(tresholdReachedSpy).toHaveBeenCalledTimes(1); }); diff --git a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts index aee063b8f4..4b0e446e48 100644 --- a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts +++ b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts @@ -38,7 +38,7 @@ export class NbScrollThresholdDirective { @Output() topThresholdReached = new EventEmitter(); - @HostListener('document:nbscroll', ['$event']) + @HostListener('window:nbscroll', ['$event']) layoutScroll($event) { if (this.listenWindowScroll) { const { scrollHeight, scrollTop } = $event.detail; From 3283305538a7426ad92d94fbac00cc487f380db5 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Mon, 9 Jul 2018 14:29:49 +0300 Subject: [PATCH 055/129] docs(nb-list): add styles section --- src/framework/theme/components/list/list.component.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/framework/theme/components/list/list.component.ts b/src/framework/theme/components/list/list.component.ts index 735db2d04d..d56a062b1a 100644 --- a/src/framework/theme/components/list/list.component.ts +++ b/src/framework/theme/components/list/list.component.ts @@ -10,6 +10,11 @@ import { Component, Input, HostBinding } from '@angular/core'; * * List of users: * @stacked-example(Users list, list/users-list-showcase.component) + * + * @styles + * + * list-item-border-color: + * list-item-padding: */ @Component({ selector: 'nb-list', From a8bee45e93242f2baae61759f70fd51cdbb998f5 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Mon, 9 Jul 2018 16:18:03 +0300 Subject: [PATCH 056/129] feat(nb-scroll): emit events only if scrolled in according direction --- .../scroll-threshold.directive.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts index 4b0e446e48..b2dfe7e0c2 100644 --- a/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts +++ b/src/framework/theme/components/list/infinite-list/scroll-threshold.directive.ts @@ -1,5 +1,10 @@ import { Directive, Input, HostListener, ElementRef, EventEmitter, Output } from '@angular/core'; +export enum NbScrollDirection { + UP, + DOWN, +} + /** * Scroll Threshold Directive * @@ -11,6 +16,8 @@ import { Directive, Input, HostListener, ElementRef, EventEmitter, Output } from }) export class NbScrollThresholdDirective { + private lastScrollPosition = 0; + /** * Threshold value. * Please ensure that its height is greater than element height, otherwise threshold will never be reached. @@ -43,6 +50,7 @@ export class NbScrollThresholdDirective { if (this.listenWindowScroll) { const { scrollHeight, scrollTop } = $event.detail; this.checkPosition(scrollHeight, scrollTop); + this.lastScrollPosition = scrollTop; } } @@ -51,17 +59,23 @@ export class NbScrollThresholdDirective { if (!this.listenWindowScroll) { const { scrollTop, scrollHeight } = this.elementRef.nativeElement; this.checkPosition(scrollHeight, scrollTop); + this.lastScrollPosition = scrollTop; } } constructor(private elementRef: ElementRef) {} checkPosition(scrollHeight: number, scrollTop: number) { - if (scrollHeight - scrollTop <= this.threshold) { + const scrollDelta = scrollTop - this.lastScrollPosition; + const scrollDirection = scrollDelta > 0 + ? NbScrollDirection.DOWN + : NbScrollDirection.UP; + + if (scrollDirection === NbScrollDirection.DOWN && scrollHeight - scrollTop <= this.threshold) { this.bottomThresholdReached.emit(); } - if (scrollTop <= this.threshold) { + if (scrollDirection === NbScrollDirection.UP && scrollTop <= this.threshold) { this.topThresholdReached.emit(); } } From a65e87550436b8bd77ff63be7797cc519d5f73f6 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Mon, 9 Jul 2018 16:21:33 +0300 Subject: [PATCH 057/129] docs(news-infinite-list): add news page component --- .../infinite-news-list.component.ts | 47 ++++++++++++------- src/playground/playground-routing.module.ts | 11 +++-- src/playground/playground.module.ts | 2 + 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/playground/infinite-list/infinite-news-list.component.ts b/src/playground/infinite-list/infinite-news-list.component.ts index b9fd6b0456..8361499373 100644 --- a/src/playground/infinite-list/infinite-news-list.component.ts +++ b/src/playground/infinite-list/infinite-news-list.component.ts @@ -25,7 +25,32 @@ export class Post { id: number; title: string; text: string; - link: string; +} + +function newPost(id: number): Post { + const post = new Post(); + post.id = id; + post.title = `Post ${id}`; + post.text = 'Post placeholder text.'; + return post; +} + +@Component({ + selector: 'nb-post-page', + template: ``, +}) +export class NbPostPageComponent implements OnInit { + post: Post; + + constructor(private route: ActivatedRoute) {} + + ngOnInit() { + this.route.params + .pipe(take(1)) + .subscribe(params => { + this.post = newPost(params.id); + }); + } } @Component({ @@ -55,7 +80,7 @@ export class NbNewsPostPlaceholderComponent {}

{{post.title}}

{{post.text}}

- Read full article + Read full article `, }) @@ -75,10 +100,8 @@ export class NbNewsPostComponent { - - - - + +
`, @@ -243,23 +266,13 @@ export class NbInfiniteNewsListComponent implements OnInit, AfterViewInit, OnDes this.postIdsToObserve.push(...firstAndLastPostsIds); setTimeout( - () => idsWithPosts.forEach(p => { - p.post = this.newPost(p.id + 1); - }), + () => idsWithPosts.forEach(p => p.post = newPost(p.id + 1)), this.pageLoadingDelay, ); return idsWithPosts; } - private newPost(id: number): Post { - const post = new Post(); - post.id = id; - post.title = `Post ${id}`; - post.text = 'Post placeholder text.'; - return post; - } - postUniqueId(_, { id }: TrackingIdWithPost) { return id; } diff --git a/src/playground/playground-routing.module.ts b/src/playground/playground-routing.module.ts index e6e2b9c8d2..b8d53fd9be 100644 --- a/src/playground/playground-routing.module.ts +++ b/src/playground/playground-routing.module.ts @@ -138,7 +138,7 @@ import { NbInfiniteListWindowShowcaseComponent } from './infinite-list/infinite- import { NbUsersListShowcaseComponent } from './list/users-list-showcase.component'; import { NbCardWithoutBodyComponent } from './card/card-without-body.component'; import { NbInfiniteImagesListComponent } from './infinite-list/infinite-images-list.component'; -import { NbInfiniteNewsListComponent, NbNewsPostComponent } from './infinite-list/infinite-news-list.component'; +import { NbInfiniteNewsListComponent, NbPostPageComponent } from './infinite-list/infinite-news-list.component'; export const routes: Routes = [ { @@ -599,11 +599,14 @@ export const routes: Routes = [ }, { path: 'infinite-news-list.component', - component: NbInfiniteNewsListComponent, children: [ { - path: 'post/{id}', - component: NbNewsPostComponent, + path: 'news', + component: NbInfiniteNewsListComponent, + }, + { + path: 'post/:id', + component: NbPostPageComponent, }, ], }, diff --git a/src/playground/playground.module.ts b/src/playground/playground.module.ts index d0c9a7788f..d88b64021f 100644 --- a/src/playground/playground.module.ts +++ b/src/playground/playground.module.ts @@ -174,6 +174,7 @@ import { NbInfiniteNewsListComponent, NbNewsPostComponent, NbNewsPostPlaceholderComponent, + NbPostPageComponent, } from './infinite-list/infinite-news-list.component'; export const NB_MODULES = [ @@ -339,6 +340,7 @@ export const NB_EXAMPLE_COMPONENTS = [ NbInfiniteNewsListComponent, NbNewsPostComponent, NbNewsPostPlaceholderComponent, + NbPostPageComponent, ]; From 8cec13068203f4ac7b0f46d4face8a6359d844e3 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Tue, 10 Jul 2018 18:41:40 +0300 Subject: [PATCH 058/129] docs(nb-infinite-list): simplify news example --- docs/assets/data/news.json | 194 ++++++++++++ .../infinite-list/infinite-list.component.ts | 2 + .../infinite-news-list.component.scss | 9 +- .../infinite-news-list.component.ts | 286 +++--------------- src/playground/infinite-list/news.service.ts | 30 ++ src/playground/playground-routing.module.ts | 13 +- src/playground/playground.module.ts | 12 +- 7 files changed, 283 insertions(+), 263 deletions(-) create mode 100644 docs/assets/data/news.json create mode 100644 src/playground/infinite-list/news.service.ts diff --git a/docs/assets/data/news.json b/docs/assets/data/news.json new file mode 100644 index 0000000000..58ae830a5c --- /dev/null +++ b/docs/assets/data/news.json @@ -0,0 +1,194 @@ +{ + "news": [ + { + "title": "Moving Design Beyond Pictures", + "link": "https://blog.angular.io/moving-design-beyond-pictures-1509c315f94e?source=rss----447683c3d9a3---4", + "guid": { + "@isPermaLink": "false", + "#text": "https://medium.com/p/1509c315f94e" + }, + "category": [ + "prototyping", + "angular", + "ux-design", + "design-process", + "web-development" + ], + "creator": "Blair Metcalf", + "pubDate": "Wed, 06 Jun 2018 22:26:30 GMT", + "updated": "2018-06-06T22:26:30.766Z", + "encoded": "

Quickly create real Angular prototypes without writing code.<\/em><\/p>

Designers put a lot of time into designing components and features for their products, however there often still remains a gap between a designer’s vision and a developer’s reality.<\/p>

“An author can write a book. A musician can compose a song, an animator can compose a short, a painter can compose a painting. But most dynamic artists cannot realize their own creations, and this breaks my heart.”<\/em> — Bret Victor in Dynamic Pictures<\/a><\/p>

We want to change that… soon<\/h3>

We are a team of UX Engineers at Google that is partnering with Angular on their Angular for Designers initiative<\/a>. While a public launch is still months away, we have begun building a WYSIWYG prototyping tool that allows designers to use Angular components to create realistic, data-driven prototypes.<\/p>

Many articles have been written about the unresponsive nature of current design tools<\/a> that lack real data<\/a>; designers are not able to work in a medium that feels like their final product. Instead, designers are limited to creating static mockups that don’t convey experiences well and are often on their own if they want to build a more interactive prototype.<\/p>

Designers are ready for a dynamic, data-driven way of working and the web is capable of delivering the tools they want. The tools we use shape our thoughts and define the limit of what’s possible. Change the tools and change our thinking.<\/p>

\"\"
Quickly create a new page from a template, edit, and export code.<\/em><\/figcaption><\/figure>

Build together. Build to scale.<\/h3>

We envision teams with more efficient communication between design and development. Because of the different mediums that designers and developers work in (mockups vs. code) communication does not happen naturally. However, in a world where a designer is able to assemble a page using the same components as a developer, they can now speak the same language.<\/p>

Companies have invested in componentizing their design systems (think Material<\/a> or Clarity<\/a>), and there is now a wealth of components that are readily available. Making all of these components accessible to designers will empower them to work from a single source of truth with their developers. This will greatly help improve consistency and quality as a product or company scales.<\/p>

A day in the life of the prototyping tool<\/h3>

Imagine Dan the designer has a prototype where he has used a table component across 50 different screens. After getting user feedback on the design, Dan decides he wants to allow users to optionally hide columns in a table. Dan talks to Emma the engineer who is able to add the hide column feature to the component. Once Emma pushes her update, Dan opens his project and sees that the new feature is working in the table component across all 50 of his screens!<\/p>

After Dan has finished his design, he sends a link to Stacey the stakeholder. She opens a preview of the prototype and clicks through each stage of the workflow. Because the prototype is built with real components and real data, she is able to get a very good sense of what the final outcome will be and gives an enthusiastic approval. Emma opens Dan’s project and is able to export an Angular template for each page in the prototype to get a jump-start on the front-end work that will be needed.<\/p>

\"\"
Use spreadsheets to quickly populate your components with real data.<\/em><\/figcaption><\/figure>

Let’s make it better, together.<\/h3>

We aren’t ready to share the tool yet, but we would love to gather your thoughts on the usefulness of this idea. If you have a few minutes, please fill out this survey<\/a>, so we can improve it.<\/p>

Join our growing team!<\/h3>

This is just the beginning and our team is growing quickly! We are hiring talented engineers to make a big impact on the lives of design system creators and users. If you are looking for a challenge and want to help us make this a reality, we would love to talk with you! Join our team!<\/a><\/p>

— <\/em>Tim Sawyer<\/em><\/a> and Blair Metcalf<\/em><\/p>


Moving Design Beyond Pictures<\/a> was originally published in Angular Blog<\/a> on Medium, where people are continuing the conversation by highlighting and responding to this story.<\/p>" + }, + { + "title": "Version 6 of Angular Now Available", + "link": "https://blog.angular.io/version-6-of-angular-now-available-cc56b0efa7a4?source=rss----447683c3d9a3---4", + "guid": { + "@isPermaLink": "false", + "#text": "https://medium.com/p/cc56b0efa7a4" + }, + "category": [ + "web-development", + "angular", + "release-notes", + "front-end-development", + "software-development" + ], + "creator": "Stephen Fluin", + "pubDate": "Thu, 03 May 2018 22:04:36 GMT", + "updated": "2018-07-02T17:10:44.908Z", + "encoded": "

The 6.0.0 release of Angular is here! This is a major release focused less on the underlying framework, and more on the toolchain and on making it easier to move quickly with Angular in the future.<\/p>

\"\"
a photo by Manu Murthy of the Angular Team<\/figcaption><\/figure>

As a part of this release, we are synchronizing the major versions going forward for the framework packages (@angular/core, @angular/common, @angular/compiler, etc), the Angular CLI, and Angular Material + CDK. All are releasing as 6.0.0 today. We made this change to clarify cross compatibility. The minor and patch releases for these projects will be issued based on the project’s needs.<\/p>

See the full list of changes in our changelogs: framework<\/a>, material+cdk<\/a>, cli<\/a>.<\/p>

ng update<\/h3>

ng update <package> is a new CLI command that analyzes your package.json and uses its knowledge of Angular to recommend updates to your application. To see it in action, check out our update guide<\/a>.<\/p>

Not only will ng update help you adopt the right version of dependencies, and keep your dependencies in sync, but 3rd parties can provide update scripts using schematics<\/a>. If one of your dependencies provides an ng update schematic, they can automatically update your code when they need to make breaking changes!<\/p>

ng update will not replace your package manager, but uses npm or yarn under the hood to manage dependencies. In addition to updating dependencies and peer dependencies, ng update will apply needed transforms to your project.<\/p>

For example, the command ng update @angular/core will update all of the Angular framework packages as well as RxJS and TypeScript, and will run any schematics available on these packages to keep you up to date. As part of this one command, we’ll automatically install rxjs-compat into your application to make the adoption of RxJS v6 smoother.<\/p>

We expect to see many more libraries and packages add ng update schematics over the coming months, and have already heard from enterprise component library teams that are planning to use ng update to push through important changes in an automated way to save their developers time.<\/p>

Learn more about how the <\/a>ng update command works<\/a>. To get started creating your own ng update schematic, take a look at the entry in the package.json of rxjs<\/a> and its associated collection.json<\/a>.<\/p>

ng add<\/h3>

Another new CLI command ng add <package> makes adding new capabilities to your project easy. ng add will use your package manager to download new dependencies and invoke an installation script (implemented as a schematic) which can update your project with configuration changes, add additional dependencies (e.g. polyfills), or scaffold package-specific initialization code.<\/p>

Try out some of the following on your fresh ng new application:<\/p>