diff --git a/demo/src/app/components/+popover/demos/four-directions/four-directions.html b/demo/src/app/components/+popover/demos/four-directions/four-directions.html
index 20131853d9..94931e0961 100644
--- a/demo/src/app/components/+popover/demos/four-directions/four-directions.html
+++ b/demo/src/app/components/+popover/demos/four-directions/four-directions.html
@@ -25,3 +25,10 @@
placement="right">
Popover on right
+
+
diff --git a/demo/src/app/components/+popover/popover-section.component.ts b/demo/src/app/components/+popover/popover-section.component.ts
index fd5c68386c..4e4baed99c 100644
--- a/demo/src/app/components/+popover/popover-section.component.ts
+++ b/demo/src/app/components/+popover/popover-section.component.ts
@@ -51,6 +51,7 @@ let titleDoc = require('html-loader!markdown-loader!./docs/title.md');
Four directions
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.
diff --git a/demo/src/app/components/+tooltip/demos/four-directions/four-directions.html b/demo/src/app/components/+tooltip/demos/four-directions/four-directions.html
index 6d31dc4aea..b3f67667e4 100644
--- a/demo/src/app/components/+tooltip/demos/four-directions/four-directions.html
+++ b/demo/src/app/components/+tooltip/demos/four-directions/four-directions.html
@@ -21,3 +21,9 @@
placement="right">
Tooltip on right
+
+
diff --git a/demo/src/app/components/+tooltip/tooltip-section.component.ts b/demo/src/app/components/+tooltip/tooltip-section.component.ts
index 5ad1ac88f2..0d9a2c3f34 100644
--- a/demo/src/app/components/+tooltip/tooltip-section.component.ts
+++ b/demo/src/app/components/+tooltip/tooltip-section.component.ts
@@ -48,6 +48,7 @@ let titleDoc = require('html-loader!markdown-loader!./docs/title.md');
Four directions
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.
diff --git a/src/popover/popover.config.ts b/src/popover/popover.config.ts
index 884f2dcd4a..729f934609 100644
--- a/src/popover/popover.config.ts
+++ b/src/popover/popover.config.ts
@@ -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';
/**
diff --git a/src/popover/popover.directive.ts b/src/popover/popover.directive.ts
index d14c4efd2f..f57fbe08b6 100644
--- a/src/popover/popover.directive.ts
+++ b/src/popover/popover.directive.ts
@@ -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
*/
diff --git a/src/positioning/ng-positioning.ts b/src/positioning/ng-positioning.ts
index 56ba83762c..921598bff8 100644
--- a/src/positioning/ng-positioning.ts
+++ b/src/positioning/ng-positioning.ts
@@ -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 = {
@@ -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));
@@ -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';
}
diff --git a/src/spec/ng-bootstrap/popover.spec.ts b/src/spec/ng-bootstrap/popover.spec.ts
index ae64e80cc5..88d386502b 100644
--- a/src/spec/ng-bootstrap/popover.spec.ts
+++ b/src/spec/ng-bootstrap/popover.spec.ts
@@ -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(``);
+ 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(``);
+ 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(``);
+ 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(``);
+ 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(``);
+ 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', () => {
diff --git a/src/spec/ng-bootstrap/tooltip.spec.ts b/src/spec/ng-bootstrap/tooltip.spec.ts
index 44588fd17d..0e8c5a6ade 100644
--- a/src/spec/ng-bootstrap/tooltip.spec.ts
+++ b/src/spec/ng-bootstrap/tooltip.spec.ts
@@ -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(``);
+ 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', () => {