Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(positioning): auto option for positioning #1986

Merged
merged 3 commits into from
Aug 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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