-
Notifications
You must be signed in to change notification settings - Fork 6.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(overlay): add global position strategy
- Loading branch information
Showing
11 changed files
with
367 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,17 @@ | ||
import {PositionStrategy} from './position/position-strategy'; | ||
|
||
|
||
/** | ||
* OverlayState is a bag of values for either the initial configuration or current state of an | ||
* overlay. | ||
*/ | ||
export class OverlayState { | ||
// Not yet implemented. | ||
// TODO(jelbourn): add overlay state / configuration. | ||
/** Strategy with which to position the overlay. */ | ||
positionStrategy: PositionStrategy; | ||
|
||
// TODO(jelbourn): configuration still to add | ||
// - overlay size | ||
// - focus trap | ||
// - disable pointer events | ||
// - z-index | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,4 +15,5 @@ | |
/** A single overlay pane. */ | ||
.md-overlay-pane { | ||
position: absolute; | ||
pointer-events: auto; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
118 changes: 118 additions & 0 deletions
118
src/core/overlay/position/global-position-strategy.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import { | ||
inject, | ||
fakeAsync, | ||
flushMicrotasks, | ||
} from 'angular2/testing'; | ||
import { | ||
it, | ||
describe, | ||
expect, | ||
beforeEach, | ||
} from '../../../core/facade/testing'; | ||
import {BrowserDomAdapter} from '../../platform/browser/browser_adapter'; | ||
import {DOM} from '../../platform/dom/dom_adapter'; | ||
import {GlobalPositionStrategy} from './global-position-strategy'; | ||
|
||
|
||
export function main() { | ||
describe('GlobalPositonStrategy', () => { | ||
BrowserDomAdapter.makeCurrent(); | ||
let element: HTMLElement; | ||
let strategy: GlobalPositionStrategy; | ||
|
||
beforeEach(() => { | ||
element = DOM.createElement('div'); | ||
strategy = new GlobalPositionStrategy(); | ||
}); | ||
|
||
it('should set explicit (top, left) position to the element', fakeAsyncTest(() => { | ||
strategy.top('10px').left('40%').apply(element); | ||
|
||
flushMicrotasks(); | ||
|
||
expect(element.style.top).toBe('10px'); | ||
expect(element.style.left).toBe('40%'); | ||
expect(element.style.bottom).toBe(''); | ||
expect(element.style.right).toBe(''); | ||
})); | ||
|
||
it('should set explicit (bottom, right) position to the element', fakeAsyncTest(() => { | ||
strategy.bottom('70px').right('15em').apply(element); | ||
|
||
flushMicrotasks(); | ||
|
||
expect(element.style.top).toBe(''); | ||
expect(element.style.left).toBe(''); | ||
expect(element.style.bottom).toBe('70px'); | ||
expect(element.style.right).toBe('15em'); | ||
})); | ||
|
||
it('should overwrite previously applied positioning', fakeAsyncTest(() => { | ||
strategy.centerHorizontally().centerVertically().apply(element); | ||
flushMicrotasks(); | ||
|
||
strategy.top('10px').left('40%').apply(element); | ||
flushMicrotasks(); | ||
|
||
expect(element.style.top).toBe('10px'); | ||
expect(element.style.left).toBe('40%'); | ||
expect(element.style.bottom).toBe(''); | ||
expect(element.style.right).toBe(''); | ||
expect(element.style.transform).not.toContain('translate'); | ||
|
||
strategy.bottom('70px').right('15em').apply(element); | ||
|
||
flushMicrotasks(); | ||
|
||
expect(element.style.top).toBe(''); | ||
expect(element.style.left).toBe(''); | ||
expect(element.style.bottom).toBe('70px'); | ||
expect(element.style.right).toBe('15em'); | ||
expect(element.style.transform).not.toContain('translate'); | ||
})); | ||
|
||
it('should center the element', fakeAsyncTest(() => { | ||
strategy.centerHorizontally().centerVertically().apply(element); | ||
|
||
flushMicrotasks(); | ||
|
||
expect(element.style.top).toBe('50%'); | ||
expect(element.style.left).toBe('50%'); | ||
expect(element.style.transform).toContain('translateX(-50%)'); | ||
expect(element.style.transform).toContain('translateY(-50%)'); | ||
})); | ||
|
||
it('should center the element with an offset', fakeAsyncTest(() => { | ||
strategy.centerHorizontally('10px').centerVertically('15px').apply(element); | ||
|
||
flushMicrotasks(); | ||
|
||
expect(element.style.top).toBe('50%'); | ||
expect(element.style.left).toBe('50%'); | ||
expect(element.style.transform).toContain('translateX(-50%)'); | ||
expect(element.style.transform).toContain('translateX(10px)'); | ||
expect(element.style.transform).toContain('translateY(-50%)'); | ||
expect(element.style.transform).toContain('translateY(15px)'); | ||
})); | ||
|
||
it('should default the element to position: absolute', fakeAsyncTest(() => { | ||
strategy.apply(element); | ||
|
||
flushMicrotasks(); | ||
|
||
expect(element.style.position).toBe('absolute'); | ||
})); | ||
|
||
it('should make the element position: fixed', fakeAsyncTest(() => { | ||
strategy.fixed().apply(element); | ||
|
||
flushMicrotasks(); | ||
|
||
expect(element.style.position).toBe('fixed'); | ||
})); | ||
}); | ||
} | ||
|
||
function fakeAsyncTest(fn: () => void) { | ||
return inject([], fakeAsync(fn)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import {PositionStrategy} from './position-strategy'; | ||
import {DOM} from '../../platform/dom/dom_adapter'; | ||
|
||
|
||
/** | ||
* A strategy for positioning overlays. Using this strategy, an overlay is given an | ||
* explicit position relative to the browser's viewport. | ||
*/ | ||
export class GlobalPositionStrategy implements PositionStrategy { | ||
private _cssPosition: string = 'absolute'; | ||
private _top: string = ''; | ||
private _bottom: string = ''; | ||
private _left: string = ''; | ||
private _right: string = ''; | ||
|
||
/** Array of individual applications of translateX(). Currently only for centering. */ | ||
private _translateX: string[] = []; | ||
|
||
/** Array of individual applications of translateY(). Currently only for centering. */ | ||
private _translateY: string[] = []; | ||
|
||
/** Sets the element to usee CSS position: fixed */ | ||
fixed() { | ||
this._cssPosition = 'fixed'; | ||
return this; | ||
} | ||
|
||
/** Sets the element to usee CSS position: absolute. This is the default. */ | ||
absolute() { | ||
this._cssPosition = 'absolute'; | ||
return this; | ||
} | ||
|
||
/** Sets the top position of the overlay. Clears any previously set vertical position. */ | ||
top(value: string) { | ||
this._bottom = ''; | ||
this._translateY = []; | ||
this._top = value; | ||
return this; | ||
} | ||
|
||
/** Sets the left position of the overlay. Clears any previously set horizontal position. */ | ||
left(value: string) { | ||
this._right = ''; | ||
this._translateX = []; | ||
this._left = value; | ||
return this; | ||
} | ||
|
||
/** Sets the bottom position of the overlay. Clears any previously set vertical position. */ | ||
bottom(value: string) { | ||
this._top = ''; | ||
this._translateY = []; | ||
this._bottom = value; | ||
return this; | ||
} | ||
|
||
/** Sets the right position of the overlay. Clears any previously set horizontal position. */ | ||
right(value: string) { | ||
this._left = ''; | ||
this._translateX = []; | ||
this._right = value; | ||
return this; | ||
} | ||
|
||
/** | ||
* Centers the overlay horizontally with an optional offset. | ||
* Clears any previously set horizontal position. | ||
*/ | ||
centerHorizontally(offset = '0px') { | ||
this._left = '50%'; | ||
this._right = ''; | ||
this._translateX = ['-50%', offset]; | ||
return this; | ||
} | ||
|
||
/** | ||
* Centers the overlay vertically with an optional offset. | ||
* Clears any previously set vertical position. | ||
*/ | ||
centerVertically(offset = '0px') { | ||
this._top = '50%'; | ||
this._bottom = ''; | ||
this._translateY = ['-50%', offset]; | ||
return this; | ||
} | ||
|
||
/** Apply the position to the element. */ | ||
apply(element: Element): Promise<void> { | ||
DOM.setStyle(element, 'position', this._cssPosition); | ||
DOM.setStyle(element, 'top', this._top); | ||
DOM.setStyle(element, 'left', this._left); | ||
DOM.setStyle(element, 'bottom', this._bottom); | ||
DOM.setStyle(element, 'right', this._right); | ||
|
||
// TODO(jelbourn): we don't want to always overwrite the transform property here, | ||
// because it will need to be used for animations. | ||
let tranlateX = this._reduceTranslateValues('translateX', this._translateX); | ||
let translateY = this._reduceTranslateValues('translateY', this._translateY); | ||
|
||
// It's important to trim the result, because the browser will ignore the set operation | ||
// if the string contains only whitespace. | ||
DOM.setStyle(element, 'transform', `${tranlateX} ${translateY}`.trim()); | ||
|
||
return Promise.resolve(); | ||
} | ||
|
||
/** Reduce a list of translate values to a string that can be used in the transform property */ | ||
private _reduceTranslateValues(translateFn: string, values: string[]) { | ||
return values.map(t => `${translateFn}(${t})`).join(' '); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/** Strategy for setting the position on an overlay. */ | ||
export interface PositionStrategy { | ||
|
||
/** Updates the position of the overlay element. */ | ||
apply(element: Element): Promise<void>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import {PositionStrategy} from './position-strategy'; | ||
import {ElementRef} from 'angular2/core'; | ||
|
||
export class RelativePositionStrategy implements PositionStrategy { | ||
constructor(private _relativeTo: ElementRef) { } | ||
|
||
apply(element: Element): Promise<void> { | ||
// Not yet implemented. | ||
return null; | ||
} | ||
} |
Oops, something went wrong.