From 36799b0f190f74021c5ca9a7d9e730b31ecf2b3f Mon Sep 17 00:00:00 2001 From: arturovt Date: Tue, 9 May 2023 13:33:08 +0300 Subject: [PATCH] feat: Do not trigger change detection internally on `mouseenter` --- src/lib/picker/category.component.ts | 8 +-- .../picker/ngx-emoji/emoji.component.spec.ts | 27 ++++++++ src/lib/picker/ngx-emoji/emoji.component.ts | 66 +++++++++++-------- src/lib/picker/picker.component.html | 2 +- src/lib/picker/picker.component.spec.ts | 2 +- 5 files changed, 70 insertions(+), 35 deletions(-) diff --git a/src/lib/picker/category.component.ts b/src/lib/picker/category.component.ts index 506539c..3ce9ce1 100644 --- a/src/lib/picker/category.component.ts +++ b/src/lib/picker/category.component.ts @@ -49,7 +49,7 @@ import { EmojiFrequentlyService } from './emoji-frequently.service'; [imageUrlFn]="emojiImageUrlFn" [hideObsolete]="hideObsolete" [useButton]="emojiUseButton" - (emojiOver)="emojiOver.emit($event)" + (emojiOverOutsideAngular)="emojiOverOutsideAngular.emit($event)" (emojiLeaveOutsideAngular)="emojiLeaveOutsideAngular.emit($event)" (emojiClick)="emojiClick.emit($event)" > @@ -93,7 +93,7 @@ import { EmojiFrequentlyService } from './emoji-frequently.service'; [imageUrlFn]="emojiImageUrlFn" [hideObsolete]="hideObsolete" [useButton]="emojiUseButton" - (emojiOver)="emojiOver.emit($event)" + (emojiOverOutsideAngular)="emojiOverOutsideAngular.emit($event)" (emojiLeaveOutsideAngular)="emojiLeaveOutsideAngular.emit($event)" (emojiClick)="emojiClick.emit($event)" > @@ -126,12 +126,12 @@ export class CategoryComponent implements OnChanges, OnInit, AfterViewInit { @Input() emojiBackgroundImageFn?: Emoji['backgroundImageFn']; @Input() emojiImageUrlFn?: Emoji['imageUrlFn']; @Input() emojiUseButton?: boolean; - @Output() emojiOver: Emoji['emojiOver'] = new EventEmitter(); + @Output() emojiClick: Emoji['emojiClick'] = new EventEmitter(); /** * Note: the suffix is added explicitly so we know the event is dispatched outside of the Angular zone. */ + @Output() emojiOverOutsideAngular: Emoji['emojiOver'] = new EventEmitter(); @Output() emojiLeaveOutsideAngular: Emoji['emojiLeave'] = new EventEmitter(); - @Output() emojiClick: Emoji['emojiClick'] = new EventEmitter(); @ViewChild('container', { static: true }) container!: ElementRef; @ViewChild('label', { static: true }) label!: ElementRef; containerStyles: any = {}; diff --git a/src/lib/picker/ngx-emoji/emoji.component.spec.ts b/src/lib/picker/ngx-emoji/emoji.component.spec.ts index ce0985e..11a2703 100644 --- a/src/lib/picker/ngx-emoji/emoji.component.spec.ts +++ b/src/lib/picker/ngx-emoji/emoji.component.spec.ts @@ -4,6 +4,33 @@ import { TestBed } from '@angular/core/testing'; import { EmojiModule } from './emoji.module'; describe('EmojiComponent', () => { + it('should trigger change detection whenever `emojiOver` has observers', () => { + @Component({ + template: '', + }) + class TestComponent { + onEmojiOver() {} + } + + TestBed.configureTestingModule({ + imports: [EmojiModule], + declarations: [TestComponent], + }); + + const fixture = TestBed.createComponent(TestComponent); + fixture.detectChanges(); + + const appRef = TestBed.inject(ApplicationRef); + spyOn(appRef, 'tick'); + spyOn(fixture.componentInstance, 'onEmojiOver'); + + const emoji = fixture.nativeElement.querySelector('span.emoji-mart-emoji'); + emoji.dispatchEvent(new MouseEvent('mouseenter')); + + expect(appRef.tick).toHaveBeenCalledTimes(1); + expect(fixture.componentInstance.onEmojiOver).toHaveBeenCalled(); + }); + it('should trigger change detection whenever `emojiLeave` has observers', () => { @Component({ template: '', diff --git a/src/lib/picker/ngx-emoji/emoji.component.ts b/src/lib/picker/ngx-emoji/emoji.component.ts index 69f505f..d439be9 100644 --- a/src/lib/picker/ngx-emoji/emoji.component.ts +++ b/src/lib/picker/ngx-emoji/emoji.component.ts @@ -49,7 +49,6 @@ export interface EmojiEvent { #button type="button" (click)="handleClick($event)" - (mouseenter)="handleOver($event)" [attr.title]="title" [attr.aria-label]="label" class="emoji-mart-emoji" @@ -67,7 +66,6 @@ export interface EmojiEvent { + this.button$.pipe( // Note: `EMPTY` is used to remove event listener once the DOM node is removed. - switchMap(button => (button ? fromEvent(button, 'mouseleave') : EMPTY)), + switchMap(button => (button ? fromEvent(button, eventName) : EMPTY)), takeUntil(this.destroy$), - ) - .subscribe($event => { - const emoji = this.getSanitizedData(); - this.emojiLeaveOutsideAngular.emit({ emoji, $event }); - // Note: this is done for backwards compatibility. We run change detection if developers - // are listening to `emojiLeave` in their code. For instance: - // ``. - if (this.emojiLeave.observed) { - this.ngZone.run(() => this.emojiLeave.emit({ emoji, $event })); - } - }); + ); + + eventListener$('mouseenter').subscribe($event => { + const emoji = this.getSanitizedData(); + this.emojiOverOutsideAngular.emit({ emoji, $event }); + // Note: this is done for backwards compatibility. We run change detection if developers + // are listening to `emojiOver` in their code. For instance: + // ``. + if (this.emojiOver.observed) { + this.ngZone.run(() => this.emojiOver.emit({ emoji, $event })); + } + }); + + eventListener$('mouseleave').subscribe($event => { + const emoji = this.getSanitizedData(); + this.emojiLeaveOutsideAngular.emit({ emoji, $event }); + // Note: this is done for backwards compatibility. We run change detection if developers + // are listening to `emojiLeave` in their code. For instance: + // ``. + if (this.emojiLeave.observed) { + this.ngZone.run(() => this.emojiLeave.emit({ emoji, $event })); + } + }); } } diff --git a/src/lib/picker/picker.component.html b/src/lib/picker/picker.component.html index 8e539cd..a691584 100644 --- a/src/lib/picker/picker.component.html +++ b/src/lib/picker/picker.component.html @@ -51,7 +51,7 @@ [emojiBackgroundImageFn]="backgroundImageFn" [emojiImageUrlFn]="imageUrlFn" [emojiUseButton]="useButton" - (emojiOver)="handleEmojiOver($event)" + (emojiOverOutsideAngular)="handleEmojiOver($event)" (emojiLeaveOutsideAngular)="handleEmojiLeave()" (emojiClick)="handleEmojiClick($event)" > diff --git a/src/lib/picker/picker.component.spec.ts b/src/lib/picker/picker.component.spec.ts index 897324e..36ca190 100644 --- a/src/lib/picker/picker.component.spec.ts +++ b/src/lib/picker/picker.component.spec.ts @@ -29,7 +29,7 @@ describe('PickerComponent', () => { fixture.detectChanges(); }); - it('should update preview on `mouseleave` but should not trigger change detection', fakeAsync(() => { + it('should update preview on `mouseenter` and `mouseleave` but should not trigger change detection', fakeAsync(() => { const picker = fixture.debugElement.query(By.directive(PickerComponent)); expect(picker.componentInstance.previewEmoji).toEqual(null);