Skip to content

Commit

Permalink
feat(popup): Added arrow centering, size and width properties (edcarr…
Browse files Browse the repository at this point in the history
…oll#350)

* feat(popup): Added size and width properties

* feat(popup): Added arrow centering on small popup anchors

* fix(popup): Corrected popup positioning, removed double popper update

* fix(popup): Added timeout for positioning service create
  • Loading branch information
avaneev95 authored and avohra committed Oct 6, 2018
1 parent 4ee95bb commit 4307585
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 24 deletions.
21 changes: 19 additions & 2 deletions demo/src/app/pages/modules/popup/popup.page.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</demo-page-title>
<demo-page-content>
<h2 class="ui dividing header">Examples</h2>

<demo-example [code]="exampleStandardTemplate">
<div info>
<h4 class="ui header">Popup</h4>
Expand All @@ -32,7 +32,7 @@ <h4 class="ui header">Template</h4>
</div>
<example-popup-template result></example-popup-template>
</demo-example>

<demo-example [code]="examplePlacementTemplate">
<div info>
<h4 class="ui header">Placement</h4>
Expand All @@ -47,6 +47,23 @@ <h4 class="ui header">Placement</h4>
</div>
</demo-example>

<demo-example [code]="exampleSizeTemplate">
<div info>
<h4 class="ui header">Size</h4>
<p>A popup can vary in size.</p>
</div>
<example-popup-size result></example-popup-size>
</demo-example>

<demo-example [code]="exampleWidthTemplate">
<div info>
<h4 class="ui header">Width</h4>
<p>A popup can be extra wide to allow for longer content</p>
<p>A <code>flowing</code> popup have no maximum width and continue to flow to fit its content</p>
</div>
<example-popup-width result></example-popup-width>
</demo-example>

<h4 class="ui header">Manual Popup Trigger</h4>
<p>Manually triggering a popup is shown below:</p>
<div class="ui segment">
Expand Down
83 changes: 82 additions & 1 deletion demo/src/app/pages/modules/popup/popup.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,52 @@ const examplePlacementTemplate = `
</div>
`;

const exampleWidthTemplate = `
<ng-template let-popup #popupTemplate>
<div class="content">
<div class="ui three column divided center aligned grid">
<div class="column">
<h4 class="ui header">Basic Plan</h4>
<p><b>2</b> projects, $10 a month</p>
<div class="ui button">Choose</div>
</div>
<div class="column">
<h4 class="ui header">Business Plan</h4>
<p><b>5</b> projects, $20 a month</p>
<div class="ui button">Choose</div>
</div>
<div class="column">
<h4 class="ui header">Premium Plan</h4>
<p><b>8</b> projects, $25 a month</p>
<div class="ui button">Choose</div>
</div>
</div>
</div>
</ng-template>
<i class="circular heart icon link" suiPopup popupWidth="wide"
popupText="Hello. This is a wide pop-up which allows for lots of content with additional space.
You can fit a lot of words here and the paragraphs will be pretty wide."></i>
<i class="circular heart icon link" suiPopup popupWidth="very wide"
popupText="Hello. This is a very wide pop-up which allows for lots of content with additional space.
You can fit a lot of words here and the paragraphs will be pretty wide."></i>
<br/>
<br/>
<button class="ui icon button" suiPopup popupWidth="flowing" [popupTemplate]="popupTemplate" popupTrigger="outsideClick">
Show flowing popup
</button>
`;

const exampleSizeTemplate = `
<i class="circular star icon link" suiPopup popupSize="mini" popupText="Hello, this is a mini popup"></i>
<i class="circular star icon link" suiPopup popupSize="tiny" popupText="Hello, this is a tiny popup"></i>
<i class="circular star icon link" suiPopup popupSize="small" popupText="Hello, this is a small popup"></i>
<i class="circular star icon link" suiPopup popupText="Hello, this is a standard popup"></i>
<i class="circular star icon link" suiPopup popupSize="large" popupText="Hello, this is a large popup"></i>
<i class="circular star icon link" suiPopup popupSize="huge" popupText="Hello, this is a huge popup"></i>
`;

@Component({
selector: "demo-page-popup",
templateUrl: "./popup.page.html"
Expand All @@ -56,6 +102,18 @@ export class PopupPage {
description: "Sets the placement of the popup relative to the anchor.",
defaultValue: "top left"
},
{
name: "popupSize",
type: "PopupSize",
description: "Sets the size of the popup. Available options are: <code>mini</code>, " +
"<code>tiny</code>, <code>small</code>, <code>large</code> & <code>huge</code>"
},
{
name: "popupWidth",
type: "PopupWidth",
description: "Sets the width of the popup. Available options are: <code>wide</code>, " +
"<code>very wide</code>, <code>flowing</code>"
},
{
name: "popupInverted",
type: "boolean",
Expand Down Expand Up @@ -128,6 +186,8 @@ export class PopupPage {
public exampleStandardTemplate:string = exampleStandardTemplate;
public exampleTemplateTemplate:string = exampleTemplateTemplate;
public examplePlacementTemplate:string = examplePlacementTemplate;
public exampleSizeTemplate:string = exampleSizeTemplate;
public exampleWidthTemplate:string = exampleWidthTemplate;

public placements:string[] = [
"top left",
Expand Down Expand Up @@ -207,4 +267,25 @@ export class PopupExamplePlacement {
public position:string = "right bottom";
}

export const PopupPageComponents = [PopupPage, PopupExampleStandard, PopupExampleTemplate, PopupExamplePlacement];
@Component({
selector: "example-popup-size",
template: exampleSizeTemplate,
providers: [SuiPopupConfig]
})
export class PopupExampleSize {}

@Component({
selector: "example-popup-width",
template: exampleWidthTemplate,
providers: [SuiPopupConfig]
})
export class PopupExampleWidth {}

export const PopupPageComponents = [
PopupPage,
PopupExampleStandard,
PopupExampleTemplate,
PopupExamplePlacement,
PopupExampleSize,
PopupExampleWidth
];
66 changes: 59 additions & 7 deletions src/misc/util/services/positioning.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,22 @@ export class PositioningService {
private _popper:PopperInstance;
private _popperState:Data;
private _placement:PositioningPlacement;
private _hasArrow:boolean;
private _arrowSelector:string | undefined;

public get placement():PositioningPlacement {
return this._placement;
}

public set placement(placement:PositioningPlacement) {
this._placement = placement;
this._popper.options.placement = placementToPopper(placement);
this.update();
if (this._popper) {
this._popper.options.placement = placementToPopper(placement);
}
}

public set hasArrow(hasArrow:boolean) {
this._hasArrow = hasArrow;
}

public get actualPlacement():PositioningPlacement {
Expand All @@ -141,7 +148,11 @@ export class PositioningService {
this.anchor = anchor;
this.subject = subject;
this._placement = placement;
this._arrowSelector = arrowSelector;
this.init();
}

public init():void {
const modifiers:PopperModifiers = {
computeStyle: {
gpuAcceleration: false
Expand All @@ -151,19 +162,29 @@ export class PositioningService {
boundariesElement: document.body
},
arrow: {
element: arrowSelector
element: this._arrowSelector
},
offset: {
fn: (data:Popper.Data) => {
if (this._hasArrow) {
const offsets = this.calculateOffsets();
data.offsets.popper.left += offsets.left;
data.offsets.popper.top += offsets.top;
}
return data;
}
}
};

if (!arrowSelector) {
if (!this._arrowSelector) {
delete modifiers.arrow;
}

this._popper = new Popper(
anchor.nativeElement,
subject.nativeElement,
this.anchor.nativeElement,
this.subject.nativeElement,
{
placement: placementToPopper(placement),
placement: placementToPopper(this._placement),
modifiers,
onCreate: initial => this._popperState = initial,
onUpdate: update => this._popperState = update
Expand All @@ -177,4 +198,35 @@ export class PositioningService {
public destroy():void {
this._popper.destroy();
}

private calculateOffsets():Popper.Offset {
let left = 0; let top = 0;

// To support correct positioning for all popup sizes we should calculate offset using em
const fontSize = parseFloat(window.getComputedStyle(this.subject.nativeElement).getPropertyValue("font-size"));
// The Semantic UI popup arrow width and height are 0.71428571em and the margin from the popup edge is 1em
const arrowCenter = (0.71428571 / 2 + 1) * fontSize;

if (this.anchor.nativeElement.offsetWidth <= arrowCenter * 2) {
const anchorCenterWidth = this.anchor.nativeElement.offsetWidth / 2;
if (this._placement === PositioningPlacement.TopLeft || this._placement === PositioningPlacement.BottomLeft) {
left = anchorCenterWidth - arrowCenter;
}
if (this._placement === PositioningPlacement.TopRight || this._placement === PositioningPlacement.BottomRight) {
left = arrowCenter - anchorCenterWidth;
}
}

if (this.anchor.nativeElement.offsetHeight <= arrowCenter * 2) {
const anchorCenterHeight = this.anchor.nativeElement.offsetHeight / 2;
if (this._placement === PositioningPlacement.LeftTop || this._placement === PositioningPlacement.RightTop) {
top = anchorCenterHeight - arrowCenter;
}
if (this._placement === PositioningPlacement.LeftBottom || this._placement === PositioningPlacement.RightBottom) {
top = arrowCenter - anchorCenterHeight;
}
}
return { top, left, width: 0, height: 0 };
}

}
4 changes: 4 additions & 0 deletions src/modules/popup/classes/popup-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { ITemplateRefContext, PositioningPlacement } from "../../../misc/util/in
import { IPopup } from "./popup-controller";

export type PopupTrigger = "hover" | "click" | "outsideClick" | "focus" | "manual";
export type PopupSize = "mini" | "tiny" | "small" | "large" | "huge";
export type PopupWidth = "wide" | "very wide" | "flowing";

export const PopupTrigger = {
Hover: "hover" as PopupTrigger,
Expand Down Expand Up @@ -35,6 +37,8 @@ export class PopupConfig implements IPopupConfig {
public delay:number;
public isBasic:boolean;
public transition:string;
public size:PopupSize;
public width:PopupWidth;
public transitionDuration:number;
public isFlowing:boolean;
public isInline:boolean;
Expand Down
37 changes: 24 additions & 13 deletions src/modules/popup/components/popup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ import { TemplatePopupConfig } from "../classes/popup-template-controller";
<div #templateSibling></div>
<sui-popup-arrow *ngIf="!config.isBasic"
[placement]="positioningService.actualPlacement"
[placement]="config.placement"
[inverted]="config.isInverted"></sui-popup-arrow>
</div>
`,
styles: [`
.ui.popup {
/* Autofit popup to the contents. */
right: auto;
margin: 0;
}
.ui.animating.popup {
Expand Down Expand Up @@ -63,6 +64,7 @@ export class SuiPopup implements IPopup {

public transitionController:TransitionController;
public positioningService:PositioningService;
private _anchor:ElementRef;

// Keeps track of whether the popup is open internally.
private _isOpen:boolean;
Expand All @@ -83,22 +85,18 @@ export class SuiPopup implements IPopup {
private _container:ViewContainerRef;

public set anchor(anchor:ElementRef) {
// Whenever the anchor is set (which is when the popup is created), recreate the positioning service with the appropriate options.
this.positioningService = new PositioningService(anchor, this._container.element, this.config.placement, ".dynamic.arrow");
this._anchor = anchor;
}

// Returns the direction (`top`, `left`, `right`, `bottom`) of the current placement.
public get direction():string | undefined {
if (this.positioningService) {
return this.positioningService.actualPlacement.split(" ").shift();
}
// We need to set direction attribute before popper init to allow correct positioning
return this.config.placement.split(" ").shift();
}

// Returns the alignment (`top`, `left`, `right`, `bottom`) of the current placement.
public get alignment():string | undefined {
if (this.positioningService) {
return this.positioningService.actualPlacement.split(" ").pop();
}
return this.config.placement.split(" ").pop();
}

public get dynamicClasses():IDynamicClasses {
Expand All @@ -118,6 +116,12 @@ export class SuiPopup implements IPopup {
if (this.config.isFlowing) {
classes.flowing = true;
}
if (this.config.size) {
classes[this.config.size] = true;
}
if (this.config.width) {
classes[this.config.width] = true;
}
return classes;
}

Expand Down Expand Up @@ -145,6 +149,17 @@ export class SuiPopup implements IPopup {
// Cancel the closing timer.
clearTimeout(this.closingTimeout);

// Create positioning service after a brief delay.
setTimeout(() => {
this.positioningService = new PositioningService(
this._anchor,
this._container.element,
this.config.placement,
".dynamic.arrow"
);
this.positioningService.hasArrow = !this.config.isBasic;
});

// Cancel all other transitions, and initiate the opening transition.
this.transitionController.stopAll();
this.transitionController.animate(
Expand All @@ -159,10 +174,6 @@ export class SuiPopup implements IPopup {
}
}));

// Refresh the popup position after a brief delay to allow for browser processing time.
this.positioningService.placement = this.config.placement;
setTimeout(() => this.positioningService.update());

// Finally, set the popup to be open.
this._isOpen = true;
this.onOpen.emit();
Expand Down
12 changes: 11 additions & 1 deletion src/modules/popup/directives/popup.directive.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Directive, Input, ElementRef, TemplateRef, Renderer2 } from "@angular/core";
import { ITemplateRefContext, Util, PositioningPlacement, SuiComponentFactory } from "../../../misc/util/internal";
import { SuiPopup } from "../components/popup";
import { PopupConfig, PopupTrigger } from "../classes/popup-config";
import { PopupConfig, PopupTrigger, PopupSize, PopupWidth } from "../classes/popup-config";
import { SuiPopupConfig } from "../services/popup.service";
import { SuiPopupController } from "../classes/popup-controller";
import { SuiPopupTemplateController, ITemplatePopupContext, ITemplatePopupConfig } from "../classes/popup-template-controller";
Expand Down Expand Up @@ -58,6 +58,16 @@ export class SuiPopupDirective<T> extends SuiPopupTemplateController<T> {
this.popup.config.placement = placement;
}

@Input()
public set popupWidth(width:PopupWidth) {
this.popup.config.width = width;
}

@Input()
public set popupSize(size:PopupSize) {
this.popup.config.size = size;
}

@Input()
public set popupDelay(delay:number) {
this.popup.config.delay = delay;
Expand Down

0 comments on commit 4307585

Please sign in to comment.