From 5c11a426ddb2b4a15405b879a3dcc44a875e6435 Mon Sep 17 00:00:00 2001 From: ParsaArvanehPA Date: Thu, 1 Feb 2024 22:52:18 +0330 Subject: [PATCH 01/15] fix(module:tabs): wrong cursor --- components/tabs/style/patch.less | 1 + 1 file changed, 1 insertion(+) diff --git a/components/tabs/style/patch.less b/components/tabs/style/patch.less index 4b2cdd47488..a3e00fdba0f 100644 --- a/components/tabs/style/patch.less +++ b/components/tabs/style/patch.less @@ -7,6 +7,7 @@ .ant-tabs-tab-btn { border: none; background-color: unset; + cursor: pointer; } .ant-tabs-tab a[nz-tab-link] { From a494c4fdcbf391ff321105eb0e1acf14d9386687 Mon Sep 17 00:00:00 2001 From: Parsa Arvaneh Date: Sun, 24 Mar 2024 23:29:30 +0330 Subject: [PATCH 02/15] feat(module:anchor): url fragment change on click - nzReplace --- components/anchor/anchor-link.component.ts | 5 ++- components/anchor/anchor.component.ts | 19 ++++++++- components/anchor/anchor.spec.ts | 40 ++++++++++++++++++- components/anchor/doc/index.en-US.md | 20 +++++----- components/anchor/doc/index.zh-CN.md | 12 +++--- .../site/template/demo-component.template.ts | 6 --- .../site/template/doc-component.template.ts | 8 +--- scripts/site/utils/generate-demo.js | 2 +- scripts/site/utils/generate-docs.js | 2 +- 9 files changed, 80 insertions(+), 34 deletions(-) diff --git a/components/anchor/anchor-link.component.ts b/components/anchor/anchor-link.component.ts index 5bd34547f92..a31ef29eeb8 100644 --- a/components/anchor/anchor-link.component.ts +++ b/components/anchor/anchor-link.component.ts @@ -57,6 +57,7 @@ import { NzAnchorComponent } from './anchor.component'; export class NzAnchorLinkComponent implements OnInit, OnDestroy { @Input() nzHref = '#'; @Input() nzTarget?: string; + @Input() nzReplace: boolean = false; titleStr: string | null = ''; titleTpl?: TemplateRef; @@ -99,11 +100,11 @@ export class NzAnchorLinkComponent implements OnInit, OnDestroy { this.renderer.removeClass(this.elementRef.nativeElement, 'ant-anchor-link-active'); } - goToClick(e: Event): void { + async goToClick(e: Event): Promise { e.preventDefault(); e.stopPropagation(); if (this.platform.isBrowser) { - this.anchorComp.handleScrollTo(this); + await this.anchorComp.handleScrollTo(this); } } diff --git a/components/anchor/anchor.component.ts b/components/anchor/anchor.component.ts index 75f69afd405..19d9c736944 100644 --- a/components/anchor/anchor.component.ts +++ b/components/anchor/anchor.component.ts @@ -23,6 +23,7 @@ import { ViewChild, ViewEncapsulation } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; import { fromEvent, Subject } from 'rxjs'; import { takeUntil, throttleTime } from 'rxjs/operators'; @@ -112,6 +113,7 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit, OnChanges { @Input() nzContainer?: string | HTMLElement; @Input() nzCurrentAnchor?: string; @Input() nzDirection: NzDirectionVHType = 'vertical'; + @Input() nzReplace: boolean = false; @Output() readonly nzClick = new EventEmitter(); @Output() readonly nzChange = new EventEmitter(); @@ -135,7 +137,9 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit, OnChanges { private cdr: ChangeDetectorRef, private platform: Platform, private zone: NgZone, - private renderer: Renderer2 + private renderer: Renderer2, + private router: Router, + private activatedRoute: ActivatedRoute ) {} registerLink(link: NzAnchorLinkComponent): void { @@ -255,7 +259,7 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit, OnChanges { } } - handleScrollTo(linkComp: NzAnchorLinkComponent): void { + async handleScrollTo(linkComp: NzAnchorLinkComponent): Promise { const el = this.doc.querySelector(linkComp.nzHref); if (!el) { return; @@ -273,6 +277,7 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit, OnChanges { } }); this.nzClick.emit(linkComp.nzHref); + await this.updateUrlFragment(linkComp.nzHref, linkComp.nzReplace); } ngOnChanges(changes: SimpleChanges): void { @@ -291,4 +296,14 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit, OnChanges { this.setActive(); } } + + async updateUrlFragment(nzHref: string, replace?: boolean): Promise { + let urlWithoutFragment = nzHref.split('#')[1] ?? nzHref; + + await this.router.navigate([], { + relativeTo: this.activatedRoute, + fragment: urlWithoutFragment, + replaceUrl: replace ? replace : this.nzReplace + }); + } } diff --git a/components/anchor/anchor.spec.ts b/components/anchor/anchor.spec.ts index 912d5513afb..cd207cc67e4 100644 --- a/components/anchor/anchor.spec.ts +++ b/components/anchor/anchor.spec.ts @@ -1,24 +1,37 @@ /* eslint-disable */ // eslint-disable -import { fakeAsync, tick, TestBed, ComponentFixture } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { Component, DebugElement, ViewChild } from '@angular/core'; import { By } from '@angular/platform-browser'; import { NzScrollService } from 'ng-zorro-antd/core/services'; import { NzAnchorModule } from './anchor.module'; import { NzAnchorComponent } from './anchor.component'; import { NzDirectionVHType } from 'ng-zorro-antd/core/types'; +import { ActivatedRoute, Router } from '@angular/router'; +import { of } from 'rxjs'; +import { RouterTestingModule } from '@angular/router/testing'; const throttleTime = 51; +const activatedRouteStub = { + paramMap: { + subscribe() { + return of(); + } + } +}; + describe('anchor', () => { let fixture: ComponentFixture; let dl: DebugElement; let context: TestComponent; let page: PageObject; let srv: NzScrollService; + let router: Router; beforeEach(() => { TestBed.configureTestingModule({ imports: [NzAnchorModule], + providers: [{ provide: ActivatedRoute, useValue: activatedRouteStub }, RouterTestingModule], declarations: [TestComponent] }); fixture = TestBed.createComponent(TestComponent); @@ -26,6 +39,7 @@ describe('anchor', () => { context = fixture.componentInstance; fixture.detectChanges(); page = new PageObject(); + router = TestBed.get(Router); spyOn(context, '_scroll'); spyOn(context, '_change'); srv = TestBed.inject(NzScrollService); @@ -121,6 +135,28 @@ describe('anchor', () => { done(); }, throttleTime); }); + + it('should call updateUrlFragment method when an anchor link is clicked', () => { + spyOn(context.comp, 'updateUrlFragment'); + const linkList = dl.queryAll(By.css('.ant-anchor-link-title')); + (linkList[0].nativeElement as HTMLLinkElement).click(); + fixture.detectChanges(); + expect(context.comp.updateUrlFragment).toHaveBeenCalled(); + }); + + it('should update fragment part of url when anchor link is clicked', fakeAsync(() => { + fixture['ngZone']!.run(() => { + router.initialNavigation(); + router.navigateByUrl('/'); + + const linkList = dl.queryAll(By.css('.ant-anchor-link-title')); + (linkList[1].nativeElement as HTMLLinkElement).click(); + fixture.detectChanges(); + }); + tick(); + + expect(router.url).toBe(`/#basic`); + })); }); describe('property', () => { @@ -302,6 +338,7 @@ describe('anchor', () => { [nzContainer]="nzContainer" [nzCurrentAnchor]="nzCurrentAnchor" [nzDirection]="nzDirection" + [nzReplace]="replace" (nzClick)="_click($event)" (nzScroll)="_scroll($event)" (nzChange)="_change($event)" @@ -362,6 +399,7 @@ export class TestComponent { nzContainer: any = null; nzCurrentAnchor?: string; nzDirection: NzDirectionVHType = 'vertical'; + replace = false; _click() {} _change() {} _scroll() {} diff --git a/components/anchor/doc/index.en-US.md b/components/anchor/doc/index.en-US.md index 7af3d116c9b..c8d44ce366d 100755 --- a/components/anchor/doc/index.en-US.md +++ b/components/anchor/doc/index.en-US.md @@ -21,22 +21,24 @@ import { NzAnchorModule } from 'ng-zorro-antd/anchor'; | Property | Description | Type | Default | Global Config | | -------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------- | ------------ | ------------- | -| `[nzAffix]` | Fixed mode of Anchor | `boolean` | `true` | +| `[nzAffix]` | Fixed mode of Anchor | `boolean` | `true` | | | `[nzBounds]` | Bounding distance of anchor area, unit: px | `number` | `5` | ✅ | | `[nzOffsetTop]` | Pixels to offset from top when calculating position of scroll | `number` | `0` | ✅ | | `[nzShowInkInFixed]` | Whether show ink-balls in Fixed mode | `boolean` | `false` | ✅ | | `[nzTargetOffset]` | Anchor scroll offset, default as `offsetTop`, [example](#components-anchor-demo-targetOffset) | `number` | - | | -| `[nzContainer]` | Scrolling container | `string \| HTMLElement` | `window` | +| `[nzContainer]` | Scrolling container | `string \| HTMLElement` | `window` | | | `[nzCurrentAnchor]` | Customize the anchor highlight | `string` | - | | | `[nzDirection]` | Set Anchor direction | `'vertical' \| 'horizontal'` | `'vertical'` | | -| `(nzClick)` | Click of Anchor item | `EventEmitter` | - | +| `[nzReplace]` | Replace items' href in browser history instead of pushing it | `boolean` | `false` | | +| `(nzClick)` | Click of Anchor item | `EventEmitter` | - | | | `(nzChange)` | Listening for anchor link change | `EventEmitter` | - | | -| `(nzScroll)` | The scroll function that is triggered when scrolling to an anchor. | `EventEmitter` | - | +| `(nzScroll)` | The scroll function that is triggered when scrolling to an anchor. | `EventEmitter` | - | | ### nz-link:standalone -| Property | Description | Type | -| ------------ | ----------------------------------------- | ----------------------------- | -| `[nzHref]` | target of hyperlink | `string` | -| `[nzTarget]` | Specifies where to display the linked URL | `string` | -| `[nzTitle]` | content of hyperlink | `string \| TemplateRef` | +| Property | Description | Type | Default | +| ------------- | ---------------------------------------------------------- | ----------------------------- | ------- | +| `[nzHref]` | target of hyperlink | `string` | - | +| `[nzTarget]` | Specifies where to display the linked URL | `string` | - | +| `[nzTitle]` | content of hyperlink | `string \| TemplateRef` | - | +| `[nzReplace]` | Replace item href in browser history instead of pushing it | `boolean` | `false` | diff --git a/components/anchor/doc/index.zh-CN.md b/components/anchor/doc/index.zh-CN.md index 9b695a2e691..eb080c559fe 100755 --- a/components/anchor/doc/index.zh-CN.md +++ b/components/anchor/doc/index.zh-CN.md @@ -30,14 +30,16 @@ import { NzAnchorModule } from 'ng-zorro-antd/anchor'; | `[nzContainer]` | 指定滚动的容器 | `string \| HTMLElement` | `window` | | `[nzCurrentAnchor]` | 自定义高亮的锚点 | string | - | | | `[nzDirection]` | 设置导航方向 | `'vertical' \| 'horizontal'` | `'vertical'` | | +| `[nzReplace]` | 替换浏览器历史记录中项目的 href 而不是推送它 | `boolean` | `false` | | | `(nzClick)` | 点击项触发 | `EventEmitter` | - | | `(nzChange)` | 监听锚点链接改变 | `EventEmitter` | - | | | `(nzScroll)` | 滚动至某锚点时触发 | `EventEmitter` | - | ### nz-link:standalone -| 成员 | 说明 | 类型 | -| ------------ | -------------------------------- | ----------------------------- | -| `[nzHref]` | 锚点链接 | `string` | -| `[nzTarget]` | 该属性指定在何处显示链接的资源。 | `string` | -| `[nzTitle]` | 文字内容 | `string \| TemplateRef` | +| 成员 | 说明 | 类型 | Default | +| ------------- | -------------------------------------------- | ----------------------------- | ------- | +| `[nzHref]` | 锚点链接 | `string` | - | +| `[nzTarget]` | 该属性指定在何处显示链接的资源。 | `string` | - | +| `[nzTitle]` | 文字内容 | `string \| TemplateRef` | - | +| `[nzReplace]` | 替换浏览器历史记录中的项目 href 而不是推送它 | `boolean` | `false` | diff --git a/scripts/site/template/demo-component.template.ts b/scripts/site/template/demo-component.template.ts index a4738b54f49..528f9af2861 100644 --- a/scripts/site/template/demo-component.template.ts +++ b/scripts/site/template/demo-component.template.ts @@ -10,12 +10,6 @@ export class {{componentName}} { expanded = false; @ViewChildren(NzCodeBoxComponent) codeBoxes!: QueryList; - goLink(link: string): void { - if (window) { - window.location.hash = link; - } - } - expandAllCode(): void { this.expanded = !this.expanded; this.codeBoxes.forEach(code => { diff --git a/scripts/site/template/doc-component.template.ts b/scripts/site/template/doc-component.template.ts index de20b90fe59..b739af7344a 100644 --- a/scripts/site/template/doc-component.template.ts +++ b/scripts/site/template/doc-component.template.ts @@ -5,10 +5,4 @@ import { Component } from '@angular/core'; templateUrl : './{{component}}-{{language}}.html', preserveWhitespaces: false }) -export class NzDoc{{componentName}}Component { - goLink(link: string) { - if (window) { - window.location.hash = link; - } - } -} +export class NzDoc{{componentName}}Component { } diff --git a/scripts/site/utils/generate-demo.js b/scripts/site/utils/generate-demo.js index 0d54b585856..99dedefb385 100644 --- a/scripts/site/utils/generate-demo.js +++ b/scripts/site/utils/generate-demo.js @@ -167,7 +167,7 @@ function generateToc(language, name, demoMap) { const links = linkArray.map(link => link.content).join(''); return ` - + ${links} `; diff --git a/scripts/site/utils/generate-docs.js b/scripts/site/utils/generate-docs.js index 5a63c1e2e97..4c325a2b8fb 100644 --- a/scripts/site/utils/generate-docs.js +++ b/scripts/site/utils/generate-docs.js @@ -43,7 +43,7 @@ function generateToc(meta, raw) { } return ` - + ${links} `; From 9f9acf938c110693413da77cc9236b7ee55f4b27 Mon Sep 17 00:00:00 2001 From: Parsa Arvaneh Date: Sun, 24 Mar 2024 23:43:41 +0330 Subject: [PATCH 03/15] feat(module:anchor): url fragment change on click - nzReplace --- components/anchor/demo/replace.md | 14 ++++++++++++++ components/anchor/demo/replace.ts | 13 +++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 components/anchor/demo/replace.md create mode 100644 components/anchor/demo/replace.ts diff --git a/components/anchor/demo/replace.md b/components/anchor/demo/replace.md new file mode 100644 index 00000000000..af8fee824a0 --- /dev/null +++ b/components/anchor/demo/replace.md @@ -0,0 +1,14 @@ +--- +order: 8 +title: + zh-CN: 替换历史中的 href + en-US: Replace href in history +--- + +## zh-CN + +替换浏览器历史记录中的路径,后退按钮将返回到上一页而不是上一个锚点。 + +## en-US + +Replace path in browser history, so back button returns to previous page instead of previous anchor item. diff --git a/components/anchor/demo/replace.ts b/components/anchor/demo/replace.ts new file mode 100644 index 00000000000..c6af8b10ffc --- /dev/null +++ b/components/anchor/demo/replace.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'nz-demo-anchor-replace', + template: ` + + + + + + ` +}) +export class NzDemoAnchorReplaceComponent {} From 500dcf9d75e23ca606bd469ee481639107994ad2 Mon Sep 17 00:00:00 2001 From: Parsa Arvaneh Date: Mon, 12 Aug 2024 17:38:59 +0330 Subject: [PATCH 04/15] feat(module:anchor): url fragment change on click - nzReplace --- components/anchor/anchor.spec.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/components/anchor/anchor.spec.ts b/components/anchor/anchor.spec.ts index 9de4f41db29..5b6638954d2 100644 --- a/components/anchor/anchor.spec.ts +++ b/components/anchor/anchor.spec.ts @@ -1,15 +1,15 @@ /* eslint-disable */ // eslint-disable -import {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing'; -import {Component, DebugElement, ViewChild} from '@angular/core'; -import {By} from '@angular/platform-browser'; -import {NzScrollService} from 'ng-zorro-antd/core/services'; -import {NzDirectionVHType} from 'ng-zorro-antd/core/types'; -import {NzAnchorComponent} from './anchor.component'; -import {NzAnchorModule} from './anchor.module'; -import {ActivatedRoute, Router} from '@angular/router'; -import {of} from 'rxjs'; -import {RouterTestingModule} from '@angular/router/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { Component, DebugElement, ViewChild } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { NzScrollService } from 'ng-zorro-antd/core/services'; +import { NzDirectionVHType } from 'ng-zorro-antd/core/types'; +import { NzAnchorComponent } from './anchor.component'; +import { NzAnchorModule } from './anchor.module'; +import { ActivatedRoute, Router } from '@angular/router'; +import { of } from 'rxjs'; +import { RouterTestingModule } from '@angular/router/testing'; const throttleTime = 51; @@ -252,7 +252,7 @@ describe('anchor', () => { expect(+inkNode.style.top!.replace('px', '')).toBeGreaterThan(0); expect(context._change).toHaveBeenCalled(); done(); - }, throttleTime); + }, throttleTime * 3); }); }); @@ -403,7 +403,7 @@ export class TestComponent { nzCurrentAnchor?: string; nzDirection: NzDirectionVHType = 'vertical'; replace = false; - _click() { } - _change() { } - _scroll() { } + _click() {} + _change() {} + _scroll() {} } From 7b9723cdba5c0502a0a5dba044a19d2c26e6f916 Mon Sep 17 00:00:00 2001 From: Parsa Arvaneh Date: Tue, 13 Aug 2024 10:37:16 +0330 Subject: [PATCH 05/15] feat(module:anchor): url fragment change on click - nzReplace --- components/anchor/anchor.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/anchor/anchor.spec.ts b/components/anchor/anchor.spec.ts index 5b6638954d2..8e398446de3 100644 --- a/components/anchor/anchor.spec.ts +++ b/components/anchor/anchor.spec.ts @@ -252,7 +252,7 @@ describe('anchor', () => { expect(+inkNode.style.top!.replace('px', '')).toBeGreaterThan(0); expect(context._change).toHaveBeenCalled(); done(); - }, throttleTime * 3); + }, throttleTime * 20); }); }); From 04b1832da338550cb9e78e8eb3df8ba49df562fe Mon Sep 17 00:00:00 2001 From: Laffery <49607541+Laffery@users.noreply.github.com> Date: Sat, 23 Nov 2024 08:03:41 +0800 Subject: [PATCH 06/15] Update anchor-link.component.ts --- components/anchor/anchor-link.component.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/components/anchor/anchor-link.component.ts b/components/anchor/anchor-link.component.ts index a31ef29eeb8..3fd24d94247 100644 --- a/components/anchor/anchor-link.component.ts +++ b/components/anchor/anchor-link.component.ts @@ -6,6 +6,7 @@ import { Platform } from '@angular/cdk/platform'; import { NgTemplateOutlet } from '@angular/common'; import { + booleanAttribute, ChangeDetectionStrategy, Component, ContentChild, @@ -34,7 +35,7 @@ import { NzAnchorComponent } from './anchor.component'; #linkTitle class="ant-anchor-link-title" [href]="nzHref" - [title]="titleStr" + [attr.title]="titleStr" [target]="nzTarget" (click)="goToClick($event)" > @@ -57,7 +58,7 @@ import { NzAnchorComponent } from './anchor.component'; export class NzAnchorLinkComponent implements OnInit, OnDestroy { @Input() nzHref = '#'; @Input() nzTarget?: string; - @Input() nzReplace: boolean = false; + @Input({ transform: booleanAttribute }) nzReplace: boolean = false; titleStr: string | null = ''; titleTpl?: TemplateRef; @@ -100,11 +101,11 @@ export class NzAnchorLinkComponent implements OnInit, OnDestroy { this.renderer.removeClass(this.elementRef.nativeElement, 'ant-anchor-link-active'); } - async goToClick(e: Event): Promise { + goToClick(e: Event): void { e.preventDefault(); e.stopPropagation(); if (this.platform.isBrowser) { - await this.anchorComp.handleScrollTo(this); + this.anchorComp.handleScrollTo(this).then(); } } From b451362853084337b75ee5e3ecb7c49c19dcb06b Mon Sep 17 00:00:00 2001 From: Laffery <49607541+Laffery@users.noreply.github.com> Date: Sat, 23 Nov 2024 08:04:05 +0800 Subject: [PATCH 07/15] Update anchor.component.ts --- components/anchor/anchor.component.ts | 45 ++++++++++++++++----------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/components/anchor/anchor.component.ts b/components/anchor/anchor.component.ts index 5d28dd9b749..1742f2e70a0 100644 --- a/components/anchor/anchor.component.ts +++ b/components/anchor/anchor.component.ts @@ -4,7 +4,7 @@ */ import { normalizePassiveListenerOptions, Platform } from '@angular/cdk/platform'; -import { DOCUMENT, NgClass, NgIf, NgStyle, NgTemplateOutlet } from '@angular/common'; +import { DOCUMENT, NgStyle, NgTemplateOutlet } from '@angular/common'; import { AfterViewInit, booleanAttribute, @@ -30,7 +30,7 @@ import { fromEvent, Subject } from 'rxjs'; import { takeUntil, throttleTime } from 'rxjs/operators'; import { NzAffixModule } from 'ng-zorro-antd/affix'; -import { NzConfigKey, NzConfigService, WithConfig } from 'ng-zorro-antd/core/config'; +import { NzConfigKey, WithConfig } from 'ng-zorro-antd/core/config'; import { NzScrollService } from 'ng-zorro-antd/core/services'; import { NgStyleInterface, NzDirectionVHType } from 'ng-zorro-antd/core/types'; import { numberAttributeWithZeroFallback } from 'ng-zorro-antd/core/util'; @@ -54,7 +54,7 @@ const passiveEventListenerOptions = normalizePassiveListenerOptions({ passive: t exportAs: 'nzAnchor', preserveWhitespaces: false, standalone: true, - imports: [NgClass, NgIf, NgStyle, NgTemplateOutlet, NzAffixModule], + imports: [NgStyle, NgTemplateOutlet, NzAffixModule], template: ` @if (nzAffix) { @@ -67,10 +67,10 @@ const passiveEventListenerOptions = normalizePassiveListenerOptions({ passive: t
-
+
@@ -98,17 +98,17 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit, OnChanges { nzBounds: number = 5; @Input({ transform: numberAttributeWithZeroFallback }) - @WithConfig() + @WithConfig() nzOffsetTop?: number = undefined; @Input({ transform: numberAttributeWithZeroFallback }) - @WithConfig() + @WithConfig() nzTargetOffset?: number = undefined; @Input() nzContainer?: string | HTMLElement; @Input() nzCurrentAnchor?: string; @Input() nzDirection: NzDirectionVHType = 'vertical'; - @Input() nzReplace: boolean = false; + @Input({ transform: booleanAttribute }) nzReplace: boolean = false; @Output() readonly nzClick = new EventEmitter(); @Output() readonly nzChange = new EventEmitter(); @@ -126,15 +126,16 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit, OnChanges { private handleScrollTimeoutID?: ReturnType; private doc: Document = inject(DOCUMENT); + // router module is optional for some projects, so these should be injected as optional. + private router = inject(Router, { optional: true }); + private activatedRoute = inject(ActivatedRoute, { optional: true }); + constructor( - public nzConfigService: NzConfigService, private scrollSrv: NzScrollService, private cdr: ChangeDetectorRef, private platform: Platform, private zone: NgZone, - private renderer: Renderer2, - private router: Router, - private activatedRoute: ActivatedRoute + private renderer: Renderer2 ) {} registerLink(link: NzAnchorLinkComponent): void { @@ -170,7 +171,7 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit, OnChanges { .subscribe(() => this.handleScroll()); }); // Browser would maintain the scrolling position when refreshing. - // So we have to delay calculation in avoid of getting a incorrect result. + // So we have to delay calculation in avoid of getting an incorrect result. this.handleScrollTimeoutID = setTimeout(() => this.handleScroll()); } @@ -271,7 +272,7 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit, OnChanges { } }); this.nzClick.emit(linkComp.nzHref); - await this.updateUrlFragment(linkComp.nzHref, linkComp.nzReplace); + return this.updateUrlFragment(linkComp.nzHref, linkComp.nzReplace); } ngOnChanges(changes: SimpleChanges): void { @@ -292,12 +293,18 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit, OnChanges { } async updateUrlFragment(nzHref: string, replace?: boolean): Promise { + if (!this.router || !this.activatedRoute) { + return; + } + let urlWithoutFragment = nzHref.split('#')[1] ?? nzHref; - await this.router.navigate([], { - relativeTo: this.activatedRoute, - fragment: urlWithoutFragment, - replaceUrl: replace ? replace : this.nzReplace - }); + return this.router + .navigate([], { + relativeTo: this.activatedRoute, + fragment: urlWithoutFragment, + replaceUrl: replace ? replace : this.nzReplace + }) + .then(); } } From 4c2a70940281206f5f02f219dbac3e6ce65676cd Mon Sep 17 00:00:00 2001 From: Laffery <49607541+Laffery@users.noreply.github.com> Date: Sat, 23 Nov 2024 08:06:25 +0800 Subject: [PATCH 08/15] Update anchor.spec.ts --- components/anchor/anchor.spec.ts | 144 +++++++++++++++++++++++-------- 1 file changed, 107 insertions(+), 37 deletions(-) diff --git a/components/anchor/anchor.spec.ts b/components/anchor/anchor.spec.ts index 8e398446de3..57bf94de893 100644 --- a/components/anchor/anchor.spec.ts +++ b/components/anchor/anchor.spec.ts @@ -1,15 +1,16 @@ -/* eslint-disable */ -// eslint-disable +import { Platform } from '@angular/cdk/platform'; +import { DOCUMENT } from '@angular/common'; +import { Component, DebugElement, ElementRef, Provider, ViewChild } from '@angular/core'; import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { Component, DebugElement, ViewChild } from '@angular/core'; import { By } from '@angular/platform-browser'; +import { ActivatedRoute, Router } from '@angular/router'; +import { of } from 'rxjs'; + import { NzScrollService } from 'ng-zorro-antd/core/services'; -import { NzDirectionVHType } from 'ng-zorro-antd/core/types'; +import { NzDirectionVHType, NzSafeAny } from 'ng-zorro-antd/core/types'; + import { NzAnchorComponent } from './anchor.component'; import { NzAnchorModule } from './anchor.module'; -import { ActivatedRoute, Router } from '@angular/router'; -import { of } from 'rxjs'; -import { RouterTestingModule } from '@angular/router/testing'; const throttleTime = 51; @@ -21,6 +22,13 @@ const activatedRouteStub = { } }; +function provideActivatedRouteStub(): Provider { + return { + provide: ActivatedRoute, + useValue: activatedRouteStub + }; +} + describe('anchor', () => { let fixture: ComponentFixture; let dl: DebugElement; @@ -28,26 +36,23 @@ describe('anchor', () => { let page: PageObject; let srv: NzScrollService; let router: Router; + beforeEach(() => { - TestBed.configureTestingModule({ - imports: [NzAnchorModule], - providers: [{ provide: ActivatedRoute, useValue: activatedRouteStub }, RouterTestingModule], - declarations: [TestComponent] - }); fixture = TestBed.createComponent(TestComponent); dl = fixture.debugElement; context = fixture.componentInstance; fixture.detectChanges(); page = new PageObject(); - router = TestBed.get(Router); + router = TestBed.inject(Router); spyOn(context, '_scroll'); spyOn(context, '_change'); srv = TestBed.inject(NzScrollService); }); + afterEach(() => context.comp.ngOnDestroy()); describe('[default]', () => { - it(`should scolling to target via click a link`, () => { + it(`should scrolling to target via click a link`, () => { spyOn(srv, 'scrollTo').and.callFake((_containerEl, _targetTopValue = 0, options = {}) => { if (options.callback) { options.callback(); @@ -58,14 +63,7 @@ describe('anchor', () => { expect(context._scroll).toHaveBeenCalled(); }); - it('should hava remove listen when the component is destroyed', () => { - expect(context.comp['destroy$']!.isStopped).toBeFalsy(); - context.comp.ngOnDestroy(); - fixture.detectChanges(); - expect(context.comp['destroy$']!.isStopped).toBeTruthy(); - }); - - it('should actived when scrolling to the anchor', (done: () => void) => { + it('should be activated when scrolling to the anchor', (done: () => void) => { expect(context._scroll).not.toHaveBeenCalled(); page.scrollTo(); setTimeout(() => { @@ -76,7 +74,7 @@ describe('anchor', () => { }, throttleTime); }); - it('should actived when scrolling to the anchor - horizontal', (done: () => void) => { + it('should be activated when scrolling to the anchor - horizontal', (done: () => void) => { context.nzDirection = 'horizontal'; fixture.detectChanges(); expect(context._scroll).not.toHaveBeenCalled(); @@ -89,8 +87,8 @@ describe('anchor', () => { }, throttleTime); }); - it('should clean actived when leave all anchor', fakeAsync(() => { - spyOn(context.comp, 'clearActive' as any); + it('should clean activated when leaving all anchor', fakeAsync(() => { + spyOn(context.comp, 'clearActive' as NzSafeAny); page.scrollTo(); tick(throttleTime); fixture.detectChanges(); @@ -102,7 +100,7 @@ describe('anchor', () => { expect(context.comp['clearActive']!).toHaveBeenCalled(); })); - it(`won't scolling when is not exists link`, () => { + it(`won't scrolling when is not exists link`, () => { spyOn(srv, 'getScroll'); expect(context._scroll).not.toHaveBeenCalled(); expect(srv.getScroll).not.toHaveBeenCalled(); @@ -110,7 +108,7 @@ describe('anchor', () => { expect(srv.getScroll).not.toHaveBeenCalled(); }); - it(`won't scolling when is invalid link`, () => { + it(`won't scrolling when is invalid link`, () => { spyOn(srv, 'getScroll'); expect(context._scroll).not.toHaveBeenCalled(); expect(srv.getScroll).not.toHaveBeenCalled(); @@ -165,6 +163,7 @@ describe('anchor', () => { const linkList = dl.queryAll(By.css('nz-affix')); expect(linkList.length).toBe(1); }); + it(`is [false]`, () => { let linkList = dl.queryAll(By.css('nz-affix')); expect(linkList.length).toBe(1); @@ -199,12 +198,14 @@ describe('anchor', () => { context.nzAffix = false; fixture.detectChanges(); }); + it('should be show ink when [false]', () => { context.nzShowInkInFixed = false; fixture.detectChanges(); scrollTo(); expect(dl.query(By.css('.ant-anchor-fixed')) == null).toBe(false); }); + it('should be hide ink when [true]', () => { context.nzShowInkInFixed = true; fixture.detectChanges(); @@ -220,6 +221,7 @@ describe('anchor', () => { fixture.detectChanges(); expect(window.addEventListener).toHaveBeenCalled(); }); + it('with string', () => { spyOn(context, '_click'); const el = document.querySelector('#target')!; @@ -243,6 +245,7 @@ describe('anchor', () => { page.to('#basic-target'); expect(context._change).toHaveBeenCalled(); })); + it('should emit nzChange when scrolling to the anchor', (done: () => void) => { spyOn(context, '_change'); expect(context._change).not.toHaveBeenCalled(); @@ -252,7 +255,7 @@ describe('anchor', () => { expect(+inkNode.style.top!.replace('px', '')).toBeGreaterThan(0); expect(context._change).toHaveBeenCalled(); done(); - }, throttleTime * 20); + }, throttleTime); }); }); @@ -271,6 +274,7 @@ describe('anchor', () => { it(`should show custom template of [nzTemplate]`, () => { expect(dl.query(By.css('.nzTemplate-title')) != null).toBe(true); }); + it(`should show custom template of [nzTitle]`, () => { expect(dl.query(By.css('.nzTitle-title')) != null).toBe(true); }); @@ -293,11 +297,11 @@ describe('anchor', () => { describe('**boundary**', () => { it('#getOffsetTop', (done: () => void) => { const el1 = document.getElementById('何时使用')!; - spyOn(el1, 'getClientRects').and.returnValue([] as any); + spyOn(el1, 'getClientRects').and.returnValue([] as NzSafeAny); const el2 = document.getElementById('parallel1')!; spyOn(el2, 'getBoundingClientRect').and.returnValue({ top: 0 - } as any); + } as NzSafeAny); expect(context._scroll).not.toHaveBeenCalled(); page.scrollTo(); setTimeout(() => { @@ -327,7 +331,73 @@ describe('anchor', () => { } }); +describe('NzAnchor', () => { + let component: NzAnchorComponent; + let fixture: ComponentFixture; + let mockPlatform: Platform; + let scrollService: NzScrollService; + let mockDocument: Document; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + provideActivatedRouteStub(), + { provide: ElementRef, useValue: new ElementRef(document.createElement('div')) } + ] + }); + + fixture = TestBed.createComponent(NzAnchorComponent); + component = fixture.componentInstance; + mockPlatform = TestBed.inject(Platform); + scrollService = TestBed.inject(NzScrollService); + mockDocument = TestBed.inject(DOCUMENT); + }); + + it('should not register listeners if platform is not browser', () => { + mockPlatform.isBrowser = false; + + component.ngAfterViewInit(); + expect(component['handleScrollTimeoutID']).toBeFalsy(); + }); + + it('should calculate the correct offsetTop in handleScroll method', () => { + component.nzTargetOffset = 50; + component.nzOffsetTop = 20; + component.nzBounds = 5; + + component.handleScroll(); + + expect(component.nzTargetOffset).toBe(50); + expect(component.nzOffsetTop).toBe(20); + }); + + it('should calculate target scroll top correctly and call scrollTo', fakeAsync(() => { + const mockElement = document.createElement('div'); + spyOn(mockDocument, 'querySelector').and.returnValue(mockElement); + spyOn(scrollService, 'getScroll').and.returnValue(100); + spyOn(component, 'getContainer').and.returnValue(window); + + component.nzTargetOffset = undefined; + component.nzOffsetTop = undefined; + + const mockLinkComponent = { + nzHref: '#test', + setActive: jasmine.createSpy('setActive'), + getLinkTitleElement: () => document.createElement('a') + } as NzSafeAny; + + const scrollToSpy = spyOn(scrollService, 'scrollTo').and.callThrough(); + + component.handleScrollTo(mockLinkComponent); + + expect(scrollToSpy).toHaveBeenCalledWith(component['getContainer'](), 100, jasmine.any(Object)); + })); +}); + @Component({ + standalone: true, + imports: [NzAnchorModule], + providers: [provideActivatedRouteStub()], template: ` { [nzCurrentAnchor]="nzCurrentAnchor" [nzDirection]="nzDirection" [nzReplace]="replace" - (nzClick)="_click($event)" - (nzScroll)="_scroll($event)" - (nzChange)="_change($event)" + (nzClick)="_click()" + (nzScroll)="_scroll()" + (nzChange)="_change()" > @@ -399,11 +469,11 @@ export class TestComponent { nzOffsetTop = 0; nzTargetOffset?: number; nzShowInkInFixed = false; - nzContainer: any = null; + nzContainer: NzSafeAny = null; nzCurrentAnchor?: string; nzDirection: NzDirectionVHType = 'vertical'; replace = false; - _click() {} - _change() {} - _scroll() {} + _click(): void {} + _change(): void {} + _scroll(): void {} } From fe67bf45acf0f90330663ef763406c13f96904e3 Mon Sep 17 00:00:00 2001 From: Laffery <49607541+Laffery@users.noreply.github.com> Date: Sat, 23 Nov 2024 15:35:19 +0800 Subject: [PATCH 09/15] Update anchor.spec.ts --- components/anchor/anchor.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/anchor/anchor.spec.ts b/components/anchor/anchor.spec.ts index 57bf94de893..13c2be416ec 100644 --- a/components/anchor/anchor.spec.ts +++ b/components/anchor/anchor.spec.ts @@ -255,7 +255,7 @@ describe('anchor', () => { expect(+inkNode.style.top!.replace('px', '')).toBeGreaterThan(0); expect(context._change).toHaveBeenCalled(); done(); - }, throttleTime); + }, throttleTime * 3); }); }); From 5de5899c77ac6d75909fc1903137d86983400c31 Mon Sep 17 00:00:00 2001 From: Laffery <49607541+Laffery@users.noreply.github.com> Date: Sat, 23 Nov 2024 16:15:15 +0800 Subject: [PATCH 10/15] Update anchor.spec.ts --- components/anchor/anchor.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/anchor/anchor.spec.ts b/components/anchor/anchor.spec.ts index 13c2be416ec..5e9e6b0f5b3 100644 --- a/components/anchor/anchor.spec.ts +++ b/components/anchor/anchor.spec.ts @@ -255,7 +255,7 @@ describe('anchor', () => { expect(+inkNode.style.top!.replace('px', '')).toBeGreaterThan(0); expect(context._change).toHaveBeenCalled(); done(); - }, throttleTime * 3); + }, throttleTime * 20); }); }); From 0a751177cb363225ca12bc3ee5a5991d3c4571e4 Mon Sep 17 00:00:00 2001 From: Laffery <49607541+Laffery@users.noreply.github.com> Date: Sat, 23 Nov 2024 16:16:42 +0800 Subject: [PATCH 11/15] Update replace.ts --- components/anchor/demo/replace.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/anchor/demo/replace.ts b/components/anchor/demo/replace.ts index c6af8b10ffc..a4ae39674c0 100644 --- a/components/anchor/demo/replace.ts +++ b/components/anchor/demo/replace.ts @@ -1,7 +1,11 @@ import { Component } from '@angular/core'; +import { NzAnchorModule } from 'ng-zorro-antd/anchor'; + @Component({ selector: 'nz-demo-anchor-replace', + standalone: true, + imports: [NzAnchorModule], template: ` From fbe2b98b570407d9c36c86f873c917eb88e915e2 Mon Sep 17 00:00:00 2001 From: Laffery <49607541+Laffery@users.noreply.github.com> Date: Tue, 26 Nov 2024 10:29:23 +0800 Subject: [PATCH 12/15] Update anchor.component.ts --- components/anchor/anchor.component.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/components/anchor/anchor.component.ts b/components/anchor/anchor.component.ts index 5986f3a0522..7e56246cee1 100644 --- a/components/anchor/anchor.component.ts +++ b/components/anchor/anchor.component.ts @@ -251,7 +251,7 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit, OnChanges { } } - async handleScrollTo(linkComp: NzAnchorLinkComponent): Promise { + handleScrollTo(linkComp: NzAnchorLinkComponent): void { const el = this.doc.querySelector(linkComp.nzHref); if (!el) { return; @@ -269,7 +269,7 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit, OnChanges { } }); this.nzClick.emit(linkComp.nzHref); - return this.updateUrlFragment(linkComp.nzHref, linkComp.nzReplace); + this.updateUrlFragment(linkComp.nzHref, linkComp.nzReplace); } ngOnChanges(changes: SimpleChanges): void { @@ -289,14 +289,13 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit, OnChanges { } } - async updateUrlFragment(nzHref: string, replace?: boolean): Promise { + updateUrlFragment(nzHref: string, replace?: boolean): void { if (!this.router || !this.activatedRoute) { return; } let urlWithoutFragment = nzHref.split('#')[1] ?? nzHref; - - return this.router + this.router .navigate([], { relativeTo: this.activatedRoute, fragment: urlWithoutFragment, From f9fb540052899b5ebe1cdf7eb7b67629f942852a Mon Sep 17 00:00:00 2001 From: Laffery <49607541+Laffery@users.noreply.github.com> Date: Tue, 26 Nov 2024 10:29:50 +0800 Subject: [PATCH 13/15] Update anchor-link.component.ts --- components/anchor/anchor-link.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/anchor/anchor-link.component.ts b/components/anchor/anchor-link.component.ts index 3fd24d94247..cbfb12d0d41 100644 --- a/components/anchor/anchor-link.component.ts +++ b/components/anchor/anchor-link.component.ts @@ -105,7 +105,7 @@ export class NzAnchorLinkComponent implements OnInit, OnDestroy { e.preventDefault(); e.stopPropagation(); if (this.platform.isBrowser) { - this.anchorComp.handleScrollTo(this).then(); + this.anchorComp.handleScrollTo(this); } } From 561d0d946473cfdad8ee79b1937257b2a0934bc4 Mon Sep 17 00:00:00 2001 From: Laffery <49607541+Laffery@users.noreply.github.com> Date: Tue, 26 Nov 2024 11:07:10 +0800 Subject: [PATCH 14/15] Update anchor.spec.ts --- components/anchor/anchor.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/anchor/anchor.spec.ts b/components/anchor/anchor.spec.ts index 5e9e6b0f5b3..75150e1ddd0 100644 --- a/components/anchor/anchor.spec.ts +++ b/components/anchor/anchor.spec.ts @@ -29,7 +29,7 @@ function provideActivatedRouteStub(): Provider { }; } -describe('anchor', () => { +describe('nz-anchor', () => { let fixture: ComponentFixture; let dl: DebugElement; let context: TestComponent; @@ -249,13 +249,13 @@ describe('anchor', () => { it('should emit nzChange when scrolling to the anchor', (done: () => void) => { spyOn(context, '_change'); expect(context._change).not.toHaveBeenCalled(); - page.scrollTo(); + page.scrollTo('#API'); setTimeout(() => { const inkNode = page.getEl('.ant-anchor-ink-ball'); expect(+inkNode.style.top!.replace('px', '')).toBeGreaterThan(0); expect(context._change).toHaveBeenCalled(); done(); - }, throttleTime * 20); + }, throttleTime); }); }); From 49ecf10b1675cc215e6ac94304a7a324e2482185 Mon Sep 17 00:00:00 2001 From: Laffery <49607541+Laffery@users.noreply.github.com> Date: Tue, 26 Nov 2024 11:29:43 +0800 Subject: [PATCH 15/15] Update anchor.spec.ts --- components/anchor/anchor.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/anchor/anchor.spec.ts b/components/anchor/anchor.spec.ts index 75150e1ddd0..e988d78869a 100644 --- a/components/anchor/anchor.spec.ts +++ b/components/anchor/anchor.spec.ts @@ -249,8 +249,9 @@ describe('nz-anchor', () => { it('should emit nzChange when scrolling to the anchor', (done: () => void) => { spyOn(context, '_change'); expect(context._change).not.toHaveBeenCalled(); - page.scrollTo('#API'); + page.scrollTo(); setTimeout(() => { + fixture.detectChanges(); const inkNode = page.getEl('.ant-anchor-ink-ball'); expect(+inkNode.style.top!.replace('px', '')).toBeGreaterThan(0); expect(context._change).toHaveBeenCalled();