Skip to content

Commit

Permalink
fix(module:tooltip): fix mouse leave overlay not obey delaying (#5868)
Browse files Browse the repository at this point in the history
close #5713
  • Loading branch information
Wendell Hu authored Oct 13, 2020
1 parent dcc743a commit 6b5fdee
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 152 deletions.
55 changes: 23 additions & 32 deletions components/popconfirm/popconfirm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { NzNoAnimationDirective } from 'ng-zorro-antd/core/no-animation';
import { BooleanInput, NgStyleInterface, NzTSType } from 'ng-zorro-antd/core/types';

import { InputBoolean } from 'ng-zorro-antd/core/util';
import { NzTooltipBaseDirective, NzToolTipComponent, NzTooltipTrigger } from 'ng-zorro-antd/tooltip';
import { NzTooltipBaseDirective, NzToolTipComponent, NzTooltipTrigger, PropertyMapping } from 'ng-zorro-antd/tooltip';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

Expand All @@ -43,52 +43,43 @@ export class NzPopconfirmDirective extends NzTooltipBaseDirective {
static ngAcceptInputType_nzCondition: BooleanInput;
static ngAcceptInputType_nzPopconfirmShowArrow: BooleanInput;

@Input('nzPopconfirmTitle') specificTitle?: NzTSType;
@Input('nz-popconfirm') directiveNameTitle?: NzTSType | null;
@Input('nzPopconfirmTrigger') specificTrigger?: NzTooltipTrigger = 'click';
@Input('nzPopconfirmPlacement') specificPlacement?: string = 'top';
@Input('nzPopconfirmOrigin') specificOrigin?: ElementRef<HTMLElement>;
@Input('nzPopconfirmMouseEnterDelay') specificMouseEnterDelay?: number;
@Input('nzPopconfirmMouseLeaveDelay') specificMouseLeaveDelay?: number;
@Input('nzPopconfirmOverlayClassName') specificOverlayClassName?: string;
@Input('nzPopconfirmOverlayStyle') specificOverlayStyle?: NgStyleInterface;
@Input('nzPopconfirmTitle') title?: NzTSType;
@Input('nz-popconfirm') directiveTitle?: NzTSType | null;
@Input('nzPopconfirmTrigger') trigger?: NzTooltipTrigger = 'click';
@Input('nzPopconfirmPlacement') placement?: string = 'top';
@Input('nzPopconfirmOrigin') origin?: ElementRef<HTMLElement>;
@Input('nzPopconfirmMouseEnterDelay') mouseEnterDelay?: number;
@Input('nzPopconfirmMouseLeaveDelay') mouseLeaveDelay?: number;
@Input('nzPopconfirmOverlayClassName') overlayClassName?: string;
@Input('nzPopconfirmOverlayStyle') overlayStyle?: NgStyleInterface;
@Input('nzPopconfirmVisible') visible?: boolean;
@Input() nzOkText?: string;
@Input() nzOkType?: string;
@Input() nzCancelText?: string;
@Input() nzIcon?: string | TemplateRef<void>;
@Input() @InputBoolean() nzCondition: boolean = false;
@Input() @InputBoolean() nzPopconfirmShowArrow: boolean = true;

/**
* @deprecated 10.0.0. This is deprecated and going to be removed in 10.0.0.
* Please use a more specific API. Like `nzTooltipTrigger`.
*/
@Input() nzTrigger: NzTooltipTrigger = 'click';

@Input('nzPopconfirmVisible') specificVisible?: boolean;

// tslint:disable-next-line:no-output-rename
@Output('nzPopconfirmVisibleChange') readonly specificVisibleChange = new EventEmitter<boolean>();
@Output('nzPopconfirmVisibleChange') readonly visibleChange = new EventEmitter<boolean>();
@Output() readonly nzOnCancel = new EventEmitter<void>();
@Output() readonly nzOnConfirm = new EventEmitter<void>();

protected readonly componentFactory: ComponentFactory<NzPopconfirmComponent> = this.resolver.resolveComponentFactory(
NzPopconfirmComponent
);

protected readonly needProxyProperties = [
'nzOverlayClassName',
'nzOverlayStyle',
'nzMouseEnterDelay',
'nzMouseLeaveDelay',
'nzVisible',
'nzOkText',
'nzOkType',
'nzCancelText',
'nzCondition',
'nzIcon',
'nzPopconfirmShowArrow'
];
protected getProxyPropertyMap(): PropertyMapping {
return {
nzOkText: ['nzOkText', () => this.nzOkText],
nzOkType: ['nzOkType', () => this.nzOkType],
nzCancelText: ['nzCancelText', () => this.nzCancelText],
nzCondition: ['nzCondition', () => this.nzCondition],
nzIcon: ['nzIcon', () => this.nzIcon],
nzPopconfirmShowArrow: ['nzPopconfirmShowArrow', () => this.nzPopconfirmShowArrow],
...super.getProxyPropertyMap()
};
}

constructor(
elementRef: ElementRef,
Expand Down
24 changes: 12 additions & 12 deletions components/popover/popover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,20 @@ import { isTooltipEmpty, NzTooltipBaseDirective, NzToolTipComponent, NzTooltipTr
}
})
export class NzPopoverDirective extends NzTooltipBaseDirective {
@Input('nzPopoverTitle') specificTitle?: NzTSType;
@Input('nzPopoverContent') specificContent?: NzTSType;
@Input('nz-popover') directiveNameTitle?: NzTSType | null;
@Input('nzPopoverTrigger') specificTrigger?: NzTooltipTrigger = 'hover';
@Input('nzPopoverPlacement') specificPlacement?: string = 'top';
@Input('nzPopoverOrigin') specificOrigin?: ElementRef<HTMLElement>;
@Input('nzPopoverVisible') specificVisible?: boolean;
@Input('nzPopoverMouseEnterDelay') specificMouseEnterDelay?: number;
@Input('nzPopoverMouseLeaveDelay') specificMouseLeaveDelay?: number;
@Input('nzPopoverOverlayClassName') specificOverlayClassName?: string;
@Input('nzPopoverOverlayStyle') specificOverlayStyle?: NgStyleInterface;
@Input('nzPopoverTitle') title?: NzTSType;
@Input('nzPopoverContent') content?: NzTSType;
@Input('nz-popover') directiveTitle?: NzTSType | null;
@Input('nzPopoverTrigger') trigger?: NzTooltipTrigger = 'hover';
@Input('nzPopoverPlacement') placement?: string = 'top';
@Input('nzPopoverOrigin') origin?: ElementRef<HTMLElement>;
@Input('nzPopoverVisible') visible?: boolean;
@Input('nzPopoverMouseEnterDelay') mouseEnterDelay?: number;
@Input('nzPopoverMouseLeaveDelay') mouseLeaveDelay?: number;
@Input('nzPopoverOverlayClassName') overlayClassName?: string;
@Input('nzPopoverOverlayStyle') overlayStyle?: NgStyleInterface;

// tslint:disable-next-line:no-output-rename
@Output('nzPopoverVisibleChange') readonly specificVisibleChange = new EventEmitter<boolean>();
@Output('nzPopoverVisibleChange') readonly visibleChange = new EventEmitter<boolean>();

componentFactory: ComponentFactory<NzPopoverComponent> = this.resolver.resolveComponentFactory(NzPopoverComponent);

Expand Down
175 changes: 81 additions & 94 deletions components/tooltip/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,27 @@ import { isNotNil, toBoolean } from 'ng-zorro-antd/core/util';
import { Subject } from 'rxjs';
import { distinctUntilChanged, takeUntil } from 'rxjs/operators';

export interface PropertyMapping {
[key: string]: [string, () => unknown];
}

export type NzTooltipTrigger = 'click' | 'focus' | 'hover' | null;

@Directive()
export abstract class NzTooltipBaseDirective implements OnChanges, OnDestroy, AfterViewInit {
directiveNameTitle?: NzTSType | null;
directiveNameContent?: NzTSType | null;
specificTitle?: NzTSType | null;
specificContent?: NzTSType | null;
specificTrigger?: NzTooltipTrigger;
specificPlacement?: string;
specificOrigin?: ElementRef<HTMLElement>;
specificVisible?: boolean;
specificMouseEnterDelay?: number;
specificMouseLeaveDelay?: number;
specificOverlayClassName?: string;
specificOverlayStyle?: NgStyleInterface;
specificVisibleChange = new EventEmitter<boolean>();
directiveTitle?: NzTSType | null;
directiveContent?: NzTSType | null;
title?: NzTSType | null;
content?: NzTSType | null;
trigger?: NzTooltipTrigger;
placement?: string;
origin?: ElementRef<HTMLElement>;
visible?: boolean;
mouseEnterDelay?: number;
mouseLeaveDelay?: number;
overlayClassName?: string;
overlayStyle?: NgStyleInterface;
visibleChange = new EventEmitter<boolean>();

/**
* For create tooltip dynamically. This should be override for each different component.
Expand All @@ -53,44 +57,49 @@ export abstract class NzTooltipBaseDirective implements OnChanges, OnDestroy, Af
/**
* This true title that would be used in other parts on this component.
*/
protected get title(): NzTSType | null {
return this.specificTitle || this.directiveNameTitle || null;
protected get _title(): NzTSType | null {
return this.title || this.directiveTitle || null;
}

protected get content(): NzTSType | null {
return this.specificContent || this.directiveNameContent || null;
protected get _content(): NzTSType | null {
return this.content || this.directiveContent || null;
}

protected get trigger(): NzTooltipTrigger {
return typeof this.specificTrigger !== 'undefined' ? this.specificTrigger : 'hover';
protected get _trigger(): NzTooltipTrigger {
return typeof this.trigger !== 'undefined' ? this.trigger : 'hover';
}

protected get placement(): string {
return this.specificPlacement || 'top';
protected get _placement(): string {
return this.placement || 'top';
}

protected get isVisible(): boolean {
return this.specificVisible || false;
protected get _visible(): boolean {
return (typeof this.visible !== 'undefined' ? this.visible : this.internalVisible) || false;
}

protected get mouseEnterDelay(): number {
return this.specificMouseEnterDelay || 0.15;
protected get _mouseEnterDelay(): number {
return this.mouseEnterDelay || 0.15;
}

protected get mouseLeaveDelay(): number {
return this.specificMouseLeaveDelay || 0.1;
protected get _mouseLeaveDelay(): number {
return this.mouseLeaveDelay || 0.1;
}

protected get overlayClassName(): string | null {
return this.specificOverlayClassName || null;
protected get _overlayClassName(): string | null {
return this.overlayClassName || null;
}

protected get overlayStyle(): NgStyleInterface | null {
return this.specificOverlayStyle || null;
protected get _overlayStyle(): NgStyleInterface | null {
return this.overlayStyle || null;
}

visible = false;
protected needProxyProperties = ['noAnimation'];
private internalVisible = false;

protected getProxyPropertyMap(): PropertyMapping {
return {
noAnimation: ['noAnimation', () => this.noAnimation]
};
}

component?: NzTooltipBaseComponent;

Expand All @@ -115,7 +124,7 @@ export abstract class NzTooltipBaseDirective implements OnChanges, OnDestroy, Af
}

if (this.component) {
this.updateChangedProperties(changes);
this.updatePropertiesByChanges(changes);
}
}

Expand Down Expand Up @@ -160,21 +169,21 @@ export abstract class NzTooltipBaseDirective implements OnChanges, OnDestroy, Af

// Remove the component's DOM because it should be in the overlay container.
this.renderer.removeChild(this.renderer.parentNode(this.elementRef.nativeElement), componentRef.location.nativeElement);
this.component.setOverlayOrigin({ elementRef: this.specificOrigin || this.elementRef });
this.component.setOverlayOrigin({ elementRef: this.origin || this.elementRef });

this.updateChangedProperties(this.needProxyProperties);
this.initProperties();

this.component.nzVisibleChange.pipe(distinctUntilChanged(), takeUntil(this.destroy$)).subscribe((visible: boolean) => {
this.visible = visible;
this.specificVisibleChange.emit(visible);
this.internalVisible = visible;
this.visibleChange.emit(visible);
});
}

protected registerTriggers(): void {
// When the method gets invoked, all properties has been synced to the dynamic component.
// After removing the old API, we can just check the directive's own `nzTrigger`.
const el = this.elementRef.nativeElement;
const trigger = this.specificTrigger;
const trigger = this.trigger;

this.removeTriggerListeners();

Expand All @@ -192,12 +201,12 @@ export abstract class NzTooltipBaseDirective implements OnChanges, OnDestroy, Af
overlayElement = this.component.overlay.overlayRef.overlayElement;
this.triggerDisposables.push(
this.renderer.listen(overlayElement, 'mouseenter', () => {
this.delayEnterLeave(false, true);
this.delayEnterLeave(false, true, this.mouseEnterDelay);
})
);
this.triggerDisposables.push(
this.renderer.listen(overlayElement, 'mouseleave', () => {
this.delayEnterLeave(false, false);
this.delayEnterLeave(false, false, this.mouseLeaveDelay);
})
);
}
Expand All @@ -213,67 +222,45 @@ export abstract class NzTooltipBaseDirective implements OnChanges, OnDestroy, Af
this.show();
})
);
} // Else do nothing because user wants to control the visibility programmatically.
}

updatePropertiesByChanges(changes: SimpleChanges): void {
const properties = {
specificTitle: ['nzTitle', this.title],
directiveNameTitle: ['nzTitle', this.title],
specificContent: ['nzContent', this.content],
directiveNameContent: ['nzContent', this.content],
specificTrigger: ['nzTrigger', this.trigger],
specificPlacement: ['nzPlacement', this.placement],
specificVisible: ['nzVisible', this.isVisible],
specificMouseEnterDelay: ['nzMouseEnterDelay', this.mouseEnterDelay],
specificMouseLeaveDelay: ['nzMouseLeaveDelay', this.mouseLeaveDelay],
specificOverlayClassName: ['nzOverlayClassName', this.overlayClassName],
specificOverlayStyle: ['nzOverlayStyle', this.overlayStyle]
}
// Else do nothing because user wants to control the visibility programmatically.
}

private updatePropertiesByChanges(changes: SimpleChanges): void {
this.updatePropertiesByKeys(Object.keys(changes));
}

private updatePropertiesByKeys(keys?: string[]): void {
const mappingProperties: PropertyMapping = {
// common mappings
title: ['nzTitle', () => this._title],
directiveTitle: ['nzTitle', () => this._title],
content: ['nzContent', () => this._content],
directiveContent: ['nzContent', () => this._content],
trigger: ['nzTrigger', () => this._trigger],
placement: ['nzPlacement', () => this._placement],
visible: ['nzVisible', () => this._visible],
mouseEnterDelay: ['nzMouseEnterDelay', () => this._mouseEnterDelay],
mouseLeaveDelay: ['nzMouseLeaveDelay', () => this._mouseLeaveDelay],
overlayClassName: ['nzOverlayClassName', () => this._overlayClassName],
overlayStyle: ['nzOverlayStyle', () => this._overlayStyle],
...this.getProxyPropertyMap()
};

const keys = Object.keys(changes);
keys.forEach((property: NzSafeAny) => {
// @ts-ignore
if (properties[property]) {
// @ts-ignore
const [name, value] = properties[property];
this.updateComponentValue(name, value);
(keys || Object.keys(mappingProperties).filter(key => !key.startsWith('directive'))).forEach((property: NzSafeAny) => {
if (mappingProperties[property]) {
const [name, valueFn] = mappingProperties[property];
this.updateComponentValue(name, valueFn());
}
});
}

updatePropertiesByArray(): void {
this.updateComponentValue('nzTitle', this.title);
this.updateComponentValue('nzContent', this.content);
this.updateComponentValue('nzPlacement', this.placement);
this.updateComponentValue('nzTrigger', this.trigger);
this.updateComponentValue('nzVisible', this.isVisible);
this.updateComponentValue('nzMouseEnterDelay', this.mouseEnterDelay);
this.updateComponentValue('nzMouseLeaveDelay', this.mouseLeaveDelay);
this.updateComponentValue('nzOverlayClassName', this.overlayClassName);
this.updateComponentValue('nzOverlayStyle', this.overlayStyle);
}
/**
* Sync changed properties to the component and trigger change detection in that component.
*/
protected updateChangedProperties(propertiesOrChanges: string[] | SimpleChanges): void {
const isArray = Array.isArray(propertiesOrChanges);
const keys = isArray ? (propertiesOrChanges as string[]) : Object.keys(propertiesOrChanges);

keys.forEach((property: NzSafeAny) => {
if (this.needProxyProperties.indexOf(property) !== -1) {
// @ts-ignore
this.updateComponentValue(property, this[property]);
}
});
if (isArray) {
this.updatePropertiesByArray();
} else {
this.updatePropertiesByChanges(propertiesOrChanges as SimpleChanges);
}
this.component?.updateByDirective();
}

private initProperties(): void {
this.updatePropertiesByKeys();
}

private updateComponentValue(key: string, value: NzSafeAny): void {
if (typeof value !== 'undefined') {
// @ts-ignore
Expand Down
2 changes: 1 addition & 1 deletion components/tooltip/demo/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Component } from '@angular/core';
@Component({
selector: 'nz-demo-tooltip-basic',
template: `
<span nz-tooltip nzTooltipTitle="prompt text">Tooltip will show when mouse enter.</span>
<span nz-tooltip nzTooltipTitle="prompt text" [nzTooltipMouseLeaveDelay]="2">Tooltip will show when mouse enter.</span>
`
})
export class NzDemoTooltipBasicComponent {}
Loading

0 comments on commit 6b5fdee

Please sign in to comment.