diff --git a/components/affix/affix.spec.ts b/components/affix/affix.spec.ts index f173e801fff..6606e04b70f 100644 --- a/components/affix/affix.spec.ts +++ b/components/affix/affix.spec.ts @@ -44,6 +44,8 @@ describe('affix', () => { new Event('pageshow'), new Event('load') ]; + const height = 100; + const width = 100; beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ @@ -109,13 +111,13 @@ describe('affix', () => { describe('when element gets shifted horizontally', () => { it('adjusts left position accordingly to maintain natural position', fakeAsync(() => { setupInitialState(); - componentObject.offsetTo(componentObject.elementRef(), { top: startOffset, left: 10, width: 100, height: 100 }); + componentObject.offsetTo(componentObject.elementRef(), { top: startOffset, left: 10, width, height }); emitScroll(window, defaultOffsetTop + startOffset + 1); expect(componentObject.wrap().offsetLeft).toBe(10); emitScroll(window, defaultOffsetTop + startOffset - 1); - componentObject.offsetTo(componentObject.elementRef(), { top: startOffset, left: 100, width: 100, height: 100 }); + componentObject.offsetTo(componentObject.elementRef(), { top: startOffset, left: 100, width, height }); emitScroll(window, defaultOffsetTop + startOffset + 1); expect(componentObject.wrap().offsetLeft).toBe(100); @@ -288,6 +290,15 @@ describe('affix', () => { fixture.detectChanges(); expect(component.updatePosition).toHaveBeenCalled(); }); + + it('should be a string value', () => { + spyOn(component, 'updatePosition'); + expect(component.updatePosition).not.toHaveBeenCalled(); + fixture.detectChanges(); + context.fakeTarget = '#target'; + fixture.detectChanges(); + expect(component.updatePosition).toHaveBeenCalled(); + }); }); describe('(nzChange)', () => { @@ -318,11 +329,12 @@ describe('affix', () => { })); }); - it('should adjust the width when resize', fakeAsync(() => { + it('should adjust placeholder width when resize', fakeAsync(() => { const offsetTop = 150; context.newOffset = offsetTop; setupInitialState({ offsetTop: offsetTop + 1 }); emitScroll(window, 2); + expect(componentObject.elementRef().style.width).toBe(`${width}px`); componentObject.offsetYTo(componentObject.elementRef(), offsetTop + 2); tick(20); fixture.detectChanges(); @@ -330,7 +342,7 @@ describe('affix', () => { tick(20); fixture.detectChanges(); - expect(componentObject.wrap().offsetTop).toBe(offsetTop); + expect(componentObject.elementRef().style.width).toBe(``); discardPeriodicTasks(); })); @@ -352,7 +364,7 @@ describe('affix', () => { } getOffset(el: Element): Offset { - return this.offsets[el.id] || { top: 10, left: 0, height: 100, width: 100 }; + return this.offsets[el.id] || { top: 10, left: 0, height, width }; } emitEvent(el: Element | Window, event: Event): void { @@ -368,8 +380,8 @@ describe('affix', () => { this.offsets[this.getKey(el)] = { top: offset.top, left: offset.left, - height: 100, - width: 100 + height, + width }; } @@ -379,8 +391,8 @@ describe('affix', () => { { top: offsetTop, left: 0, - height: 100, - width: 100 + height, + width } ); } @@ -486,14 +498,12 @@ describe('affix-extra', () => {
- `, - styleUrls: [ './style/index.less' ], - encapsulation: ViewEncapsulation.None + ` }) class TestAffixComponent { @ViewChild(NzAffixComponent) nzAffixComponent: NzAffixComponent; - fakeTarget: Element | Window = null; + fakeTarget: string | Element | Window = null; newOffset: {}; newOffsetBottom: {}; } diff --git a/components/affix/doc/index.en-US.md b/components/affix/doc/index.en-US.md index 8125b25ac3f..d0b11d41468 100755 --- a/components/affix/doc/index.en-US.md +++ b/components/affix/doc/index.en-US.md @@ -20,7 +20,7 @@ Please note that Affix should not cover other content on the page, especially wh | -------- | ----------- | ---- | ------- | | `[nzOffsetBottom]` | Pixels to offset from bottom when calculating position of scroll | number | - | | `[nzOffsetTop]` | Pixels to offset from top when calculating position of scroll | number | 0 | -| `[nzTarget]` | specifies the scrollable area dom node | `HTMLElement` | `window` | +| `[nzTarget]` | specifies the scrollable area dom node | `string, HTMLElement` | `window` | | `(nzChange)` | Callback for when affix state is changed | `EventEmitter` | - | **Note:** Children of `nz-affix` can not be `position: absolute`, but you can set `nz-affix` as `position: absolute`: diff --git a/components/affix/doc/index.zh-CN.md b/components/affix/doc/index.zh-CN.md index c1989e9ba8f..d01518a3862 100755 --- a/components/affix/doc/index.zh-CN.md +++ b/components/affix/doc/index.zh-CN.md @@ -21,7 +21,7 @@ title: Affix | --- | --- | --- | --- | | `[nzOffsetBottom]` | 距离窗口底部达到指定偏移量后触发 | number | | | `[nzOffsetTop]` | 距离窗口顶部达到指定偏移量后触发 | number | | -| `[nzTarget]` | 设置 `nz-affix` 需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数 | `HTMLElement` | `window` | +| `[nzTarget]` | 设置 `nz-affix` 需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数 | `string, HTMLElement` | `window` | | `(nzChange)` | 固定状态改变时触发的回调函数 | `EventEmitter` | 无 | **注意:**`nz-affix` 内的元素不要使用绝对定位,如需要绝对定位的效果,可以直接设置 `nz-affix` 为绝对定位: diff --git a/components/affix/nz-affix.component.ts b/components/affix/nz-affix.component.ts index c17e378f605..4b579cf62cc 100644 --- a/components/affix/nz-affix.component.ts +++ b/components/affix/nz-affix.component.ts @@ -1,15 +1,17 @@ -// tslint:disable:no-any +import { DOCUMENT } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, + Inject, Input, OnDestroy, OnInit, Output, - ViewChild + ViewChild, + ViewEncapsulation } from '@angular/core'; import { NzScrollService } from '../core/scroll/nz-scroll.service'; @@ -21,18 +23,19 @@ import { throttleByAnimationFrameDecorator } from '../core/util/throttleByAnimat selector : 'nz-affix', templateUrl : './nz-affix.component.html', changeDetection: ChangeDetectionStrategy.OnPush, - styles : [ - `:host { + styles : [ ` + nz-affix { display: block; - }` - ] + } + ` ], + encapsulation : ViewEncapsulation.None }) export class NzAffixComponent implements OnInit, OnDestroy { @Input() - set nzTarget(value: Element | Window) { + set nzTarget(value: string | Element | Window) { this.clearEventListeners(); - this._target = value || window; + this._target = typeof value === 'string' ? this.doc.querySelector(value) : value || window; this.setTargetEventListeners(); this.updatePosition({}); } @@ -60,10 +63,11 @@ export class NzAffixComponent implements OnInit, OnDestroy { @Output() readonly nzChange: EventEmitter = new EventEmitter(); - constructor(private scrollSrv: NzScrollService, private _el: ElementRef, private cd: ChangeDetectorRef) { + // tslint:disable-next-line:no-any + constructor(private scrollSrv: NzScrollService, private _el: ElementRef, @Inject(DOCUMENT) private doc: any, private cd: ChangeDetectorRef) { } - private timeout: any; + private timeout; private events = [ 'resize', 'scroll', @@ -73,8 +77,8 @@ export class NzAffixComponent implements OnInit, OnDestroy { 'pageshow', 'load' ]; - private affixStyle: any; - private placeholderStyle: any; + private affixStyle; + private placeholderStyle; @ViewChild('wrap') private wrap: ElementRef; @@ -94,6 +98,7 @@ export class NzAffixComponent implements OnInit, OnDestroy { ngOnDestroy(): void { this.clearEventListeners(); clearTimeout(this.timeout); + // tslint:disable-next-line:no-any (this.updatePosition as any).cancel(); } @@ -109,7 +114,7 @@ export class NzAffixComponent implements OnInit, OnDestroy { const scrollTop = this.scrollSrv.getScroll(target, true); const scrollLeft = this.scrollSrv.getScroll(target, false); - const docElem = window.document.body; + const docElem = this.doc.body; const clientTop = docElem.clientTop || 0; const clientLeft = docElem.clientLeft || 0; @@ -121,7 +126,72 @@ export class NzAffixComponent implements OnInit, OnDestroy { }; } + private setTargetEventListeners(): void { + this.clearEventListeners(); + this.events.forEach((eventName: string) => { + this._target.addEventListener(eventName, this.updatePosition, false); + }); + } + + private clearEventListeners(): void { + this.events.forEach(eventName => { + this._target.removeEventListener(eventName, this.updatePosition, false); + }); + } + + private getTargetRect(target: Element | Window | null): ClientRect { + return target !== window ? + (target as HTMLElement).getBoundingClientRect() : + { top: 0, left: 0, bottom: 0 } as ClientRect; + } + + private genStyle(affixStyle: {}): string { + if (affixStyle == null) { + return ''; + } + return Object.keys(affixStyle).map(key => { + const val = affixStyle[ key ]; + return `${key}:${typeof val === 'string' ? val : val + 'px'}`; + }).join(';'); + } + + private setAffixStyle(e: Event, affixStyle: {}): void { + const originalAffixStyle = this.affixStyle; + const isWindow = this._target === window; + if (e.type === 'scroll' && originalAffixStyle && affixStyle && isWindow) { + return; + } + if (shallowEqual(originalAffixStyle, affixStyle)) { + return; + } + + const fixed = !!affixStyle; + const wrapEl = this.wrap.nativeElement as HTMLElement; + wrapEl.style.cssText = this.genStyle(affixStyle); + this.affixStyle = affixStyle; + const cls = 'ant-affix'; + if (fixed) { + wrapEl.classList.add(cls); + } else { + wrapEl.classList.remove(cls); + } + + if ((affixStyle && !originalAffixStyle) || (!affixStyle && originalAffixStyle)) { + this.nzChange.emit(fixed); + } + } + + private setPlaceholderStyle(placeholderStyle: {}): void { + const originalPlaceholderStyle = this.placeholderStyle; + if (shallowEqual(placeholderStyle, originalPlaceholderStyle)) { + return; + } + (this._el.nativeElement as HTMLElement).style.cssText = this.genStyle(placeholderStyle); + this.placeholderStyle = placeholderStyle; + } + @throttleByAnimationFrameDecorator() + // tslint:disable-next-line:no-any updatePosition(e: any): void { const targetNode = this._target; // Backwards support @@ -187,69 +257,4 @@ export class NzAffixComponent implements OnInit, OnDestroy { this.setPlaceholderStyle(null); } } - - private setTargetEventListeners(): void { - this.clearEventListeners(); - this.events.forEach((eventName: string) => { - this._target.addEventListener(eventName, this.updatePosition, false); - }); - } - - private clearEventListeners(): void { - this.events.forEach(eventName => { - this._target.removeEventListener(eventName, this.updatePosition, false); - }); - } - - private getTargetRect(target: Element | Window | null): ClientRect { - return target !== window ? - (target as HTMLElement).getBoundingClientRect() : - { top: 0, left: 0, bottom: 0 } as ClientRect; - } - - private genStyle(affixStyle: {}): string { - if (affixStyle == null) { - return ''; - } - return Object.keys(affixStyle).map(key => { - const val = affixStyle[ key ]; - return `${key}:${typeof val === 'string' ? val : val + 'px'}`; - }).join(';'); - } - - private setAffixStyle(e: any, affixStyle: {}): void { - const originalAffixStyle = this.affixStyle; - const isWindow = this._target === window; - if (e.type === 'scroll' && originalAffixStyle && affixStyle && isWindow) { - return; - } - if (shallowEqual(originalAffixStyle, affixStyle)) { - return; - } - - const fixed = !!affixStyle; - const wrapEl = this.wrap.nativeElement as HTMLElement; - wrapEl.style.cssText = this.genStyle(affixStyle); - this.affixStyle = affixStyle; - const cls = 'ant-affix'; - if (fixed) { - wrapEl.classList.add(cls); - } else { - wrapEl.classList.remove(cls); - } - - if ((affixStyle && !originalAffixStyle) || (!affixStyle && originalAffixStyle)) { - this.nzChange.emit(fixed); - } - } - - private setPlaceholderStyle(placeholderStyle: {}): void { - const originalPlaceholderStyle = this.placeholderStyle; - if (shallowEqual(placeholderStyle, originalPlaceholderStyle)) { - return; - } - (this._el.nativeElement as HTMLElement).style.cssText = this.genStyle(placeholderStyle); - this.placeholderStyle = placeholderStyle; - } - }