Skip to content

Commit

Permalink
Merge pull request #90 from edcarroll/develop
Browse files Browse the repository at this point in the history
v0.8.1 into master
  • Loading branch information
edcarroll authored Jun 5, 2017
2 parents b40f28c + 9f876ef commit a8c2687
Show file tree
Hide file tree
Showing 18 changed files with 217 additions and 60 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Now you're good to go!

## Dependencies

* [Angular](https://angular.io) (^4.0.0)
* [Angular](https://angular.io) (^4.1.0)
* [Semantic UI CSS](http://semantic-ui.com/) (jQuery is **not** required)

## Components
Expand Down
3 changes: 0 additions & 3 deletions components/collapse/collapse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@ export class SuiCollapse {
return this._pristine ? 0 : this.collapseDuration;
}

// Timer for window resize counter.
private _resizeTimeout:number;

public constructor(private _element:ElementRef, private _renderer:Renderer2) {
this._pristine = true;

Expand Down
5 changes: 5 additions & 0 deletions components/modal/modal-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export class ModalConfig<T, U = null, V = null> {
// Whether or not the modal has basic styles applied.
public isBasic:boolean;

// Whether or not the modal should always display a scrollbar.
public mustScroll:boolean;

// Transition to display modal with.
public transition:string;
// Duration of the modal & dimmer transitions.
Expand All @@ -41,6 +44,8 @@ export class ModalConfig<T, U = null, V = null> {
this.isFullScreen = false;
this.isBasic = false;

this.mustScroll = false;

this.transition = "scale";
this.transitionDuration = 500;
}
Expand Down
6 changes: 5 additions & 1 deletion components/modal/modal.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class SuiModalService {
$implicit: modal.context,
// `let-modal="modal"`
modal: componentRef.instance.controls
});
})
}
// If the config is for a component based modal,
else if (modal instanceof ComponentModalConfig) {
Expand Down Expand Up @@ -59,6 +59,10 @@ export class SuiModalService {
contentElement.remove();
}

// Remove the template div from the DOM. This is to fix some styling issues.
const templateElement = modalComponent.templateSibling.element.nativeElement as Element;
templateElement.remove();

// Attach the new modal component to the application.
this._applicationRef.attachView(componentRef.hostView);

Expand Down
65 changes: 59 additions & 6 deletions components/modal/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {ModalConfig, ModalSize} from './modal-config';
[class.active]="transitionController?.isVisible"
[class.fullscreen]="isFullScreen"
[class.basic]="isBasic"
[class.scrolling]="mustScroll"
#modal>
<!-- Configurable close icon -->
Expand All @@ -25,7 +26,15 @@ import {ModalConfig, ModalSize} from './modal-config';
<!-- @ViewChild reference so we can insert elements beside this div. -->
<div #templateSibling></div>
</div>
`
`,
styles: [`
.scrolling {
position: absolute !important;
margin-top: 3.5rem !important;
margin-bottom: 3.5rem !important;
top: 0;
}
`]
})
export class SuiModal<T, U> implements OnInit, AfterViewInit {
@Input()
Expand Down Expand Up @@ -83,6 +92,23 @@ export class SuiModal<T, U> implements OnInit, AfterViewInit {
@Input()
public isBasic:boolean;

// Whether the modal currently is displaying a scrollbar.
private _mustScroll:boolean;
// Whether or not the modal should always display a scrollbar.
private _mustAlwaysScroll:boolean;

@Input()
public get mustScroll() {
return this._mustScroll;
}

public set mustScroll(mustScroll:boolean) {
this._mustScroll = mustScroll;
// 'Cache' value in _mustAlwaysScroll so that if `true`, _mustScroll isn't ever auto-updated.
this._mustAlwaysScroll = mustScroll;
this.updateScroll();
}

public transitionController:TransitionController;

// Transition to display modal with.
Expand Down Expand Up @@ -124,16 +150,21 @@ export class SuiModal<T, U> implements OnInit, AfterViewInit {
}

public ngOnInit() {
// Transition the modal to be visible.
this.transitionController.animate(new Transition(this.transition, this.transitionDuration, TransitionDirection.In));
// Use a slight delay as the `<sui-dimmer>` cancels the initial transition.
setTimeout(() => this.dimBackground = true);
setTimeout(() => {
// Transition the modal to be visible.
this.transitionController.animate(new Transition(this.transition, this.transitionDuration, TransitionDirection.In))
this.dimBackground = true;
});
}

public ngAfterViewInit() {
// Update margin offset to center modal correctly on-screen.
const element = this._modalElement.nativeElement as Element;
this._renderer.setStyle(element, "margin-top", `-${element.clientHeight / 2}px`);
setTimeout(() => {
this._renderer.setStyle(element, "margin-top", `-${element.clientHeight / 2}px`);
this.updateScroll();
});
}

// Updates the modal with the specified configuration.
Expand All @@ -145,6 +176,8 @@ export class SuiModal<T, U> implements OnInit, AfterViewInit {
this.isFullScreen = config.isFullScreen;
this.isBasic = config.isBasic;

this.mustScroll = config.mustScroll;

this.transition = config.transition;
this.transitionDuration = config.transitionDuration;
}
Expand Down Expand Up @@ -175,11 +208,31 @@ export class SuiModal<T, U> implements OnInit, AfterViewInit {
}
}

// Decides whether the modal needs to reposition to allow scrolling.
private updateScroll() {
// Semantic UI modal margin is 3.5rem, which is relative to the global font size, so for compatibility:
const fontSize = parseFloat(window.getComputedStyle(document.documentElement, null).getPropertyValue('font-size'));
const margin = fontSize * 3.5;

// _mustAlwaysScroll works by stopping _mustScroll from being automatically updated, so it stays `true`.
if (!this._mustAlwaysScroll && this._modalElement) {
const element = this._modalElement.nativeElement as Element;

// The modal must scroll if the window height is smaller than the modal height + both margins.
this._mustScroll = window.innerHeight < element.clientHeight + margin * 2;
}
}

@HostListener("document:keyup", ["$event"])
public onKeyup(e:KeyboardEvent) {
public onDocumentKeyup(e:KeyboardEvent) {
if (e.keyCode == KeyCode.Escape) {
// Close automatically covers case of `!isClosable`, so check not needed.
this.close();
}
}

@HostListener("window:resize")
public onDocumentResize() {
this.updateScroll();
}
}
4 changes: 2 additions & 2 deletions components/popup/popup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import Popper from "popper.js";
<div class="content">{{ config.text }}</div>
</ng-container>
<div #templateSibling></div>
<sui-popup-arrow *ngIf="!config.isBasic" [placement]="_positioningService.placement" [inverted]="config.isInverted"></sui-popup-arrow>
<sui-popup-arrow *ngIf="!config.isBasic" [placement]="_positioningService.actualPlacement" [inverted]="config.isInverted"></sui-popup-arrow>
</div>
`,
styles: [`
Expand Down Expand Up @@ -83,7 +83,7 @@ export class SuiPopup implements IPopup {
// Returns the direction (`top`, `left`, `right`, `bottom`) of the current placement.
public get direction() {
if (this._positioningService) {
return this._positioningService.placement.split(" ").shift();
return this._positioningService.actualPlacement.split(" ").shift();
}
}

Expand Down
41 changes: 25 additions & 16 deletions components/search/search.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {readValue} from '../util/util';

// Define useful types to avoid any.
export type LookupFn<T> = (query:string) => Promise<T[]> | Promise<T>;
export type QueryLookupFn<T> = (query:string) => Promise<T[]>;
export type ItemLookupFn<T, U> = (query:string, initial:U) => Promise<T>;
export type ItemsLookupFn<T, U> = (query:string, initial:U[]) => Promise<T[]>;
export type LookupFn<T> = (query:string) => (Promise<T[]> | Promise<T> | T[] | T);
export type QueryLookupFn<T> = (query:string) => (Promise<T[]> | T[]);
export type ItemLookupFn<T, U> = (query:string, initial:U) => (Promise<T> | T);
export type ItemsLookupFn<T, U> = (query:string, initial:U[]) => (Promise<T[]> | T[]);

type CachedArray<T> = { [query:string]:T[] };

Expand Down Expand Up @@ -131,18 +131,27 @@ export class SearchService<T> {
if (this._optionsLookup) {
this._isSearching = true;

this.queryLookup(this._query)
.then(results => {
// Unset 'loading' state, and display & cache the results.
this._isSearching = false;
this.updateResults(results);
return callback(null);
})
.catch(error => {
// Unset 'loading' state, and throw the returned error without updating the results.
this._isSearching = false;
return callback(error);
});
const lookupFinished = (results:T[]) => {
this._isSearching = false;

this.updateResults(results);
return callback(null);
}

const queryLookup = this.queryLookup(this._query);

if (queryLookup instanceof Promise) {
queryLookup
.then(results => lookupFinished(results))
.catch(error => {
// Unset 'loading' state, and throw the returned error without updating the results.
this._isSearching = false;
return callback(error);
});
}
else {
lookupFinished(queryLookup);
}
return;
}

Expand Down
11 changes: 9 additions & 2 deletions components/select/multi-select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,15 @@ export class SuiMultiSelect<T, U> extends SuiSelectBase<T, U> implements AfterVi
if (values.length > 0 && this.selectedOptions.length == 0) {
if (this.valueField && this.searchService.hasItemLookup) {
// If the search service has a selected lookup function, make use of that to load the initial values.
this.searchService.itemsLookup<U>(values)
.then(r => this.selectedOptions = r);
const lookupFinished = (items:T[]) => this.selectedOptions = items;

const itemsLookup = this.searchService.itemsLookup<U>(values);
if (itemsLookup instanceof Promise) {
itemsLookup.then(r => lookupFinished(r));
}
else {
lookupFinished(itemsLookup);
}
}
else {
// Otherwise, cache the written value for when options are set.
Expand Down
18 changes: 12 additions & 6 deletions components/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,18 @@ export class SuiSelect<T, U> extends SuiSelectBase<T, U> implements CustomValueA
if (!this.selectedOption) {
if (this.valueField && this.searchService.hasItemLookup) {
// If the search service has a selected lookup function, make use of that to load the initial value.
(this.searchService.itemLookup<U>(value) as Promise<T>)
.then(r => {
this.selectedOption = r;

this.drawSelectedOption();
});
const lookupFinished = (i:T) => {
this.selectedOption = i;
this.drawSelectedOption();
}

const itemLookup = this.searchService.itemLookup<U>(value);
if (itemLookup instanceof Promise) {
itemLookup.then(r => lookupFinished(r));
}
else {
lookupFinished(itemLookup);
}
return;
}
else {
Expand Down
47 changes: 46 additions & 1 deletion components/util/positioning.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,43 @@ function placementToPopper(placement:PositioningPlacement):PopperPlacement {
return chosenPlacement.join("-") as PopperPlacement;
}

function popperToPlacement(popper:string):PositioningPlacement {
if (!popper || popper == "inherit") {
return "inherit";
}

const [direction, alignment] = popper.split("-");

let chosenPlacement = [direction];

switch (direction) {
case "top":
case "bottom":
switch (alignment) {
case "start":
chosenPlacement.push("left");
break;
case "end":
chosenPlacement.push("right");
break;
}
break;
case "left":
case "right":
switch (alignment) {
case "start":
chosenPlacement.push("top");
break;
case "end":
chosenPlacement.push("bottom");
break;
}
break;
}

return chosenPlacement.join(" ") as PositioningPlacement;
}

export class PositioningService {
public readonly anchor:ElementRef;
public readonly subject:ElementRef;
Expand All @@ -65,7 +102,7 @@ export class PositioningService {
private _popperState:Popper.Data;
private _placement:PositioningPlacement;

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

Expand All @@ -75,6 +112,14 @@ export class PositioningService {
this.update();
}

public get actualPlacement() {
if (!this._popperState) {
return PositioningPlacement.Inherit;
}

return popperToPlacement(this._popperState.placement);
}

public get state() {
return this._popperState;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ <h2 class="ui dividing header">Installation</h2>
</div>
<h2 class="ui dividing header">Dependencies</h2>
<div class="ui bulleted list">
<div class="item"><a href="https://angular.io">Angular</a> (^4.0.0)</div>
<div class="item"><a href="https://angular.io">Angular</a> (^4.1.0)</div>
<div class="item"><a href="http://semantic-ui.com/">Semantic UI CSS</a> (jQuery is <strong>not</strong> required)</div>
</div>
</demo-page-content>
Loading

0 comments on commit a8c2687

Please sign in to comment.