Skip to content

Commit

Permalink
feat(overlay): add noop trigger strategy (#1133)
Browse files Browse the repository at this point in the history
  • Loading branch information
nnixaa authored and Tibing committed Jan 8, 2019
1 parent 6cda5e7 commit 1e43929
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 10 deletions.
6 changes: 6 additions & 0 deletions src/app/playground-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,12 @@ export const PLAYGROUND_COMPONENTS: ComponentLink[] = [
component: 'PopoverTestComponent',
name: 'Popover Test',
},
{
path: 'popover-noop.component',
link: '/popover/popover-noop.component',
component: 'PopoverNoopComponent',
name: 'Popover Noop',
},
],
},
{
Expand Down
55 changes: 55 additions & 0 deletions src/framework/theme/components/cdk/overlay/overlay-trigger.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,3 +252,58 @@ describe('focus-trigger-strategy', () => {
expect(showSpy).toHaveBeenCalledTimes(1);
}));
});

describe('noop-trigger-strategy', () => {
let triggerStrategyBuilder: NbTriggerStrategyBuilderService;
let document: Document;
let host: HTMLElement;
let container: HTMLElement;

beforeEach(() => {
const bed = TestBed.configureTestingModule({
providers: [
NbTriggerStrategyBuilderService,
{ provide: NB_DOCUMENT, useExisting: DOCUMENT },
],
});
document = bed.get(NB_DOCUMENT);
triggerStrategyBuilder = bed.get(NbTriggerStrategyBuilderService);
});

beforeEach(() => {
host = createElement();
container = createElement();
triggerStrategyBuilder
.trigger(NbTrigger.NOOP)
.host(host)
.container(withContainer(container));
});

it('should NOT fire show$ when hover/click/focus on host', fakeAsync(() => {
const showSpy = createSpy('showSpy');
const triggerStrategy = triggerStrategyBuilder
.container(() => null)
.build();
triggerStrategy.show$.subscribe(showSpy);

focus(host);
click(host);
mouseEnter(host);
tick(100);

expect(showSpy).toHaveBeenCalledTimes(0);
}));

it('should NOT fire hide$ when hover out from host', fakeAsync(() => {
const showSpy = createSpy('showSpy');
const triggerStrategy = triggerStrategyBuilder.build();

triggerStrategy.hide$.subscribe(showSpy);
mouseLeave(host);
blur(host);
focus(document);
tick(100);

expect(showSpy).toHaveBeenCalledTimes(0);
}));
});
23 changes: 17 additions & 6 deletions src/framework/theme/components/cdk/overlay/overlay-trigger.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { ComponentRef, Inject, Injectable } from '@angular/core';
import { fromEvent as observableFromEvent, merge as observableMerge, Observable } from 'rxjs';
import { EMPTY, fromEvent as observableFromEvent, merge as observableMerge, Observable } from 'rxjs';
import { debounceTime, delay, filter, repeat, share, switchMap, takeUntil, takeWhile, map } from 'rxjs/operators';
import { NB_DOCUMENT } from '../../../theme.options';

export enum NbTrigger {
NOOP = 'noop',
CLICK = 'click',
HOVER = 'hover',
HINT = 'hint',
Expand All @@ -15,11 +16,16 @@ export enum NbTrigger {
* Each stream provides different events depends on implementation.
* We have three main trigger strategies: click, hint and hover.
* */
export interface NbTriggerStrategy {
show$: Observable<never | Event>;
hide$: Observable<never | Event>;
}

/**
* TODO maybe we have to use renderer.listen instead of observableFromEvent?
* Renderer provides capability use it in service worker, ssr and so on.
* */
export abstract class NbTriggerStrategy {
export abstract class NbTriggerStrategyBase implements NbTriggerStrategy {

protected isNotOnHostOrContainer(event: Event): boolean {
return !this.isOnHost(event) && !this.isOnContainer(event);
Expand Down Expand Up @@ -58,7 +64,7 @@ export abstract class NbTriggerStrategy {
* Fires close event when the click was performed on the document but
* not on the host or container.
* */
export class NbClickTriggerStrategy extends NbTriggerStrategy {
export class NbClickTriggerStrategy extends NbTriggerStrategyBase {

// since we should track click for both SHOW and HIDE event we firstly need to track the click and the state
// of the container and then later on decide should we hide it or show
Expand Down Expand Up @@ -91,7 +97,7 @@ export class NbClickTriggerStrategy extends NbTriggerStrategy {
* Fires open event when a mouse hovers over the host element and stay over at least 100 milliseconds.
* Fires close event when the mouse leaves the host element and stops out of the host and popover container.
* */
export class NbHoverTriggerStrategy extends NbTriggerStrategy {
export class NbHoverTriggerStrategy extends NbTriggerStrategyBase {

show$: Observable<Event> = observableFromEvent<Event>(this.host, 'mouseenter')
.pipe(
Expand Down Expand Up @@ -122,7 +128,7 @@ export class NbHoverTriggerStrategy extends NbTriggerStrategy {
* Fires open event when a mouse hovers over the host element and stay over at least 100 milliseconds.
* Fires close event when the mouse leaves the host element.
* */
export class NbHintTriggerStrategy extends NbTriggerStrategy {
export class NbHintTriggerStrategy extends NbTriggerStrategyBase {
show$: Observable<Event> = observableFromEvent<Event>(this.host, 'mouseenter')
.pipe(
takeWhile(() => this.isHostInBody()),
Expand All @@ -142,7 +148,7 @@ export class NbHintTriggerStrategy extends NbTriggerStrategy {
* Fires open event when a focus is on the host element and stay over at least 100 milliseconds.
* Fires close event when the focus leaves the host element.
* */
export class NbFocusTriggerStrategy extends NbTriggerStrategy {
export class NbFocusTriggerStrategy extends NbTriggerStrategyBase {

protected focusOut$: Observable<Event> = observableFromEvent<Event>(this.host, 'focusout')
.pipe(
Expand Down Expand Up @@ -223,6 +229,11 @@ export class NbTriggerStrategyBuilderService {
return new NbHoverTriggerStrategy(this._document, this._host, this._container);
case NbTrigger.FOCUS:
return new NbFocusTriggerStrategy(this._document, this._host, this._container);
case NbTrigger.NOOP:
return {
show$: EMPTY,
hide$: EMPTY,
};
default:
throw new Error('Trigger have to be provided');
}
Expand Down
11 changes: 9 additions & 2 deletions src/framework/theme/components/popover/popover.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,22 @@ import { NbPopoverComponent } from './popover.component';
*
* Also popover has some different modes which provides capability show$ and hide$ popover in different ways:
*
* - Click mode popover shows when a user clicking on the host element and hides when the user clicks
* - Click popover mode shows when a user clicking on the host element and hides when the user clicks
* somewhere on the document except popover.
* - Hint mode provides capability show$ popover when the user hovers on the host element
* and hide$ popover when user hovers out of the host.
* - Hover mode works like hint mode with one exception - when the user moves mouse from host element to
* the container element popover will not be hidden.
* - Focus mode is applied when user focuses the element.
* - Noop mode - the popover won't react based on user interaction.
*
* @stacked-example(Available Modes, popover/popover-modes.component.html)
*
* Noop mode is especially useful when you need to control Popover programmatically, for example show/hide
* as a result of some third-party action, like HTTP request or validation check:
*
* @stacked-example(Manual control, popover/popover-noop.component)
*
* @additional-example(Template Ref, popover/popover-template-ref.component)
* @additional-example(Custom Component, popover/popover-custom-component.component)
* */
Expand Down Expand Up @@ -153,7 +160,7 @@ export class NbPopoverDirective implements AfterViewInit, OnDestroy {

/**
* Describes when the container will be shown.
* Available options: click, hover and hint
* Available options: `click`, `hover`, `hint`, `focus` and `noop`
* */
@Input('nbPopoverTrigger')
trigger: NbTrigger = NbTrigger.CLICK;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<input placeholder="Enter your username" nbInput nbPopover="Username is already taken" nbPopoverTrigger="noop">

<div>
<button nbButton size="small" status="success" (click)="open()">Open Popover</button>
<button nbButton size="small" status="danger" (click)="close()">Close Popover</button>
</div>
37 changes: 37 additions & 0 deletions src/playground/with-layout/popover/popover-noop.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* @license
* Copyright Akveo. All Rights Reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import { Component, ViewChild } from '@angular/core';
import { NbPopoverDirective } from '@nebular/theme';


@Component({
selector: 'nb-popover-noop',
templateUrl: './popover-noop.component.html',
styles: [`
:host {
display: block;
margin: 5rem;
}
button {
margin-right: 1rem;
margin-top: 1rem;
}
`],
})
export class PopoverNoopComponent {

@ViewChild(NbPopoverDirective) popover: NbPopoverDirective;

open() {
this.popover.show();
}

close() {
this.popover.hide();
}

}
5 changes: 5 additions & 0 deletions src/playground/with-layout/popover/popover-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { PopoverPlacementsComponent } from './popover-placements.component';
import { PopoverShowcaseComponent } from './popover-showcase.component';
import { PopoverTemplateRefComponent } from './popover-template-ref.component';
import { PopoverTestComponent } from './popover-test.component';
import { PopoverNoopComponent } from './popover-noop.component';

const routes: Route[] = [
{
Expand All @@ -38,6 +39,10 @@ const routes: Route[] = [
path: 'popover-test.component',
component: PopoverTestComponent,
},
{
path: 'popover-noop.component',
component: PopoverNoopComponent,
},
];

@NgModule({
Expand Down
6 changes: 4 additions & 2 deletions src/playground/with-layout/popover/popover.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { NgModule } from '@angular/core';
import { NbCardModule, NbPopoverModule } from '@nebular/theme';
import { NbButtonModule, NbCardModule, NbInputModule, NbPopoverModule } from '@nebular/theme';
import { DynamicToAddComponent } from './components/dynamic.component';
import { PopoverRoutingModule } from './popover-routing.module';
import { PopoverCustomComponentComponent } from './popover-custom-component.component';
Expand All @@ -14,6 +14,7 @@ import { PopoverPlacementsComponent } from './popover-placements.component';
import { PopoverShowcaseComponent } from './popover-showcase.component';
import { PopoverTemplateRefComponent } from './popover-template-ref.component';
import { PopoverTestComponent } from './popover-test.component';
import { PopoverNoopComponent } from './popover-noop.component';

@NgModule({
declarations: [
Expand All @@ -24,8 +25,9 @@ import { PopoverTestComponent } from './popover-test.component';
PopoverShowcaseComponent,
PopoverTemplateRefComponent,
PopoverTestComponent,
PopoverNoopComponent,
],
imports: [ NbPopoverModule, NbCardModule, PopoverRoutingModule ],
imports: [ NbPopoverModule, NbCardModule, NbButtonModule, NbInputModule, PopoverRoutingModule ],
entryComponents: [ DynamicToAddComponent ],
})
export class PopoverModule {}

0 comments on commit 1e43929

Please sign in to comment.