Skip to content

Commit

Permalink
feat(positioning): auto option for positioning (#1986)
Browse files Browse the repository at this point in the history
* feat(positioning): auto option for positioning

fixes #1111 

* Update tooltip.spec.ts

Concistensy
  • Loading branch information
vfcosta authored and valorkin committed Aug 29, 2017
1 parent 9f833eb commit 114ed42
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,10 @@
placement="right">
Popover on right
</button>

<button type="button" class="btn btn-default btn-secondary"
popover="Vivamus sagittis lacus vel augue laoreet rutrum faucibus."
popoverTitle="Popover auto"
placement="auto">
Popover auto
</button>
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ let titleDoc = require('html-loader!markdown-loader!./docs/title.md');
<!-- four directions -->
<h2 routerLink="." fragment="four-directions" id="four-directions">Four directions</h2>
Four positioning options are available: top, right, bottom, and left aligned.
Besides that, auto option may be used to detect a position that fits the component on screen.
<ng-sample-box [ts]="demos.forDirections.component" [html]="demos.forDirections.html">
<demo-popover-four-directions></demo-popover-four-directions>
</ng-sample-box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@
placement="right">
Tooltip on right
</button>

<button type="button" class="btn btn-default btn-secondary"
tooltip="Vivamus sagittis lacus vel augue laoreet rutrum faucibus."
placement="auto">
Tooltip auto
</button>
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ let titleDoc = require('html-loader!markdown-loader!./docs/title.md');
<!-- four directions -->
<h2 routerLink="." fragment="four-directions" id="four-directions">Four directions</h2>
Four positioning options are available: top, right, bottom, and left aligned.
Besides that, auto option may be used to detect a position that fits the component on screen.
<ng-sample-box [ts]="demos.forDirections.component" [html]="demos.forDirections.html">
<demo-tooltip-four-directions></demo-tooltip-four-directions>
</ng-sample-box>
Expand Down
2 changes: 1 addition & 1 deletion src/popover/popover.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Injectable } from '@angular/core';
@Injectable()
export class PopoverConfig {
/**
* Placement of a popover. Accepts: "top", "bottom", "left", "right"
* Placement of a popover. Accepts: "top", "bottom", "left", "right", "auto"
*/
public placement: string = 'top';
/**
Expand Down
2 changes: 1 addition & 1 deletion src/popover/popover.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class PopoverDirective implements OnInit, OnDestroy {
/**
* Placement of a popover. Accepts: "top", "bottom", "left", "right"
*/
@Input() public placement: 'top' | 'bottom' | 'left' | 'right';
@Input() public placement: 'top' | 'bottom' | 'left' | 'right' | 'auto';
/**
* Close popover on outside click
*/
Expand Down
23 changes: 22 additions & 1 deletion src/positioning/ng-positioning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export class Positioning {
bottom: hostElPosition.top + hostElPosition.height
};
const targetElBCR = targetElement.getBoundingClientRect();
const placementPrimary = placement.split(' ')[0] || 'top';
let placementPrimary = placement.split(' ')[0] || 'top';
const placementSecondary = placement.split(' ')[1] || 'center';

let targetElPosition: ClientRect = {
Expand All @@ -104,6 +104,13 @@ export class Positioning {
right: targetElBCR.width || targetElement.offsetWidth
};

if (placementPrimary==="auto") {
let newPlacementPrimary = this.autoPosition(targetElPosition, hostElPosition, targetElement, placementSecondary);
if (!newPlacementPrimary) newPlacementPrimary = this.autoPosition(targetElPosition, hostElPosition, targetElement);
if (newPlacementPrimary) placementPrimary = newPlacementPrimary;
targetElement.classList.add(placementPrimary);
}

switch (placementPrimary) {
case 'top':
targetElPosition.top = hostElPosition.top - (targetElement.offsetHeight + parseFloat(targetElStyles.marginBottom));
Expand Down Expand Up @@ -139,10 +146,24 @@ export class Positioning {
return targetElPosition;
}

private autoPosition(targetElPosition: ClientRect, hostElPosition: ClientRect, targetElement: HTMLElement, preferredPosition?: string) {
if ((!preferredPosition || preferredPosition==="right") && targetElPosition.left + hostElPosition.left - targetElement.offsetWidth < 0) {
return "right";
} else if ((!preferredPosition || preferredPosition==="top") && targetElPosition.bottom + hostElPosition.bottom + targetElement.offsetHeight > window.innerHeight) {
return "top";
} else if ((!preferredPosition || preferredPosition==="bottom") && targetElPosition.top + hostElPosition.top - targetElement.offsetHeight < 0) {
return "bottom";
} else if ((!preferredPosition || preferredPosition==="left") && targetElPosition.right + hostElPosition.right + targetElement.offsetWidth > window.innerWidth ) {
return "left";
}
return null;
}

private getAllStyles(element: HTMLElement) { return window.getComputedStyle(element); }

private getStyle(element: HTMLElement, prop: string): string { return (this.getAllStyles(element) as any)[prop]; }


private isStaticPositioned(element: HTMLElement): boolean {
return (this.getStyle(element, 'position') || 'static') === 'static';
}
Expand Down
90 changes: 90 additions & 0 deletions src/spec/ng-bootstrap/popover.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,96 @@ describe('popover', () => {
expect(windowEl.textContent.trim())
.toBe('Great tip!');
});

it('should set position to right when use auto position and fit on screen', () => {
const fixture = createTestComponent(`<div popover="Great tip!" placement="auto"></div>`);
const directive = fixture.debugElement.query(By.directive(PopoverDirective));

directive.triggerEventHandler('click', {});
fixture.detectChanges();
const windowEl = getWindow(fixture.nativeElement);

expect(windowEl)
.toHaveCssClass('popover');
expect(windowEl)
.toHaveCssClass('popover-auto');
expect(windowEl)
.toHaveCssClass('right');
expect(windowEl.textContent.trim())
.toBe('Great tip!');
});

it('should set position to bottom when use auto position', () => {
const fixture = createTestComponent(`<div popover="Great tip!" placement="auto bottom"></div>`);
const directive = fixture.debugElement.query(By.directive(PopoverDirective));

directive.triggerEventHandler('click', {});
fixture.detectChanges();
const windowEl = getWindow(fixture.nativeElement);

expect(windowEl)
.toHaveCssClass('popover');
expect(windowEl)
.toHaveCssClass('popover-auto');
expect(windowEl)
.toHaveCssClass('bottom');
expect(windowEl.textContent.trim())
.toBe('Great tip!');
});

it('should set position to top when use auto position and fit on screen', () => {
const fixture = createTestComponent(`<div popover="Great tip!" placement="auto top"></div>`);
const directive = fixture.debugElement.query(By.directive(PopoverDirective));

directive.triggerEventHandler('click', {});
fixture.detectChanges();
const windowEl = getWindow(fixture.nativeElement);

expect(windowEl)
.toHaveCssClass('popover');
expect(windowEl)
.toHaveCssClass('popover-auto');
expect(windowEl)
.toHaveCssClass('top');
expect(windowEl.textContent.trim())
.toBe('Great tip!');
});

it('should set position to right when use auto position and fit on screen', () => {
const fixture = createTestComponent(`<div popover="Great tip!" placement="auto right"></div>`);
const directive = fixture.debugElement.query(By.directive(PopoverDirective));

directive.triggerEventHandler('click', {});
fixture.detectChanges();
const windowEl = getWindow(fixture.nativeElement);

expect(windowEl)
.toHaveCssClass('popover');
expect(windowEl)
.toHaveCssClass('popover-auto');
expect(windowEl)
.toHaveCssClass('right');
expect(windowEl.textContent.trim())
.toBe('Great tip!');
});

it('should set position to left when use auto position and fit on screen', () => {
const fixture = createTestComponent(`<div popover="Great tip!" placement="auto left"></div>`);
const directive = fixture.debugElement.query(By.directive(PopoverDirective));

directive.triggerEventHandler('click', {});
fixture.detectChanges();
const windowEl = getWindow(fixture.nativeElement);

expect(windowEl)
.toHaveCssClass('popover');
expect(windowEl)
.toHaveCssClass('popover-auto');
expect(windowEl)
.toHaveCssClass('left');
expect(windowEl.textContent.trim())
.toBe('Great tip!');
});
});

describe('container', () => {
Expand Down
14 changes: 14 additions & 0 deletions src/spec/ng-bootstrap/tooltip.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,20 @@ describe('tooltip', () => {
expect(windowEl).toHaveCssClass('tooltip-left');
expect(windowEl.textContent.trim()).toBe('Great tip!');
});

it('should use auto position', () => {
const fixture = createTestComponent(`<div tooltip="Great tip!" placement="auto"></div>`);
const directive = fixture.debugElement.query(By.directive(TooltipDirective));

directive.triggerEventHandler('mouseover', {});
fixture.detectChanges();
const windowEl = getWindow(fixture.nativeElement);

expect(windowEl).toHaveCssClass('tooltip');
expect(windowEl).toHaveCssClass('tooltip-auto');
expect(windowEl).toHaveCssClass('left');
expect(windowEl.textContent.trim()).toBe('Great tip!');
});
});

describe('triggers', () => {
Expand Down

0 comments on commit 114ed42

Please sign in to comment.