mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-21 13:01:01 +08:00
feat(menu): adds ion-menu and ion-menu-controller
This commit is contained in:
34
packages/core/package-lock.json
generated
34
packages/core/package-lock.json
generated
@ -1501,14 +1501,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"string_decoder": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"bundled": true,
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"safe-buffer": "5.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"string-width": {
|
"string-width": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
@ -1519,6 +1511,14 @@
|
|||||||
"strip-ansi": "3.0.1"
|
"strip-ansi": "3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"string_decoder": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"bundled": true,
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"safe-buffer": "5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"stringstream": {
|
"stringstream": {
|
||||||
"version": "0.0.5",
|
"version": "0.0.5",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
@ -3136,15 +3136,6 @@
|
|||||||
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=",
|
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"string_decoder": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"safe-buffer": "5.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"string-template": {
|
"string-template": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz",
|
||||||
@ -3162,6 +3153,15 @@
|
|||||||
"strip-ansi": "3.0.1"
|
"strip-ansi": "3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"string_decoder": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"safe-buffer": "5.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"stringstream": {
|
"stringstream": {
|
||||||
"version": "0.0.5",
|
"version": "0.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
|
||||||
|
@ -5,6 +5,7 @@ import { transitionEnd } from './transition-end';
|
|||||||
|
|
||||||
|
|
||||||
export class Animator {
|
export class Animator {
|
||||||
|
|
||||||
private _afterAddClasses: string[];
|
private _afterAddClasses: string[];
|
||||||
private _afterRemoveClasses: string[];
|
private _afterRemoveClasses: string[];
|
||||||
private _afterStyles: { [property: string]: any; };
|
private _afterStyles: { [property: string]: any; };
|
||||||
@ -639,7 +640,7 @@ export class Animator {
|
|||||||
|
|
||||||
// flip the number if we're going in reverse
|
// flip the number if we're going in reverse
|
||||||
if (this._isReverse) {
|
if (this._isReverse) {
|
||||||
stepValue = ((stepValue * -1) + 1);
|
stepValue = 1 - stepValue;
|
||||||
}
|
}
|
||||||
var i = 0;
|
var i = 0;
|
||||||
var j = 0;
|
var j = 0;
|
||||||
@ -1023,12 +1024,6 @@ export class Animator {
|
|||||||
children[i].progressStep(stepValue);
|
children[i].progressStep(stepValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._isReverse) {
|
|
||||||
// if the animation is going in reverse then
|
|
||||||
// flip the step value: 0 becomes 1, 1 becomes 0
|
|
||||||
stepValue = ((stepValue * -1) + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ******** DOM WRITE ****************
|
// ******** DOM WRITE ****************
|
||||||
this._progress(stepValue);
|
this._progress(stepValue);
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { applyStyles, getElementReference, pointerCoordX, pointerCoordY } from '../../utils/helpers';
|
import { applyStyles, getElementReference, ElementRef, updateDetail, assert } from '../../utils/helpers';
|
||||||
import { BlockerDelegate, GestureController, GestureDelegate, BLOCK_ALL } from '../gesture-controller/gesture-controller';
|
import { BlockerDelegate, GestureController, GestureDelegate, BLOCK_ALL } from '../gesture-controller/gesture-controller';
|
||||||
import { Component, Element, Event, EventEmitter, Listen, Prop, PropDidChange } from '@stencil/core';
|
import { Component, Element, Event, EventEmitter, Listen, Prop, PropDidChange } from '@stencil/core';
|
||||||
import { PanRecognizer } from './recognizers';
|
import { PanRecognizer } from './recognizers';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
tag: 'ion-gesture'
|
tag: 'ion-gesture'
|
||||||
})
|
})
|
||||||
export class Gesture {
|
export class Gesture {
|
||||||
|
|
||||||
@Element() private el: HTMLElement;
|
@Element() private el: HTMLElement;
|
||||||
private detail: GestureDetail = {};
|
private detail: GestureDetail = {};
|
||||||
private positions: number[] = [];
|
private positions: number[] = [];
|
||||||
@ -18,9 +18,10 @@ export class Gesture {
|
|||||||
private hasCapturedPan = false;
|
private hasCapturedPan = false;
|
||||||
private hasPress = false;
|
private hasPress = false;
|
||||||
private hasStartedPan = false;
|
private hasStartedPan = false;
|
||||||
private requiresMove = false;
|
private hasFiredStart = true;
|
||||||
private isMoveQueued = false;
|
private isMoveQueued = false;
|
||||||
private blocker: BlockerDelegate;
|
private blocker: BlockerDelegate;
|
||||||
|
private fireOnMoveFunc: any;
|
||||||
|
|
||||||
@Event() private ionGestureMove: EventEmitter;
|
@Event() private ionGestureMove: EventEmitter;
|
||||||
@Event() private ionGestureStart: EventEmitter;
|
@Event() private ionGestureStart: EventEmitter;
|
||||||
@ -28,7 +29,8 @@ export class Gesture {
|
|||||||
@Event() private ionGestureNotCaptured: EventEmitter;
|
@Event() private ionGestureNotCaptured: EventEmitter;
|
||||||
@Event() private ionPress: EventEmitter;
|
@Event() private ionPress: EventEmitter;
|
||||||
|
|
||||||
@Prop() attachTo: string = 'child';
|
@Prop() enabled: boolean = true;
|
||||||
|
@Prop() attachTo: ElementRef = 'child';
|
||||||
@Prop() autoBlockAll: boolean = false;
|
@Prop() autoBlockAll: boolean = false;
|
||||||
@Prop() block: string = null;
|
@Prop() block: string = null;
|
||||||
@Prop() disableScroll: boolean = false;
|
@Prop() disableScroll: boolean = false;
|
||||||
@ -40,12 +42,16 @@ export class Gesture {
|
|||||||
@Prop() type: string = 'pan';
|
@Prop() type: string = 'pan';
|
||||||
|
|
||||||
@Prop() canStart: GestureCallback;
|
@Prop() canStart: GestureCallback;
|
||||||
|
@Prop() onWillStart: (_: GestureDetail) => Promise<void>;
|
||||||
@Prop() onStart: GestureCallback;
|
@Prop() onStart: GestureCallback;
|
||||||
@Prop() onMove: GestureCallback;
|
@Prop() onMove: GestureCallback;
|
||||||
@Prop() onEnd: GestureCallback;
|
@Prop() onEnd: GestureCallback;
|
||||||
@Prop() onPress: GestureCallback;
|
@Prop() onPress: GestureCallback;
|
||||||
@Prop() notCaptured: GestureCallback;
|
@Prop() notCaptured: GestureCallback;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.fireOnMoveFunc = this.fireOnMove.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
ionViewDidLoad() {
|
ionViewDidLoad() {
|
||||||
// in this case, we already know the GestureController and Gesture are already
|
// in this case, we already know the GestureController and Gesture are already
|
||||||
@ -55,17 +61,13 @@ export class Gesture {
|
|||||||
this.gesture = this.ctrl.createGesture(this.gestureName, this.gesturePriority, this.disableScroll);
|
this.gesture = this.ctrl.createGesture(this.gestureName, this.gesturePriority, this.disableScroll);
|
||||||
|
|
||||||
const types = this.type.replace(/\s/g, '').toLowerCase().split(',');
|
const types = this.type.replace(/\s/g, '').toLowerCase().split(',');
|
||||||
|
|
||||||
if (types.indexOf('pan') > -1) {
|
if (types.indexOf('pan') > -1) {
|
||||||
this.pan = new PanRecognizer(this.direction, this.threshold, this.maxAngle);
|
this.pan = new PanRecognizer(this.direction, this.threshold, this.maxAngle);
|
||||||
this.requiresMove = true;
|
|
||||||
}
|
}
|
||||||
this.hasPress = (types.indexOf('press') > -1);
|
this.hasPress = (types.indexOf('press') > -1);
|
||||||
|
|
||||||
|
this.enabledChange(true);
|
||||||
if (this.pan || this.hasPress) {
|
if (this.pan || this.hasPress) {
|
||||||
Context.enableListener(this, 'touchstart', true, this.attachTo);
|
|
||||||
Context.enableListener(this, 'mousedown', true, this.attachTo);
|
|
||||||
|
|
||||||
Context.dom.write(() => {
|
Context.dom.write(() => {
|
||||||
applyStyles(getElementReference(this.el, this.attachTo), GESTURE_INLINE_STYLES);
|
applyStyles(getElementReference(this.el, this.attachTo), GESTURE_INLINE_STYLES);
|
||||||
});
|
});
|
||||||
@ -77,6 +79,19 @@ export class Gesture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PropDidChange('enabled')
|
||||||
|
enabledChange(isEnabled: boolean) {
|
||||||
|
if (!this.gesture) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.pan || this.hasPress) {
|
||||||
|
Context.enableListener(this, 'touchstart', isEnabled, this.attachTo);
|
||||||
|
Context.enableListener(this, 'mousedown', isEnabled, this.attachTo);
|
||||||
|
if (!isEnabled) {
|
||||||
|
this.abortGesture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PropDidChange('block')
|
@PropDidChange('block')
|
||||||
blockChange(block: string) {
|
blockChange(block: string) {
|
||||||
@ -94,10 +109,12 @@ export class Gesture {
|
|||||||
onTouchStart(ev: TouchEvent) {
|
onTouchStart(ev: TouchEvent) {
|
||||||
this.lastTouch = now(ev);
|
this.lastTouch = now(ev);
|
||||||
|
|
||||||
this.enableMouse(false);
|
if (this.pointerDown(ev, this.lastTouch)) {
|
||||||
this.enableTouch(true);
|
this.enableMouse(false);
|
||||||
|
this.enableTouch(true);
|
||||||
this.pointerDown(ev, this.lastTouch);
|
} else {
|
||||||
|
this.abortGesture();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -106,29 +123,36 @@ export class Gesture {
|
|||||||
const timeStamp = now(ev);
|
const timeStamp = now(ev);
|
||||||
|
|
||||||
if (this.lastTouch === 0 || (this.lastTouch + MOUSE_WAIT < timeStamp)) {
|
if (this.lastTouch === 0 || (this.lastTouch + MOUSE_WAIT < timeStamp)) {
|
||||||
this.enableMouse(true);
|
if (this.pointerDown(ev, timeStamp)) {
|
||||||
this.enableTouch(false);
|
this.enableMouse(true);
|
||||||
|
this.enableTouch(false);
|
||||||
this.pointerDown(ev, timeStamp);
|
} else {
|
||||||
|
this.abortGesture();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private pointerDown(ev: UIEvent, timeStamp: number): boolean {
|
private pointerDown(ev: UIEvent, timeStamp: number): boolean {
|
||||||
if (!this.gesture || this.hasStartedPan) {
|
if (!this.gesture || this.hasStartedPan || !this.hasFiredStart) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const detail = this.detail;
|
const detail = this.detail;
|
||||||
|
|
||||||
detail.startX = detail.currentX = pointerCoordX(ev);
|
updateDetail(ev, detail);
|
||||||
detail.startY = detail.currentY = pointerCoordY(ev);
|
detail.startX = detail.currentX;
|
||||||
|
detail.startY = detail.currentY;
|
||||||
detail.startTimeStamp = detail.timeStamp = timeStamp;
|
detail.startTimeStamp = detail.timeStamp = timeStamp;
|
||||||
detail.velocityX = detail.velocityY = detail.deltaX = detail.deltaY = 0;
|
detail.velocityX = detail.velocityY = detail.deltaX = detail.deltaY = 0;
|
||||||
detail.directionX = detail.directionY = detail.velocityDirectionX = detail.velocityDirectionY = null;
|
|
||||||
detail.event = ev;
|
detail.event = ev;
|
||||||
this.positions.length = 0;
|
this.positions.length = 0;
|
||||||
|
|
||||||
|
assert(this.hasFiredStart, 'fired start must be false');
|
||||||
|
assert(!this.hasStartedPan, 'pan can be started at this point');
|
||||||
|
assert(!this.hasCapturedPan, 'pan can be started at this point')
|
||||||
|
assert(!this.isMoveQueued, 'some move is still queued');
|
||||||
|
assert(this.positions.length === 0, 'positions must be emprty');
|
||||||
|
|
||||||
|
// Check if gesture can start
|
||||||
if (this.canStart && this.canStart(detail) === false) {
|
if (this.canStart && this.canStart(detail) === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -145,11 +169,8 @@ export class Gesture {
|
|||||||
|
|
||||||
if (this.pan) {
|
if (this.pan) {
|
||||||
this.hasStartedPan = true;
|
this.hasStartedPan = true;
|
||||||
this.hasCapturedPan = false;
|
|
||||||
|
|
||||||
this.pan.start(detail.startX, detail.startY);
|
this.pan.start(detail.startX, detail.startY);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +180,6 @@ export class Gesture {
|
|||||||
@Listen('touchmove', { passive: true, enabled: false })
|
@Listen('touchmove', { passive: true, enabled: false })
|
||||||
onTouchMove(ev: TouchEvent) {
|
onTouchMove(ev: TouchEvent) {
|
||||||
this.lastTouch = this.detail.timeStamp = now(ev);
|
this.lastTouch = this.detail.timeStamp = now(ev);
|
||||||
|
|
||||||
this.pointerMove(ev);
|
this.pointerMove(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +187,6 @@ export class Gesture {
|
|||||||
@Listen('document:mousemove', { passive: true, enabled: false })
|
@Listen('document:mousemove', { passive: true, enabled: false })
|
||||||
onMoveMove(ev: TouchEvent) {
|
onMoveMove(ev: TouchEvent) {
|
||||||
const timeStamp = now(ev);
|
const timeStamp = now(ev);
|
||||||
|
|
||||||
if (this.lastTouch === 0 || (this.lastTouch + MOUSE_WAIT < timeStamp)) {
|
if (this.lastTouch === 0 || (this.lastTouch + MOUSE_WAIT < timeStamp)) {
|
||||||
this.detail.timeStamp = timeStamp;
|
this.detail.timeStamp = timeStamp;
|
||||||
this.pointerMove(ev);
|
this.pointerMove(ev);
|
||||||
@ -175,107 +194,124 @@ export class Gesture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private pointerMove(ev: UIEvent) {
|
private pointerMove(ev: UIEvent) {
|
||||||
|
assert(!!this.pan, 'pan must be non null');
|
||||||
|
|
||||||
|
if (this.hasCapturedPan) {
|
||||||
|
if (!this.isMoveQueued && this.hasFiredStart) {
|
||||||
|
this.isMoveQueued = true;
|
||||||
|
this.calcGestureData(ev);
|
||||||
|
Context.dom.write(this.fireOnMoveFunc);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const detail = this.detail;
|
const detail = this.detail;
|
||||||
this.calcGestureData(ev);
|
this.calcGestureData(ev);
|
||||||
|
if (this.pan.detect(detail.currentX, detail.currentY)) {
|
||||||
if (this.pan) {
|
if (this.pan.isGesture() !== 0) {
|
||||||
if (this.hasCapturedPan) {
|
if (!this.tryToCapturePan(ev)) {
|
||||||
|
this.abortGesture();
|
||||||
if (!this.isMoveQueued) {
|
|
||||||
this.isMoveQueued = true;
|
|
||||||
|
|
||||||
Context.dom.write(() => {
|
|
||||||
this.isMoveQueued = false;
|
|
||||||
detail.type = 'pan';
|
|
||||||
|
|
||||||
if (this.onMove) {
|
|
||||||
this.onMove(detail);
|
|
||||||
} else {
|
|
||||||
this.ionGestureMove.emit(this.detail);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (this.pan.detect(detail.currentX, detail.currentY)) {
|
|
||||||
if (this.pan.isGesture() !== 0) {
|
|
||||||
if (!this.tryToCapturePan(ev)) {
|
|
||||||
this.abortGesture();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fireOnMove() {
|
||||||
|
const detail = this.detail;
|
||||||
|
this.isMoveQueued = false;
|
||||||
|
if (this.onMove) {
|
||||||
|
this.onMove(detail);
|
||||||
|
} else {
|
||||||
|
this.ionGestureMove.emit(detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private calcGestureData(ev: UIEvent) {
|
private calcGestureData(ev: UIEvent) {
|
||||||
const detail = this.detail;
|
const detail = this.detail;
|
||||||
detail.currentX = pointerCoordX(ev);
|
updateDetail(ev, detail);
|
||||||
detail.currentY = pointerCoordY(ev);
|
|
||||||
detail.deltaX = (detail.currentX - detail.startX);
|
const currentX = detail.currentX;
|
||||||
detail.deltaY = (detail.currentY - detail.startY);
|
const currentY = detail.currentY;
|
||||||
|
const timestamp = detail.timeStamp;
|
||||||
|
detail.deltaX = currentX - detail.startX;
|
||||||
|
detail.deltaY = currentY - detail.startY;
|
||||||
detail.event = ev;
|
detail.event = ev;
|
||||||
|
|
||||||
// figure out which direction we're movin'
|
const timeRange = timestamp - 100;
|
||||||
detail.directionX = detail.velocityDirectionX = (detail.deltaX > 0 ? 'left' : (detail.deltaX < 0 ? 'right' : null));
|
|
||||||
detail.directionY = detail.velocityDirectionY = (detail.deltaY > 0 ? 'up' : (detail.deltaY < 0 ? 'down' : null));
|
|
||||||
|
|
||||||
const positions = this.positions;
|
const positions = this.positions;
|
||||||
positions.push(detail.currentX, detail.currentY, detail.timeStamp);
|
let startPos = positions.length - 1;
|
||||||
|
|
||||||
var endPos = (positions.length - 1);
|
|
||||||
var startPos = endPos;
|
|
||||||
var timeRange = (detail.timeStamp - 100);
|
|
||||||
|
|
||||||
// move pointer to position measured 100ms ago
|
// move pointer to position measured 100ms ago
|
||||||
for (var i = endPos; i > 0 && positions[i] > timeRange; i -= 3) {
|
for (;
|
||||||
startPos = i;
|
startPos > 0 && positions[startPos] > timeRange;
|
||||||
}
|
startPos -= 3) { }
|
||||||
|
|
||||||
if (startPos !== endPos) {
|
if (startPos > 1) {
|
||||||
// compute relative movement between these two points
|
// compute relative movement between these two points
|
||||||
var movedX = (positions[startPos - 2] - positions[endPos - 2]);
|
var frequency = 1 / (positions[startPos] - timestamp);
|
||||||
var movedY = (positions[startPos - 1] - positions[endPos - 1]);
|
var movedY = positions[startPos - 1] - currentY;
|
||||||
var factor = 16.67 / (positions[endPos] - positions[startPos]);
|
var movedX = positions[startPos - 2] - currentX;
|
||||||
|
|
||||||
// based on XXms compute the movement to apply for each render step
|
// based on XXms compute the movement to apply for each render step
|
||||||
detail.velocityX = movedX * factor;
|
// velocity = space/time = s*(1/t) = s*frequency
|
||||||
detail.velocityY = movedY * factor;
|
detail.velocityX = movedX * frequency;
|
||||||
|
detail.velocityY = movedY * frequency;
|
||||||
detail.velocityDirectionX = (movedX > 0 ? 'left' : (movedX < 0 ? 'right' : null));
|
} else {
|
||||||
detail.velocityDirectionY = (movedY > 0 ? 'up' : (movedY < 0 ? 'down' : null));
|
detail.velocityX = 0;
|
||||||
|
detail.velocityY = 0;
|
||||||
}
|
}
|
||||||
|
positions.push(currentX, currentY, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private tryToCapturePan(ev: UIEvent): boolean {
|
private tryToCapturePan(ev: UIEvent): boolean {
|
||||||
if (this.gesture && !this.gesture.capture()) {
|
if (this.gesture && !this.gesture.capture()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
this.hasCapturedPan = true;
|
||||||
|
this.hasFiredStart = false;
|
||||||
|
this.calcGestureData(ev);
|
||||||
|
if (this.onWillStart) {
|
||||||
|
this.onWillStart(this.detail).then(this.fireOnStart.bind(this));
|
||||||
|
} else {
|
||||||
|
this.fireOnStart();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
this.detail.event = ev;
|
private fireOnStart() {
|
||||||
|
assert(!this.hasFiredStart, 'has fired must be false');
|
||||||
if (this.onStart) {
|
if (this.onStart) {
|
||||||
this.onStart(this.detail);
|
this.onStart(this.detail);
|
||||||
} else {
|
} else {
|
||||||
this.ionGestureStart.emit(this.detail);
|
this.ionGestureStart.emit(this.detail);
|
||||||
}
|
}
|
||||||
|
this.hasFiredStart = true;
|
||||||
this.hasCapturedPan = true;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private abortGesture() {
|
private abortGesture() {
|
||||||
this.hasStartedPan = false;
|
this.reset();
|
||||||
this.hasCapturedPan = false;
|
|
||||||
|
|
||||||
this.gesture && this.gesture.release();
|
|
||||||
|
|
||||||
this.enable(false);
|
this.enable(false);
|
||||||
this.notCaptured && this.notCaptured(this.detail);
|
this.notCaptured && this.notCaptured(this.detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private reset() {
|
||||||
|
this.hasCapturedPan = false;
|
||||||
|
this.hasStartedPan = false;
|
||||||
|
this.hasFiredStart = true;
|
||||||
|
this.gesture && this.gesture.release();
|
||||||
|
}
|
||||||
|
|
||||||
// END *************************
|
// END *************************
|
||||||
|
|
||||||
|
@Listen('touchcancel', { passive: true, enabled: false })
|
||||||
|
onTouchCancel(ev: TouchEvent) {
|
||||||
|
this.lastTouch = this.detail.timeStamp = now(ev);
|
||||||
|
|
||||||
|
this.pointerUp(ev);
|
||||||
|
this.enableTouch(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Listen('touchend', { passive: true, enabled: false })
|
@Listen('touchend', { passive: true, enabled: false })
|
||||||
onTouchEnd(ev: TouchEvent) {
|
onTouchEnd(ev: TouchEvent) {
|
||||||
this.lastTouch = this.detail.timeStamp = now(ev);
|
this.lastTouch = this.detail.timeStamp = now(ev);
|
||||||
@ -298,47 +334,46 @@ export class Gesture {
|
|||||||
|
|
||||||
|
|
||||||
private pointerUp(ev: UIEvent) {
|
private pointerUp(ev: UIEvent) {
|
||||||
|
const hasCaptured = this.hasCapturedPan;
|
||||||
|
const hasFiredStart = this.hasFiredStart;
|
||||||
|
this.reset();
|
||||||
|
|
||||||
|
if (!hasFiredStart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const detail = this.detail;
|
const detail = this.detail;
|
||||||
|
|
||||||
this.gesture && this.gesture.release();
|
|
||||||
|
|
||||||
detail.event = ev;
|
|
||||||
|
|
||||||
this.calcGestureData(ev);
|
this.calcGestureData(ev);
|
||||||
|
|
||||||
if (this.pan) {
|
// Try to capture press
|
||||||
if (this.hasCapturedPan) {
|
if (hasCaptured) {
|
||||||
detail.type = 'pan';
|
detail.type = 'pan';
|
||||||
if (this.onEnd) {
|
if (this.onEnd) {
|
||||||
this.onEnd(detail);
|
this.onEnd(detail);
|
||||||
} else {
|
|
||||||
this.ionGestureEnd.emit(detail);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (this.hasPress) {
|
|
||||||
this.detectPress();
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (this.notCaptured) {
|
this.ionGestureEnd.emit(detail);
|
||||||
this.notCaptured(detail);
|
|
||||||
} else {
|
|
||||||
this.ionGestureNotCaptured.emit(detail);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
} else if (this.hasPress) {
|
|
||||||
this.detectPress();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hasCapturedPan = false;
|
// Try to capture press
|
||||||
this.hasStartedPan = false;
|
if (this.hasPress && this.detectPress()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not captured any event
|
||||||
|
if (this.notCaptured) {
|
||||||
|
this.notCaptured(detail);
|
||||||
|
} else {
|
||||||
|
this.ionGestureNotCaptured.emit(detail);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private detectPress(): boolean {
|
||||||
private detectPress() {
|
|
||||||
const detail = this.detail;
|
const detail = this.detail;
|
||||||
|
const vecX = detail.deltaX;
|
||||||
if (Math.abs(detail.startX - detail.currentX) < 10 && Math.abs(detail.startY - detail.currentY) < 10) {
|
const vecY = detail.deltaY;
|
||||||
|
const dis = vecX * vecX + vecY * vecY;
|
||||||
|
if (dis < 100) {
|
||||||
detail.type = 'press';
|
detail.type = 'press';
|
||||||
|
|
||||||
if (this.onPress) {
|
if (this.onPress) {
|
||||||
@ -346,25 +381,27 @@ export class Gesture {
|
|||||||
} else {
|
} else {
|
||||||
this.ionPress.emit(detail);
|
this.ionPress.emit(detail);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ENABLE LISTENERS *************************
|
// ENABLE LISTENERS *************************
|
||||||
|
|
||||||
private enableMouse(shouldEnable: boolean) {
|
private enableMouse(shouldEnable: boolean) {
|
||||||
if (this.requiresMove) {
|
if (this.pan) {
|
||||||
Context.enableListener(this, 'document:mousemove', shouldEnable);
|
Context.enableListener(this, 'document:mousemove', shouldEnable, this.attachTo);
|
||||||
}
|
}
|
||||||
Context.enableListener(this, 'document:mouseup', shouldEnable);
|
Context.enableListener(this, 'document:mouseup', shouldEnable, this.attachTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private enableTouch(shouldEnable: boolean) {
|
private enableTouch(shouldEnable: boolean) {
|
||||||
if (this.requiresMove) {
|
if (this.pan) {
|
||||||
Context.enableListener(this, 'touchmove', shouldEnable);
|
Context.enableListener(this, 'touchmove', shouldEnable, this.attachTo);
|
||||||
}
|
}
|
||||||
Context.enableListener(this, 'touchend', shouldEnable);
|
Context.enableListener(this, 'touchcancel', shouldEnable, this.attachTo);
|
||||||
|
Context.enableListener(this, 'touchend', shouldEnable, this.attachTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -413,10 +450,6 @@ export interface GestureDetail {
|
|||||||
velocityY?: number;
|
velocityY?: number;
|
||||||
deltaX?: number;
|
deltaX?: number;
|
||||||
deltaY?: number;
|
deltaY?: number;
|
||||||
directionX?: 'left'|'right';
|
|
||||||
directionY?: 'up'|'down';
|
|
||||||
velocityDirectionX?: 'left'|'right';
|
|
||||||
velocityDirectionY?: 'up'|'down';
|
|
||||||
timeStamp?: number;
|
timeStamp?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
15
packages/core/src/components/menu/animations/base.ts
Normal file
15
packages/core/src/components/menu/animations/base.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Animation } from '../../../index';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
* Menu Type
|
||||||
|
* Base class which is extended by the various types. Each
|
||||||
|
* type will provide their own animations for open and close
|
||||||
|
* and registers itself with Menu.
|
||||||
|
*/
|
||||||
|
export default function baseAnimation(Animation: Animation): Animation {
|
||||||
|
return new Animation()
|
||||||
|
.easing('cubic-bezier(0.0, 0.0, 0.2, 1)')
|
||||||
|
.easingReverse('cubic-bezier(0.4, 0.0, 0.6, 1)')
|
||||||
|
.duration(280);
|
||||||
|
}
|
35
packages/core/src/components/menu/animations/overlay.ts
Normal file
35
packages/core/src/components/menu/animations/overlay.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { Animation, Menu } from '../../../index';
|
||||||
|
import baseAnimation from './base';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
* Menu Overlay Type
|
||||||
|
* The menu slides over the content. The content
|
||||||
|
* itself, which is under the menu, does not move.
|
||||||
|
*/
|
||||||
|
export default function(Animation: Animation, _: HTMLElement, menu: Menu): Animation {
|
||||||
|
let closedX: string, openedX: string;
|
||||||
|
const width = menu.getWidth();
|
||||||
|
if (menu.isRightSide) {
|
||||||
|
// right side
|
||||||
|
closedX = 8 + width + 'px';
|
||||||
|
openedX = '0px';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// left side
|
||||||
|
closedX = -(8 + width) + 'px';
|
||||||
|
openedX = '0px';
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuAni = new Animation()
|
||||||
|
.addElement(menu.getMenuElement())
|
||||||
|
.fromTo('translateX', closedX, openedX);
|
||||||
|
|
||||||
|
const backdropApi = new Animation()
|
||||||
|
.addElement(menu.getBackdropElement())
|
||||||
|
.fromTo('opacity', 0.01, 0.35)
|
||||||
|
|
||||||
|
return baseAnimation(Animation)
|
||||||
|
.add(menuAni)
|
||||||
|
.add(backdropApi);
|
||||||
|
}
|
36
packages/core/src/components/menu/animations/push.ts
Normal file
36
packages/core/src/components/menu/animations/push.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { Animation, Menu } from '../../../index';
|
||||||
|
import baseAnimation from './base';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
* Menu Push Type
|
||||||
|
* The content slides over to reveal the menu underneath.
|
||||||
|
* The menu itself also slides over to reveal its bad self.
|
||||||
|
*/
|
||||||
|
export default function(Animation: Animation, _: HTMLElement, menu: Menu): Animation {
|
||||||
|
|
||||||
|
let contentOpenedX: string, menuClosedX: string, menuOpenedX: string;
|
||||||
|
const width = menu.getWidth();
|
||||||
|
|
||||||
|
if (menu.isRightSide) {
|
||||||
|
contentOpenedX = -width + 'px';
|
||||||
|
menuClosedX = width + 'px';
|
||||||
|
menuOpenedX = '0px';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
contentOpenedX = width + 'px';
|
||||||
|
menuOpenedX = '0px';
|
||||||
|
menuClosedX = -width + 'px';
|
||||||
|
}
|
||||||
|
const menuAni = new Animation()
|
||||||
|
.addElement(menu.getMenuElement())
|
||||||
|
.fromTo('translateX', menuClosedX, menuOpenedX);
|
||||||
|
|
||||||
|
const contentAni = new Animation()
|
||||||
|
.addElement(menu.getContentElement())
|
||||||
|
.fromTo('translateX', '0px', contentOpenedX);
|
||||||
|
|
||||||
|
return baseAnimation(Animation)
|
||||||
|
.add(menuAni)
|
||||||
|
.add(contentAni);
|
||||||
|
}
|
19
packages/core/src/components/menu/animations/reveal.ts
Normal file
19
packages/core/src/components/menu/animations/reveal.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Animation, Menu } from '../../../index';
|
||||||
|
import baseAnimation from './base';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
* Menu Reveal Type
|
||||||
|
* The content slides over to reveal the menu underneath.
|
||||||
|
* The menu itself, which is under the content, does not move.
|
||||||
|
*/
|
||||||
|
export default function(Animation: Animation, _: HTMLElement, menu: Menu): Animation {
|
||||||
|
const openedX = (menu.getWidth() * (menu.isRightSide ? -1 : 1)) + 'px';
|
||||||
|
|
||||||
|
const contentOpen = new Animation()
|
||||||
|
.addElement(menu.getContentElement())
|
||||||
|
.fromTo('translateX', '0px', openedX);
|
||||||
|
|
||||||
|
return baseAnimation(Animation)
|
||||||
|
.add(contentOpen);
|
||||||
|
}
|
@ -1,15 +1,29 @@
|
|||||||
import { Menu, MenuType } from '../../index';
|
import { Menu, AnimationController, AnimationBuilder, Animation } from '../../index';
|
||||||
import { MenuRevealType, MenuPushType, MenuOverlayType } from './menu-types';
|
import { Component, Method, Prop } from '@stencil/core';
|
||||||
|
|
||||||
|
import MenuOverlayAnimation from './animations/overlay';
|
||||||
|
import MenuRevealAnimation from './animations/reveal';
|
||||||
|
import MenuPushAnimation from './animations/push';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
tag: 'ion-menu-controller'
|
||||||
|
})
|
||||||
export class MenuController {
|
export class MenuController {
|
||||||
private _menus: Array<Menu> = [];
|
|
||||||
private _menuTypes: { [name: string]: new(...args: any[]) => MenuType } = {};
|
private menus: Menu[] = [];
|
||||||
|
private menuAnimations: { [name: string]: AnimationBuilder } = {};
|
||||||
|
|
||||||
|
@Prop({ connect: 'ion-animation-controller' }) animationCtrl: AnimationController;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.registerType('reveal', MenuRevealType);
|
this.registerAnimation('reveal', MenuRevealAnimation);
|
||||||
this.registerType('push', MenuPushType);
|
this.registerAnimation('push', MenuPushAnimation);
|
||||||
this.registerType('overlay', MenuOverlayType);
|
this.registerAnimation('overlay', MenuOverlayAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Method()
|
||||||
|
getInstance(): MenuController {
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -17,6 +31,7 @@ export class MenuController {
|
|||||||
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
||||||
* @return {Promise} returns a promise when the menu is fully opened
|
* @return {Promise} returns a promise when the menu is fully opened
|
||||||
*/
|
*/
|
||||||
|
@Method()
|
||||||
open(menuId?: string): Promise<boolean> {
|
open(menuId?: string): Promise<boolean> {
|
||||||
const menu = this.get(menuId);
|
const menu = this.get(menuId);
|
||||||
if (menu && !this.isAnimating()) {
|
if (menu && !this.isAnimating()) {
|
||||||
@ -36,6 +51,7 @@ export class MenuController {
|
|||||||
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
||||||
* @return {Promise} returns a promise when the menu is fully closed
|
* @return {Promise} returns a promise when the menu is fully closed
|
||||||
*/
|
*/
|
||||||
|
@Method()
|
||||||
close(menuId?: string): Promise<boolean> {
|
close(menuId?: string): Promise<boolean> {
|
||||||
let menu: Menu;
|
let menu: Menu;
|
||||||
|
|
||||||
@ -63,6 +79,7 @@ export class MenuController {
|
|||||||
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
||||||
* @return {Promise} returns a promise when the menu has been toggled
|
* @return {Promise} returns a promise when the menu has been toggled
|
||||||
*/
|
*/
|
||||||
|
@Method()
|
||||||
toggle(menuId?: string): Promise<boolean> {
|
toggle(menuId?: string): Promise<boolean> {
|
||||||
const menu = this.get(menuId);
|
const menu = this.get(menuId);
|
||||||
if (menu && !this.isAnimating()) {
|
if (menu && !this.isAnimating()) {
|
||||||
@ -83,6 +100,7 @@ export class MenuController {
|
|||||||
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
||||||
* @return {Menu} Returns the instance of the menu, which is useful for chaining.
|
* @return {Menu} Returns the instance of the menu, which is useful for chaining.
|
||||||
*/
|
*/
|
||||||
|
@Method()
|
||||||
enable(shouldEnable: boolean, menuId?: string): Menu {
|
enable(shouldEnable: boolean, menuId?: string): Menu {
|
||||||
const menu = this.get(menuId);
|
const menu = this.get(menuId);
|
||||||
return (menu && menu.enable(shouldEnable)) || null;
|
return (menu && menu.enable(shouldEnable)) || null;
|
||||||
@ -94,6 +112,7 @@ export class MenuController {
|
|||||||
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
||||||
* @return {Menu} Returns the instance of the menu, which is useful for chaining.
|
* @return {Menu} Returns the instance of the menu, which is useful for chaining.
|
||||||
*/
|
*/
|
||||||
|
@Method()
|
||||||
swipeEnable(shouldEnable: boolean, menuId?: string): Menu {
|
swipeEnable(shouldEnable: boolean, menuId?: string): Menu {
|
||||||
const menu = this.get(menuId);
|
const menu = this.get(menuId);
|
||||||
return (menu && menu.swipeEnable(shouldEnable)) || null;
|
return (menu && menu.swipeEnable(shouldEnable)) || null;
|
||||||
@ -104,10 +123,11 @@ export class MenuController {
|
|||||||
* @return {boolean} Returns true if the specified menu is currently open, otherwise false.
|
* @return {boolean} Returns true if the specified menu is currently open, otherwise false.
|
||||||
* If the menuId is not specified, it returns true if ANY menu is currenly open.
|
* If the menuId is not specified, it returns true if ANY menu is currenly open.
|
||||||
*/
|
*/
|
||||||
|
@Method()
|
||||||
isOpen(menuId?: string): boolean {
|
isOpen(menuId?: string): boolean {
|
||||||
if (menuId) {
|
if (menuId) {
|
||||||
var menu = this.get(menuId);
|
var menu = this.get(menuId);
|
||||||
return menu && menu.isOpen || false;
|
return menu && menu.isOpen() || false;
|
||||||
} else {
|
} else {
|
||||||
return !!this.getOpen();
|
return !!this.getOpen();
|
||||||
}
|
}
|
||||||
@ -117,6 +137,7 @@ export class MenuController {
|
|||||||
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
||||||
* @return {boolean} Returns true if the menu is currently enabled, otherwise false.
|
* @return {boolean} Returns true if the menu is currently enabled, otherwise false.
|
||||||
*/
|
*/
|
||||||
|
@Method()
|
||||||
isEnabled(menuId?: string): boolean {
|
isEnabled(menuId?: string): boolean {
|
||||||
const menu = this.get(menuId);
|
const menu = this.get(menuId);
|
||||||
return menu && menu.enabled || false;
|
return menu && menu.enabled || false;
|
||||||
@ -131,87 +152,94 @@ export class MenuController {
|
|||||||
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
* @param {string} [menuId] Optionally get the menu by its id, or side.
|
||||||
* @return {Menu} Returns the instance of the menu if found, otherwise `null`.
|
* @return {Menu} Returns the instance of the menu if found, otherwise `null`.
|
||||||
*/
|
*/
|
||||||
|
@Method()
|
||||||
get(menuId?: string): Menu {
|
get(menuId?: string): Menu {
|
||||||
var menu: Menu;
|
var menu: Menu;
|
||||||
|
|
||||||
if (menuId === 'left' || menuId === 'right') {
|
if (menuId === 'left' || menuId === 'right') {
|
||||||
// there could be more than one menu on the same side
|
// there could be more than one menu on the same side
|
||||||
// so first try to get the enabled one
|
// so first try to get the enabled one
|
||||||
menu = this._menus.find(m => m.side === menuId && m.enabled);
|
menu = this.menus.find(m => m.side === menuId && m.enabled);
|
||||||
if (menu) {
|
if (menu) {
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
// didn't find a menu side that is enabled
|
// didn't find a menu side that is enabled
|
||||||
// so try to get the first menu side found
|
// so try to get the first menu side found
|
||||||
return this._menus.find(m => m.side === menuId) || null;
|
return this.menus.find(m => m.side === menuId) || null;
|
||||||
|
|
||||||
} else if (menuId) {
|
} else if (menuId) {
|
||||||
// the menuId was not left or right
|
// the menuId was not left or right
|
||||||
// so try to get the menu by its "id"
|
// so try to get the menu by its "id"
|
||||||
return this._menus.find(m => m.id === menuId) || null;
|
return this.menus.find(m => m.id === menuId) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the first enabled menu
|
// return the first enabled menu
|
||||||
menu = this._menus.find(m => m.enabled);
|
menu = this.menus.find(m => m.enabled);
|
||||||
if (menu) {
|
if (menu) {
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the first menu in the array, if one exists
|
// get the first menu in the array, if one exists
|
||||||
return (this._menus.length ? this._menus[0] : null);
|
return (this.menus.length > 0 ? this.menus[0] : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {Menu} Returns the instance of the menu already opened, otherwise `null`.
|
* @return {Menu} Returns the instance of the menu already opened, otherwise `null`.
|
||||||
*/
|
*/
|
||||||
|
@Method()
|
||||||
getOpen(): Menu {
|
getOpen(): Menu {
|
||||||
return this._menus.find(m => m.isOpen);
|
return this.menus.find(m => m.isOpen());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {Array<Menu>} Returns an array of all menu instances.
|
* @return {Array<Menu>} Returns an array of all menu instances.
|
||||||
*/
|
*/
|
||||||
getMenus(): Array<Menu> {
|
@Method()
|
||||||
return this._menus;
|
getMenus(): Menu[] {
|
||||||
|
return this.menus;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
* @return {boolean} if any menu is currently animating
|
* @return {boolean} if any menu is currently animating
|
||||||
*/
|
*/
|
||||||
|
@Method()
|
||||||
isAnimating(): boolean {
|
isAnimating(): boolean {
|
||||||
return this._menus.some(menu => menu.isAnimating);
|
return this.menus.some(menu => menu.isAnimating());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
|
@Method()
|
||||||
_register(menu: Menu) {
|
_register(menu: Menu) {
|
||||||
if (this._menus.indexOf(menu) < 0) {
|
if (this.menus.indexOf(menu) < 0) {
|
||||||
this._menus.push(menu);
|
this.menus.push(menu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
|
@Method()
|
||||||
_unregister(menu: Menu) {
|
_unregister(menu: Menu) {
|
||||||
const index = this._menus.indexOf(menu);
|
const index = this.menus.indexOf(menu);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
this._menus.splice(index, 1);
|
this.menus.splice(index, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
|
@Method()
|
||||||
_setActiveMenu(menu: Menu) {
|
_setActiveMenu(menu: Menu) {
|
||||||
// if this menu should be enabled
|
// if this menu should be enabled
|
||||||
// then find all the other menus on this same side
|
// then find all the other menus on this same side
|
||||||
// and automatically disable other same side menus
|
// and automatically disable other same side menus
|
||||||
const side = menu.side;
|
const side = menu.side;
|
||||||
this._menus
|
this.menus
|
||||||
.filter(m => m.side === side && m !== menu)
|
.filter(m => m.side === side && m !== menu)
|
||||||
.map(m => m.enable(false));
|
.map(m => m.enable(false));
|
||||||
}
|
}
|
||||||
@ -220,15 +248,17 @@ export class MenuController {
|
|||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
registerType(name: string, cls: new(...args: any[]) => MenuType) {
|
registerAnimation(name: string, cls: AnimationBuilder) {
|
||||||
this._menuTypes[name] = cls;
|
this.menuAnimations[name] = cls;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
create(type: string, menuCmp: Menu) {
|
@Method()
|
||||||
return new this._menuTypes[type](menuCmp);
|
create(type: string, menuCmp: Menu): Promise<Animation> {
|
||||||
|
const animationBuilder = this.menuAnimations[type];
|
||||||
|
return this.animationCtrl.create(animationBuilder, null, menuCmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,113 +0,0 @@
|
|||||||
// import { Menu } from './menu';
|
|
||||||
// import { DomController } from '../../platform/dom-controller';
|
|
||||||
// import { GestureController, GESTURE_PRIORITY_MENU_SWIPE, GESTURE_MENU_SWIPE } from '../../gestures/gesture-controller';
|
|
||||||
// import { Platform } from '../../platform/platform';
|
|
||||||
// import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture';
|
|
||||||
// import { SlideData } from '../../gestures/slide-gesture';
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Gesture attached to the content which the menu is assigned to
|
|
||||||
// */
|
|
||||||
// export class MenuContentGesture extends SlideEdgeGesture {
|
|
||||||
|
|
||||||
// constructor(
|
|
||||||
// plt: Platform,
|
|
||||||
// public menu: Menu,
|
|
||||||
// gestureCtrl: GestureController,
|
|
||||||
// domCtrl: DomController,
|
|
||||||
// ) {
|
|
||||||
// super(plt, plt.doc().body, {
|
|
||||||
// direction: 'x',
|
|
||||||
// edge: menu.side,
|
|
||||||
// threshold: 5,
|
|
||||||
// maxEdgeStart: menu.maxEdgeStart || 50,
|
|
||||||
// zone: false,
|
|
||||||
// passive: true,
|
|
||||||
// domController: domCtrl,
|
|
||||||
// gesture: gestureCtrl.createGesture({
|
|
||||||
// name: GESTURE_MENU_SWIPE,
|
|
||||||
// priority: GESTURE_PRIORITY_MENU_SWIPE,
|
|
||||||
// disableScroll: true
|
|
||||||
// })
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// canStart(ev: any): boolean {
|
|
||||||
// const menu = this.menu;
|
|
||||||
// if (!menu.canSwipe()) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// if (menu.isOpen) {
|
|
||||||
// return true;
|
|
||||||
// } else if (menu.getMenuController().getOpen()) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// return super.canStart(ev);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Set CSS, then wait one frame for it to apply before sliding starts
|
|
||||||
// onSlideBeforeStart(ev: any) {
|
|
||||||
// console.debug('menu gesture, onSlideBeforeStart', this.menu.side);
|
|
||||||
// this.menu._swipeBeforeStart();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// onSlideStart() {
|
|
||||||
// console.debug('menu gesture, onSlideStart', this.menu.side);
|
|
||||||
// this.menu._swipeStart();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// onSlide(slide: SlideData, ev: any) {
|
|
||||||
// const z = (this.menu.isRightSide ? slide.min : slide.max);
|
|
||||||
// const stepValue = (slide.distance / z);
|
|
||||||
|
|
||||||
// this.menu._swipeProgress(stepValue);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// onSlideEnd(slide: SlideData, ev: any) {
|
|
||||||
// let z = (this.menu.isRightSide ? slide.min : slide.max);
|
|
||||||
// const currentStepValue = (slide.distance / z);
|
|
||||||
// const velocity = slide.velocity;
|
|
||||||
// z = Math.abs(z * 0.5);
|
|
||||||
// const shouldCompleteRight = (velocity >= 0)
|
|
||||||
// && (velocity > 0.2 || slide.delta > z);
|
|
||||||
|
|
||||||
// const shouldCompleteLeft = (velocity <= 0)
|
|
||||||
// && (velocity < -0.2 || slide.delta < -z);
|
|
||||||
|
|
||||||
// console.debug('menu gesture, onSlideEnd', this.menu.side,
|
|
||||||
// 'distance', slide.distance,
|
|
||||||
// 'delta', slide.delta,
|
|
||||||
// 'velocity', velocity,
|
|
||||||
// 'min', slide.min,
|
|
||||||
// 'max', slide.max,
|
|
||||||
// 'shouldCompleteLeft', shouldCompleteLeft,
|
|
||||||
// 'shouldCompleteRight', shouldCompleteRight,
|
|
||||||
// 'currentStepValue', currentStepValue);
|
|
||||||
|
|
||||||
// this.menu._swipeEnd(shouldCompleteLeft, shouldCompleteRight, currentStepValue, velocity);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// getElementStartPos(slide: SlideData, ev: any) {
|
|
||||||
// const menu = this.menu;
|
|
||||||
// if (menu.isRightSide) {
|
|
||||||
// return menu.isOpen ? slide.min : slide.max;
|
|
||||||
// }
|
|
||||||
// // left menu
|
|
||||||
// return menu.isOpen ? slide.max : slide.min;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// getSlideBoundaries(): { min: number, max: number } {
|
|
||||||
// const menu = this.menu;
|
|
||||||
// if (menu.isRightSide) {
|
|
||||||
// return {
|
|
||||||
// min: -menu.width(),
|
|
||||||
// max: 0
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// // left menu
|
|
||||||
// return {
|
|
||||||
// min: 0,
|
|
||||||
// max: menu.width()
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// }
|
|
@ -1,169 +0,0 @@
|
|||||||
import { Animation } from '../../index';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
* Menu Type
|
|
||||||
* Base class which is extended by the various types. Each
|
|
||||||
* type will provide their own animations for open and close
|
|
||||||
* and registers itself with Menu.
|
|
||||||
*/
|
|
||||||
export class MenuType {
|
|
||||||
|
|
||||||
ani: Animation;
|
|
||||||
isOpening: boolean;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// Ionic.createAnimation().then(Animation => {
|
|
||||||
// this.ani = new Animation();
|
|
||||||
// });;
|
|
||||||
// this.ani
|
|
||||||
// .easing('cubic-bezier(0.0, 0.0, 0.2, 1)')
|
|
||||||
// .easingReverse('cubic-bezier(0.4, 0.0, 0.6, 1)')
|
|
||||||
// .duration(280);
|
|
||||||
}
|
|
||||||
|
|
||||||
setOpen(shouldOpen: boolean, animated: boolean, done: (animation: Animation) => void) {
|
|
||||||
const ani = this.ani
|
|
||||||
.onFinish(done, {oneTimeCallback: true, clearExistingCallacks: true })
|
|
||||||
.reverse(!shouldOpen);
|
|
||||||
|
|
||||||
if (animated) {
|
|
||||||
ani.play();
|
|
||||||
} else {
|
|
||||||
ani.syncPlay();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setProgressStart(isOpen: boolean) {
|
|
||||||
this.isOpening = !isOpen;
|
|
||||||
|
|
||||||
// the cloned animation should not use an easing curve during seek
|
|
||||||
this.ani
|
|
||||||
.reverse(isOpen)
|
|
||||||
.progressStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
setProgessStep(stepValue: number) {
|
|
||||||
// adjust progress value depending if it opening or closing
|
|
||||||
this.ani.progressStep(stepValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
setProgressEnd(shouldComplete: boolean, currentStepValue: number, velocity: number, done: Function) {
|
|
||||||
let isOpen = (this.isOpening && shouldComplete);
|
|
||||||
if (!this.isOpening && !shouldComplete) {
|
|
||||||
isOpen = true;
|
|
||||||
}
|
|
||||||
const ani = this.ani;
|
|
||||||
ani.onFinish(() => {
|
|
||||||
this.isOpening = false;
|
|
||||||
done(isOpen);
|
|
||||||
}, { clearExistingCallacks: true });
|
|
||||||
|
|
||||||
const factor = 1 - Math.min(Math.abs(velocity) / 4, 0.7);
|
|
||||||
const dur = ani.getDuration() * factor;
|
|
||||||
|
|
||||||
ani.progressEnd(shouldComplete, currentStepValue, dur);
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.ani.destroy();
|
|
||||||
this.ani = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
* Menu Reveal Type
|
|
||||||
* The content slides over to reveal the menu underneath.
|
|
||||||
* The menu itself, which is under the content, does not move.
|
|
||||||
*/
|
|
||||||
export class MenuRevealType extends MenuType {
|
|
||||||
|
|
||||||
// constructor(menu: Menu) {
|
|
||||||
// super();
|
|
||||||
|
|
||||||
// const openedX = (menu.width() * (menu.isRightSide ? -1 : 1)) + 'px';
|
|
||||||
// const contentOpen = Ionic.createAnimation(menu.getContentElement());
|
|
||||||
// contentOpen.fromTo('translateX', '0px', openedX);
|
|
||||||
// this.ani.add(contentOpen);
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
* Menu Push Type
|
|
||||||
* The content slides over to reveal the menu underneath.
|
|
||||||
* The menu itself also slides over to reveal its bad self.
|
|
||||||
*/
|
|
||||||
export class MenuPushType extends MenuType {
|
|
||||||
|
|
||||||
// constructor(menu: Menu) {
|
|
||||||
// super();
|
|
||||||
|
|
||||||
// let contentOpenedX: string, menuClosedX: string, menuOpenedX: string;
|
|
||||||
// const width = menu.width();
|
|
||||||
|
|
||||||
// if (menu.isRightSide) {
|
|
||||||
// // right side
|
|
||||||
// contentOpenedX = -width + 'px';
|
|
||||||
// menuClosedX = width + 'px';
|
|
||||||
// menuOpenedX = '0px';
|
|
||||||
|
|
||||||
// } else {
|
|
||||||
// contentOpenedX = width + 'px';
|
|
||||||
// menuOpenedX = '0px';
|
|
||||||
// menuClosedX = -width + 'px';
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const menuAni = Ionic.createAnimation(menu.getMenuElement());
|
|
||||||
// menuAni.fromTo('translateX', menuClosedX, menuOpenedX);
|
|
||||||
// this.ani.add(menuAni);
|
|
||||||
|
|
||||||
// const contentApi = Ionic.createAnimation(menu.getContentElement());
|
|
||||||
// contentApi.fromTo('translateX', '0px', contentOpenedX);
|
|
||||||
// this.ani.add(contentApi);
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
* Menu Overlay Type
|
|
||||||
* The menu slides over the content. The content
|
|
||||||
* itself, which is under the menu, does not move.
|
|
||||||
*/
|
|
||||||
export class MenuOverlayType extends MenuType {
|
|
||||||
|
|
||||||
// constructor(menu: Menu) {
|
|
||||||
// super();
|
|
||||||
|
|
||||||
// let closedX: string, openedX: string;
|
|
||||||
// const width = menu.width();
|
|
||||||
|
|
||||||
// if (menu.isRightSide) {
|
|
||||||
// // right side
|
|
||||||
// closedX = 8 + width + 'px';
|
|
||||||
// openedX = '0px';
|
|
||||||
|
|
||||||
// } else {
|
|
||||||
// // left side
|
|
||||||
// closedX = -(8 + width) + 'px';
|
|
||||||
// openedX = '0px';
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const menuAni = Ionic.createAnimation(menu.getMenuElement());
|
|
||||||
// menuAni.fromTo('translateX', closedX, openedX);
|
|
||||||
// this.ani.add(menuAni);
|
|
||||||
|
|
||||||
// const backdropApi = Ionic.createAnimation(menu.getBackdropElement());
|
|
||||||
// backdropApi.fromTo('opacity', 0.01, 0.35);
|
|
||||||
// this.ani.add(backdropApi);
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
@ -19,14 +19,14 @@ $menu-ios-box-shadow: 0 0 10px $menu-ios-box-shadow-color !default;
|
|||||||
background: $menu-ios-background;
|
background: $menu-ios-background;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ios .menu-content-reveal {
|
.menu-ios .menu-content-reveal {
|
||||||
box-shadow: $menu-ios-box-shadow;
|
box-shadow: $menu-ios-box-shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ios .menu-content-push {
|
.menu-ios .menu-content-push {
|
||||||
box-shadow: $menu-ios-box-shadow;
|
box-shadow: $menu-ios-box-shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ios ion-menu[type=overlay] .menu-inner {
|
ion-menu[type=overlay] .menu-ios {
|
||||||
box-shadow: $menu-ios-box-shadow;
|
box-shadow: $menu-ios-box-shadow;
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ $menu-md-box-shadow-color: rgba(0, 0, 0, .25) !default;
|
|||||||
$menu-md-box-shadow: 0 0 10px $menu-md-box-shadow-color !default;
|
$menu-md-box-shadow: 0 0 10px $menu-md-box-shadow-color !default;
|
||||||
|
|
||||||
|
|
||||||
.menu-md {
|
.menu-md .menu-inner {
|
||||||
background: $menu-md-background;
|
background: $menu-md-background;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { Component, Element, Event, EventEmitter, Prop, PropDidChange } from '@stencil/core';
|
import { Component, Element, Event, EventEmitter, Prop, PropDidChange } from '@stencil/core';
|
||||||
import { Config } from '../../index';
|
import { Config, Animation } from '../../index';
|
||||||
import { MenuController } from './menu-controller';
|
import { MenuController } from './menu-controller';
|
||||||
import { MenuType } from './menu-types';
|
import { isRightSide, Side, assert, checkEdgeSide } from '../../utils/helpers';
|
||||||
|
|
||||||
|
export type Lazy<T> =
|
||||||
|
{[P in keyof T]: (...args: any[]) => Promise<any>; };
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
tag: 'ion-menu',
|
tag: 'ion-menu',
|
||||||
@ -16,46 +18,44 @@ import { MenuType } from './menu-types';
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
export class Menu {
|
export class Menu {
|
||||||
@Element() private el: HTMLElement;
|
|
||||||
private _backdropElm: HTMLElement;
|
private _backdropEle: HTMLElement;
|
||||||
private _ctrl: MenuController;
|
private _menuInnerEle: HTMLElement;
|
||||||
private _unregCntClick: Function;
|
private _unregCntClick: Function;
|
||||||
private _unregBdClick: Function;
|
private _unregBdClick: Function;
|
||||||
private _activeBlock: string;
|
private _activeBlock: string;
|
||||||
|
|
||||||
private _cntElm: HTMLElement;
|
private _cntElm: HTMLElement;
|
||||||
private _type: MenuType;
|
private _animation: Animation;
|
||||||
private _init = false;
|
private _init = false;
|
||||||
private _isPane = false;
|
private _isPane = false;
|
||||||
|
private _isAnimating: boolean = false;
|
||||||
|
private _isOpen: boolean = false;
|
||||||
|
private _width: number = null;
|
||||||
|
|
||||||
mode: string;
|
mode: string;
|
||||||
color: string;
|
color: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
isRightSide: boolean = false;
|
||||||
|
|
||||||
|
@Element() private el: HTMLElement;
|
||||||
|
|
||||||
@Event() ionDrag: EventEmitter;
|
@Event() ionDrag: EventEmitter;
|
||||||
@Event() ionOpen: EventEmitter;
|
@Event() ionOpen: EventEmitter;
|
||||||
@Event() ionClose: EventEmitter;
|
@Event() ionClose: EventEmitter;
|
||||||
|
|
||||||
@Prop({ context: 'config' }) config: Config;
|
@Prop({ context: 'config' }) config: Config;
|
||||||
|
|
||||||
/**
|
@Prop({ connect: 'ion-menu-controller' }) lazyMenuCtrl: Lazy<MenuController>;
|
||||||
* @hidden
|
menuCtrl: MenuController;
|
||||||
*/
|
|
||||||
@Prop() isOpen: boolean = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @input {string} The content's id the menu should use.
|
||||||
*/
|
*/
|
||||||
@Prop() isAnimating: boolean = false;
|
@Prop() content: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
isRightSide: boolean = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @input {any} A reference to the content element the menu should use.
|
|
||||||
*/
|
|
||||||
@Prop() content: any;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @input {string} An id for the menu.
|
* @input {string} An id for the menu.
|
||||||
@ -67,27 +67,22 @@ export class Menu {
|
|||||||
* see the `menuType` in the [config](../../config/Config). Available options:
|
* see the `menuType` in the [config](../../config/Config). Available options:
|
||||||
* `"overlay"`, `"reveal"`, `"push"`.
|
* `"overlay"`, `"reveal"`, `"push"`.
|
||||||
*/
|
*/
|
||||||
@Prop() type: string;
|
@Prop() type: string = 'overlay';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @input {boolean} If true, the menu is enabled. Default `true`.
|
* @input {boolean} If true, the menu is enabled. Default `true`.
|
||||||
*/
|
*/
|
||||||
@Prop() enabled: boolean;
|
@Prop({ mutable: true }) enabled: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @input {string} Which side of the view the menu should be placed. Default `"start"`.
|
* @input {string} Which side of the view the menu should be placed. Default `"start"`.
|
||||||
*/
|
*/
|
||||||
@Prop() side: string = 'start';
|
@Prop() side: Side = 'start';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @input {boolean} If true, swiping the menu is enabled. Default `true`.
|
* @input {boolean} If true, swiping the menu is enabled. Default `true`.
|
||||||
*/
|
*/
|
||||||
@Prop() swipeEnabled: boolean;
|
@Prop() swipeEnabled: boolean = true;
|
||||||
|
|
||||||
@PropDidChange('swipeEnabled')
|
|
||||||
swipeEnabledChange(isEnabled: boolean) {
|
|
||||||
this.swipeEnable(isEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @input {boolean} If true, the menu will persist on child pages.
|
* @input {boolean} If true, the menu will persist on child pages.
|
||||||
@ -97,43 +92,64 @@ export class Menu {
|
|||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
@Prop() maxEdgeStart: number;
|
@Prop() maxEdgeStart: number = 50;
|
||||||
|
|
||||||
|
|
||||||
|
// @PropDidChange('side')
|
||||||
|
// sideChanged(side: Side) {
|
||||||
|
// // TODO: const isRTL = this._plt.isRTL;
|
||||||
|
// const isRTL = false;
|
||||||
|
// // this.isRightSide = isRightSide(side, isRTL);
|
||||||
|
// }
|
||||||
|
|
||||||
|
@PropDidChange('enabled')
|
||||||
|
enabledChanged() {
|
||||||
|
this._updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PropDidChange('swipeEnabled')
|
||||||
|
swipeEnabledChange() {
|
||||||
|
this._updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
ionViewWillLoad() {
|
||||||
|
return this.lazyMenuCtrl.getInstance().then(menu => this.menuCtrl = menu);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
ionViewDidLoad() {
|
ionViewDidLoad() {
|
||||||
this._backdropElm = this.el.querySelector('.menu-backdrop') as HTMLElement;
|
assert(!!this.menuCtrl, "menucontroller was not initialized");
|
||||||
|
|
||||||
this._init = true;
|
this._menuInnerEle = this.el.querySelector('.menu-inner') as HTMLElement;
|
||||||
|
this._backdropEle = this.el.querySelector('.menu-backdrop') as HTMLElement;
|
||||||
|
|
||||||
if (this.content) {
|
const contentQuery = (this.content)
|
||||||
if ((this.content).tagName as HTMLElement) {
|
? '> #' + this.content
|
||||||
this._cntElm = this.content;
|
: '[main]';
|
||||||
} else if (typeof this.content === 'string') {
|
const parent = this.el.parentElement;
|
||||||
this._cntElm = document.querySelector(this.content) as any;
|
const content = this._cntElm = parent.querySelector(contentQuery) as HTMLElement;
|
||||||
}
|
if (!content || !content.tagName) {
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._cntElm || !this._cntElm.tagName) {
|
|
||||||
// requires content element
|
// requires content element
|
||||||
return console.error('Menu: must have a "content" element to listen for drag events on.');
|
return console.error('Menu: must have a "content" element to listen for drag events on.');
|
||||||
}
|
}
|
||||||
|
// TODO: make PropDidChange work
|
||||||
|
this.isRightSide = isRightSide(this.side, false);
|
||||||
|
|
||||||
// add menu's content classes
|
// add menu's content classes
|
||||||
this._cntElm.classList.add('menu-content');
|
content.classList.add('menu-content');
|
||||||
this._cntElm.classList.add('menu-content-' + this.type);
|
content.classList.add('menu-content-' + this.type);
|
||||||
|
|
||||||
let isEnabled = this.enabled;
|
let isEnabled = this.enabled;
|
||||||
if (isEnabled === true || typeof isEnabled === 'undefined') {
|
if (isEnabled === true || typeof isEnabled === 'undefined') {
|
||||||
// check if more than one menu is on the same side
|
const menus = this.menuCtrl.getMenus();
|
||||||
isEnabled = !this._ctrl.getMenus().some(m => {
|
isEnabled = !menus.some(m => {
|
||||||
return m.side === this.side && m.enabled;
|
return m.side === this.side && m.enabled;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// register this menu with the app's menu controller
|
// register this menu with the app's menu controller
|
||||||
this._ctrl._register(this);
|
this.menuCtrl._register(this);
|
||||||
|
|
||||||
// mask it as enabled / disabled
|
// mask it as enabled / disabled
|
||||||
this.enable(isEnabled);
|
this.enable(isEnabled);
|
||||||
@ -143,30 +159,34 @@ export class Menu {
|
|||||||
return {
|
return {
|
||||||
attrs: {
|
attrs: {
|
||||||
'role': 'navigation',
|
'role': 'navigation',
|
||||||
'side': this.side,
|
'side': this.getSide(),
|
||||||
'type': this.type
|
'type': this.type
|
||||||
},
|
},
|
||||||
class: {
|
class: {
|
||||||
'menu-enabled': this.enabled
|
'menu-enabled': this._canOpen()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
getSide(): string {
|
||||||
// normalize the "type"
|
return this.isRightSide ? 'right' : 'left';
|
||||||
if (!this.type) {
|
}
|
||||||
this.type = this.config.get('menuType', 'overlay');
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
render() {
|
||||||
|
return ([
|
||||||
<div class='menu-inner'>
|
<div class='menu-inner'>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>,
|
</div>,
|
||||||
<ion-gesture class='menu-backdrop' props={{
|
<ion-backdrop class="menu-backdrop"></ion-backdrop> ,
|
||||||
// 'canStart': this.canStart.bind(this),
|
<ion-gesture props={{
|
||||||
// 'onStart': this.onDragStart.bind(this),
|
'canStart': this.canStart.bind(this),
|
||||||
// 'onMove': this.onDragMove.bind(this),
|
'onWillStart': this._swipeWillStart.bind(this),
|
||||||
// 'onEnd': this.onDragEnd.bind(this),
|
'onStart': this._swipeStart.bind(this),
|
||||||
|
'onMove': this._swipeProgress.bind(this),
|
||||||
|
'onEnd': this._swipeEnd.bind(this),
|
||||||
|
'maxEdgeStart': this.maxEdgeStart,
|
||||||
|
'edge': this.side,
|
||||||
|
'enabled': this._canOpen() && this.swipeEnabled,
|
||||||
'gestureName': 'menu-swipe',
|
'gestureName': 'menu-swipe',
|
||||||
'gesturePriority': 10,
|
'gesturePriority': 10,
|
||||||
'type': 'pan',
|
'type': 'pan',
|
||||||
@ -176,7 +196,7 @@ export class Menu {
|
|||||||
'disableScroll': true,
|
'disableScroll': true,
|
||||||
'block': this._activeBlock
|
'block': this._activeBlock
|
||||||
}}></ion-gesture>
|
}}></ion-gesture>
|
||||||
];
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -185,21 +205,25 @@ export class Menu {
|
|||||||
onBackdropClick(ev: UIEvent) {
|
onBackdropClick(ev: UIEvent) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this._ctrl.close();
|
this.menuCtrl.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
private _getType(): MenuType {
|
private prepareAnimation(): Promise<void> {
|
||||||
if (!this._type) {
|
const width = this._menuInnerEle.offsetWidth;
|
||||||
this._type = this._ctrl.create(this.type, this);
|
if (width === this._width) {
|
||||||
|
return Promise.resolve();
|
||||||
if (this.config.getBoolean('animate') === false) {
|
|
||||||
this._type.ani.duration(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return this._type;
|
if (this._animation) {
|
||||||
|
this._animation.destroy();
|
||||||
|
this._animation = null;
|
||||||
|
}
|
||||||
|
this._width = width;
|
||||||
|
return this.menuCtrl.create(this.type, this).then(ani => {
|
||||||
|
this._animation = ani;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -207,23 +231,44 @@ export class Menu {
|
|||||||
*/
|
*/
|
||||||
setOpen(shouldOpen: boolean, animated: boolean = true): Promise<boolean> {
|
setOpen(shouldOpen: boolean, animated: boolean = true): Promise<boolean> {
|
||||||
// If the menu is disabled or it is currenly being animated, let's do nothing
|
// If the menu is disabled or it is currenly being animated, let's do nothing
|
||||||
if ((shouldOpen === this.isOpen) || !this._canOpen() || this.isAnimating) {
|
if ((shouldOpen === this._isOpen) || !this._canOpen() || this._isAnimating) {
|
||||||
return Promise.resolve(this.isOpen);
|
return Promise.resolve(this._isOpen);
|
||||||
}
|
}
|
||||||
return new Promise(resolve => {
|
this._before();
|
||||||
this._before();
|
this.prepareAnimation()
|
||||||
this._getType().setOpen(shouldOpen, animated, () => {
|
.then(() => this._startAnimation(shouldOpen, animated))
|
||||||
|
.then(() => {
|
||||||
this._after(shouldOpen);
|
this._after(shouldOpen);
|
||||||
resolve(this.isOpen);
|
return this._isOpen;
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
_startAnimation(shouldOpen: boolean, animated: boolean): Promise<Animation> {
|
||||||
|
let done;
|
||||||
|
const promise = new Promise<Animation>(resolve => done = resolve);
|
||||||
|
const ani = this._animation
|
||||||
|
.onFinish(done, {oneTimeCallback: true, clearExistingCallacks: true })
|
||||||
|
.reverse(!shouldOpen);
|
||||||
|
|
||||||
|
if (animated) {
|
||||||
|
ani.play();
|
||||||
|
} else {
|
||||||
|
ani.syncPlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
_forceClosing() {
|
_forceClosing() {
|
||||||
this.isAnimating = true;
|
assert(this._isOpen, 'menu cannot be closed');
|
||||||
this._getType().setOpen(false, false, () => {
|
|
||||||
this._after(false);
|
this._isAnimating = true;
|
||||||
});
|
this._startAnimation(false, false)
|
||||||
|
.then(() => this._after(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
getWidth(): number {
|
||||||
|
return this._width;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -231,77 +276,120 @@ export class Menu {
|
|||||||
*/
|
*/
|
||||||
canSwipe(): boolean {
|
canSwipe(): boolean {
|
||||||
return this.swipeEnabled &&
|
return this.swipeEnabled &&
|
||||||
!this.isAnimating &&
|
!this._isAnimating &&
|
||||||
this._canOpen();
|
this._canOpen();
|
||||||
// TODO: && this._app.isEnabled();
|
// TODO: && this._app.isEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
isAnimating(): boolean {
|
||||||
|
return this._isAnimating;
|
||||||
|
}
|
||||||
|
|
||||||
_swipeBeforeStart() {
|
/**
|
||||||
if (!this.canSwipe()) {
|
* @hidden
|
||||||
return;
|
*/
|
||||||
}
|
isOpen(): boolean {
|
||||||
|
return this._isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
_swipeWillStart(): Promise<void> {
|
||||||
this._before();
|
this._before();
|
||||||
|
return this.prepareAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
_swipeStart() {
|
_swipeStart() {
|
||||||
if (!this.isAnimating) {
|
assert(!!this._animation, '_type is undefined');
|
||||||
|
if (!this._isAnimating) {
|
||||||
|
assert(false, '_isAnimating has to be true');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._getType().setProgressStart(this.isOpen);
|
// the cloned animation should not use an easing curve during seek
|
||||||
|
this._animation
|
||||||
|
.reverse(this._isOpen)
|
||||||
|
.progressStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
_swipeProgress(stepValue: number) {
|
_swipeProgress(slide: any) {
|
||||||
if (!this.isAnimating) {
|
assert(!!this._animation, '_type is undefined');
|
||||||
|
if (!this._isAnimating) {
|
||||||
|
assert(false, '_isAnimating has to be true');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._getType().setProgessStep(stepValue);
|
// const isRTL = false;
|
||||||
|
const z = this._width;
|
||||||
|
// const z = (this.isRightSide !== isRTL ? slide.min : slide.max);
|
||||||
|
const stepValue = (Math.abs(slide.deltaX) / z);
|
||||||
|
|
||||||
this.ionDrag.emit({ menu: this });
|
this._animation.progressStep(stepValue);
|
||||||
|
// TODO: this.ionDrag.emit({ menu: this });
|
||||||
}
|
}
|
||||||
|
|
||||||
_swipeEnd(shouldCompleteLeft: boolean, shouldCompleteRight: boolean, stepValue: number, velocity: number) {
|
_swipeEnd(slide: any) {
|
||||||
if (!this.isAnimating) {
|
assert(!!this._animation, '_type is undefined');
|
||||||
|
if (!this._isAnimating) {
|
||||||
|
assert(false, '_isAnimating has to be true');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const width = this._width;
|
||||||
|
const delta = Math.abs(slide.deltaX)
|
||||||
|
const stepValue = delta / width;
|
||||||
|
const velocity = slide.velocityX;
|
||||||
|
const z = width / 2;
|
||||||
|
const shouldCompleteRight = (velocity >= 0)
|
||||||
|
&& (velocity > 0.2 || slide.deltaX > z);
|
||||||
|
|
||||||
|
const shouldCompleteLeft = (velocity <= 0)
|
||||||
|
&& (velocity < -0.2 || slide.deltaX < -z);
|
||||||
|
|
||||||
// user has finished dragging the menu
|
|
||||||
const isRightSide = this.isRightSide;
|
const isRightSide = this.isRightSide;
|
||||||
const opening = !this.isOpen;
|
const opening = !this._isOpen;
|
||||||
const shouldComplete = (opening)
|
const shouldComplete = (opening)
|
||||||
? isRightSide ? shouldCompleteLeft : shouldCompleteRight
|
? isRightSide ? shouldCompleteLeft : shouldCompleteRight
|
||||||
: isRightSide ? shouldCompleteRight : shouldCompleteLeft;
|
: isRightSide ? shouldCompleteRight : shouldCompleteLeft;
|
||||||
|
|
||||||
this._getType().setProgressEnd(shouldComplete, stepValue, velocity, (isOpen: boolean) => {
|
let isOpen = (opening && shouldComplete);
|
||||||
console.debug('menu, swipeEnd', this.side);
|
if (!opening && !shouldComplete) {
|
||||||
this._after(isOpen);
|
isOpen = true;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
const missing = shouldComplete ? 1 - stepValue : stepValue;
|
||||||
|
const missingDistance = missing * width;
|
||||||
|
const dur = missingDistance / Math.abs(velocity);
|
||||||
|
const realDur = Math.min(dur, 380);
|
||||||
|
|
||||||
|
this._animation
|
||||||
|
.onFinish(() => this._after(isOpen), { clearExistingCallacks: true })
|
||||||
|
.progressEnd(shouldComplete, stepValue, realDur);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _before() {
|
private _before() {
|
||||||
|
assert(!this._isAnimating, '_before() should not be called while animating');
|
||||||
|
|
||||||
// this places the menu into the correct location before it animates in
|
// this places the menu into the correct location before it animates in
|
||||||
// this css class doesn't actually kick off any animations
|
// this css class doesn't actually kick off any animations
|
||||||
this.el.classList.add('show-menu');
|
this.el.classList.add('show-menu');
|
||||||
this._backdropElm.classList.add('show-backdrop');
|
this._backdropEle.classList.add('show-backdrop');
|
||||||
|
|
||||||
this.resize();
|
this.resize();
|
||||||
|
this._isAnimating = true;
|
||||||
// TODO: this._keyboard.close();
|
|
||||||
|
|
||||||
this.isAnimating = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _after(isOpen: boolean) {
|
private _after(isOpen: boolean) {
|
||||||
|
assert(this._isAnimating, '_before() should be called while animating');
|
||||||
|
|
||||||
// TODO: this._app.setEnabled(false, 100);
|
// TODO: this._app.setEnabled(false, 100);
|
||||||
|
|
||||||
// keep opening/closing the menu disabled for a touch more yet
|
// keep opening/closing the menu disabled for a touch more yet
|
||||||
// only add listeners/css if it's enabled and isOpen
|
// only add listeners/css if it's enabled and isOpen
|
||||||
// and only remove listeners/css if it's not open
|
// and only remove listeners/css if it's not open
|
||||||
// emit opened/closed events
|
// emit opened/closed events
|
||||||
this.isOpen = isOpen;
|
this._isOpen = isOpen;
|
||||||
this.isAnimating = false;
|
this._isAnimating = false;
|
||||||
|
|
||||||
// add/remove backdrop click listeners
|
// add/remove backdrop click listeners
|
||||||
this._backdropClick(isOpen);
|
this._backdropClick(isOpen);
|
||||||
@ -311,9 +399,7 @@ export class Menu {
|
|||||||
this._activeBlock = GESTURE_BLOCKER;
|
this._activeBlock = GESTURE_BLOCKER;
|
||||||
|
|
||||||
// add css class
|
// add css class
|
||||||
Context.dom.write(() => {
|
this._cntElm.classList.add('menu-content-open');
|
||||||
this._cntElm.classList.add('menu-content-open');
|
|
||||||
});
|
|
||||||
|
|
||||||
// emit open event
|
// emit open event
|
||||||
this.ionOpen.emit({ menu: this });
|
this.ionOpen.emit({ menu: this });
|
||||||
@ -323,11 +409,9 @@ export class Menu {
|
|||||||
this._activeBlock = null;
|
this._activeBlock = null;
|
||||||
|
|
||||||
// remove css classes
|
// remove css classes
|
||||||
Context.dom.write(() => {
|
this.el.classList.remove('show-menu');
|
||||||
this._cntElm.classList.remove('menu-content-open');
|
this._cntElm.classList.remove('menu-content-open');
|
||||||
this._cntElm.classList.remove('show-menu');
|
this._backdropEle.classList.remove('show-menu');
|
||||||
this._backdropElm.classList.remove('show-menu');
|
|
||||||
});
|
|
||||||
|
|
||||||
// emit close event
|
// emit close event
|
||||||
this.ionClose.emit({ menu: this });
|
this.ionClose.emit({ menu: this });
|
||||||
@ -359,11 +443,23 @@ export class Menu {
|
|||||||
// content && content.resize();
|
// content && content.resize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canStart(detail: any): boolean {
|
||||||
|
if (!this.canSwipe()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this._isOpen) {
|
||||||
|
return true;
|
||||||
|
} else if (this.getMenuController().getOpen()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return checkEdgeSide(detail.currentX, this.isRightSide, this.maxEdgeStart);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
toggle(): Promise<boolean> {
|
toggle(): Promise<boolean> {
|
||||||
return this.setOpen(!this.isOpen);
|
return this.setOpen(!this._isOpen);
|
||||||
}
|
}
|
||||||
|
|
||||||
_canOpen(): boolean {
|
_canOpen(): boolean {
|
||||||
@ -373,40 +469,30 @@ export class Menu {
|
|||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
|
// @PropDidChange('swipeEnabled')
|
||||||
|
// @PropDidChange('enabled')
|
||||||
_updateState() {
|
_updateState() {
|
||||||
const canOpen = this._canOpen();
|
const canOpen = this._canOpen();
|
||||||
|
|
||||||
// Close menu inmediately
|
// Close menu inmediately
|
||||||
if (!canOpen && this.isOpen) {
|
if (!canOpen && this._isOpen) {
|
||||||
|
assert(this._init, 'menu must be initialized');
|
||||||
// close if this menu is open, and should not be enabled
|
// close if this menu is open, and should not be enabled
|
||||||
this._forceClosing();
|
this._forceClosing();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.enabled && this._ctrl) {
|
if (this.enabled && this.menuCtrl) {
|
||||||
this._ctrl._setActiveMenu(this);
|
this.menuCtrl._setActiveMenu(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._init) {
|
if (!this._init) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
if (this._isOpen || (this._isPane && this.enabled)) {
|
||||||
// const gesture = this._gesture;
|
|
||||||
// // only listen/unlisten if the menu has initialized
|
|
||||||
// if (canOpen && this.swipeEnabled && !gesture.isListening) {
|
|
||||||
// // should listen, but is not currently listening
|
|
||||||
// console.debug('menu, gesture listen', this.side);
|
|
||||||
// gesture.listen();
|
|
||||||
|
|
||||||
// } else if (gesture.isListening && (!canOpen || !this.swipeEnabled)) {
|
|
||||||
// // should not listen, but is currently listening
|
|
||||||
// console.debug('menu, gesture unlisten', this.side);
|
|
||||||
// gesture.unlisten();
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (this.isOpen || (this._isPane && this.enabled)) {
|
|
||||||
this.resize();
|
this.resize();
|
||||||
}
|
}
|
||||||
|
assert(!this._isAnimating, 'can not be animating');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -414,7 +500,6 @@ export class Menu {
|
|||||||
*/
|
*/
|
||||||
enable(shouldEnable: boolean): Menu {
|
enable(shouldEnable: boolean): Menu {
|
||||||
this.enabled = shouldEnable;
|
this.enabled = shouldEnable;
|
||||||
this._updateState();
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -438,7 +523,6 @@ export class Menu {
|
|||||||
*/
|
*/
|
||||||
swipeEnable(shouldEnable: boolean): Menu {
|
swipeEnable(shouldEnable: boolean): Menu {
|
||||||
this.swipeEnabled = shouldEnable;
|
this.swipeEnabled = shouldEnable;
|
||||||
this._updateState();
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -460,21 +544,14 @@ export class Menu {
|
|||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
getBackdropElement(): HTMLElement {
|
getBackdropElement(): HTMLElement {
|
||||||
return this._backdropElm;
|
return this._backdropEle;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
width(): number {
|
|
||||||
return this.getMenuElement().offsetWidth;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
getMenuController(): MenuController {
|
getMenuController(): MenuController {
|
||||||
return this._ctrl;
|
return this.menuCtrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _backdropClick(shouldAdd: boolean) {
|
private _backdropClick(shouldAdd: boolean) {
|
||||||
@ -497,10 +574,10 @@ export class Menu {
|
|||||||
ionViewDidUnload() {
|
ionViewDidUnload() {
|
||||||
this._backdropClick(false);
|
this._backdropClick(false);
|
||||||
|
|
||||||
this._ctrl._unregister(this);
|
this.menuCtrl._unregister(this);
|
||||||
this._type && this._type.destroy();
|
this._animation && this._animation.destroy();
|
||||||
|
|
||||||
this._ctrl = this._type = this._cntElm = this._backdropElm = null;
|
this.menuCtrl = this._animation = this._cntElm = this._backdropEle = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
135
packages/core/src/components/menu/tests/basic.html
Normal file
135
packages/core/src/components/menu/tests/basic.html
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html dir="ltr">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Ionic Item Sliding</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
<script src="/dist/ionic.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<ion-app>
|
||||||
|
<ion-menu side="left">
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar color="secondary">
|
||||||
|
<ion-title>Left Menu</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
|
||||||
|
<ion-list>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
Open Right Menu
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item menuClose="left" class="e2eCloseLeftMenu" detail-none>
|
||||||
|
Close Menu
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item menuClose="left" detail-none>
|
||||||
|
Close Menu
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item menuClose="left" detail-none>
|
||||||
|
Close Menu
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item menuClose="left" detail-none>
|
||||||
|
Close Menu
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item menuClose="left" detail-none>
|
||||||
|
Close Menu
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item menuClose="left" detail-none>
|
||||||
|
Close Menu
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item menuClose="left" detail-none>
|
||||||
|
Close Menu
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item menuClose="left" detail-none>
|
||||||
|
Close Menu
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item menuClose="left" detail-none>
|
||||||
|
Close Menu
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item menuClose="left" detail-none>
|
||||||
|
Close Menu
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item menuClose="left" detail-none>
|
||||||
|
Close Menu
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item menuClose="left" detail-none>
|
||||||
|
Close Menu
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
</ion-list>
|
||||||
|
</ion-content>
|
||||||
|
|
||||||
|
<ion-footer>
|
||||||
|
<ion-toolbar color="secondary">
|
||||||
|
<ion-title>Footer</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-footer>
|
||||||
|
|
||||||
|
</ion-menu>
|
||||||
|
|
||||||
|
<ion-menu side="right" >
|
||||||
|
|
||||||
|
<ion-header id="id">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Hola</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
|
||||||
|
</ion-header>
|
||||||
|
<ion-content padding>
|
||||||
|
hola macho
|
||||||
|
</ion-content>
|
||||||
|
|
||||||
|
</ion-menu>
|
||||||
|
|
||||||
|
<ion-page main class="show-page">
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Menu Basic Test</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content padding>
|
||||||
|
<ion-button onclick="openLeft()">
|
||||||
|
Open left menu
|
||||||
|
</ion-button>
|
||||||
|
<ion-button onclick="openRight()">
|
||||||
|
Open right menu
|
||||||
|
</ion-button>
|
||||||
|
</ion-content>
|
||||||
|
</ion-page>
|
||||||
|
|
||||||
|
</ion-app>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function getMenu() {
|
||||||
|
return document.querySelector('ion-menu-controller');
|
||||||
|
}
|
||||||
|
function openLeft() {
|
||||||
|
console.log('Open left menu');
|
||||||
|
getMenu().open('left');
|
||||||
|
}
|
||||||
|
function openRight() {
|
||||||
|
console.log('Open right menu');
|
||||||
|
getMenu().open('right');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@ -82,9 +82,6 @@ export class Scroll {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
detail.directionX = detail.velocityDirectionX = (detail.deltaX > 0 ? 'left' : (detail.deltaX < 0 ? 'right' : null));
|
|
||||||
detail.directionY = detail.velocityDirectionY = (detail.deltaY > 0 ? 'up' : (detail.deltaY < 0 ? 'down' : null));
|
|
||||||
|
|
||||||
// actively scrolling
|
// actively scrolling
|
||||||
positions.push(detail.scrollTop, detail.scrollLeft, detail.timeStamp);
|
positions.push(detail.scrollTop, detail.scrollLeft, detail.timeStamp);
|
||||||
|
|
||||||
@ -106,15 +103,11 @@ export class Scroll {
|
|||||||
// compute relative movement between these two points
|
// compute relative movement between these two points
|
||||||
var movedTop = (positions[startPos - 2] - positions[endPos - 2]);
|
var movedTop = (positions[startPos - 2] - positions[endPos - 2]);
|
||||||
var movedLeft = (positions[startPos - 1] - positions[endPos - 1]);
|
var movedLeft = (positions[startPos - 1] - positions[endPos - 1]);
|
||||||
var factor = 16.67 / (positions[endPos] - positions[startPos]);
|
var factor = 16.67 / (positions[startPos] - positions[endPos]);
|
||||||
|
|
||||||
// based on XXms compute the movement to apply for each render step
|
// based on XXms compute the movement to apply for each render step
|
||||||
detail.velocityY = movedTop * factor;
|
detail.velocityY = movedTop * factor;
|
||||||
detail.velocityX = movedLeft * factor;
|
detail.velocityX = movedLeft * factor;
|
||||||
|
|
||||||
// figure out which direction we're scrolling
|
|
||||||
detail.velocityDirectionX = (movedLeft > 0 ? 'left' : (movedLeft < 0 ? 'right' : null));
|
|
||||||
detail.velocityDirectionY = (movedTop > 0 ? 'up' : (movedTop < 0 ? 'down' : null));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
packages/core/src/index.d.ts
vendored
2
packages/core/src/index.d.ts
vendored
@ -10,7 +10,6 @@ import { Loading, LoadingEvent, LoadingOptions } from './components/loading/load
|
|||||||
import { LoadingController } from './components/loading-controller/loading-controller';
|
import { LoadingController } from './components/loading-controller/loading-controller';
|
||||||
import { GestureDetail, GestureCallback } from './components/gesture/gesture';
|
import { GestureDetail, GestureCallback } from './components/gesture/gesture';
|
||||||
import { Menu } from './components/menu/menu';
|
import { Menu } from './components/menu/menu';
|
||||||
import { MenuType } from './components/menu/menu-types';
|
|
||||||
import { MenuController } from './components/menu/menu-controller';
|
import { MenuController } from './components/menu/menu-controller';
|
||||||
import { Modal, ModalOptions, ModalEvent } from './components/modal/modal';
|
import { Modal, ModalOptions, ModalEvent } from './components/modal/modal';
|
||||||
import { ModalController } from './components/modal-controller/modal-controller';
|
import { ModalController } from './components/modal-controller/modal-controller';
|
||||||
@ -77,7 +76,6 @@ export {
|
|||||||
LoadingEvent,
|
LoadingEvent,
|
||||||
Menu,
|
Menu,
|
||||||
MenuController,
|
MenuController,
|
||||||
MenuType,
|
|
||||||
Modal,
|
Modal,
|
||||||
ModalController,
|
ModalController,
|
||||||
ModalOptions,
|
ModalOptions,
|
||||||
|
@ -41,7 +41,7 @@ export function assert(bool: boolean, msg: string) {
|
|||||||
if (!bool) {
|
if (!bool) {
|
||||||
console.error(msg);
|
console.error(msg);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export function toDashCase(str: string) {
|
export function toDashCase(str: string) {
|
||||||
return str.replace(/([A-Z])/g, (g) => '-' + g[0].toLowerCase());
|
return str.replace(/([A-Z])/g, (g) => '-' + g[0].toLowerCase());
|
||||||
@ -64,6 +64,26 @@ export function pointerCoordX(ev: any): number {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateDetail(ev: any, detail: any) {
|
||||||
|
// get X coordinates for either a mouse click
|
||||||
|
// or a touch depending on the given event
|
||||||
|
let x = 0;
|
||||||
|
let y = 0;
|
||||||
|
if (ev) {
|
||||||
|
var changedTouches = ev.changedTouches;
|
||||||
|
if (changedTouches && changedTouches.length > 0) {
|
||||||
|
var touch = changedTouches[0];
|
||||||
|
x = touch.clientX;
|
||||||
|
y = touch.clientY;
|
||||||
|
}else if (ev.pageX !== undefined) {
|
||||||
|
x = ev.pageX;
|
||||||
|
y = ev.pageY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
detail.currentX = x;
|
||||||
|
detail.currentY = y;
|
||||||
|
}
|
||||||
|
|
||||||
export function pointerCoordY(ev: any): number {
|
export function pointerCoordY(ev: any): number {
|
||||||
// get Y coordinates for either a mouse click
|
// get Y coordinates for either a mouse click
|
||||||
// or a touch depending on the given event
|
// or a touch depending on the given event
|
||||||
@ -79,7 +99,9 @@ export function pointerCoordY(ev: any): number {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getElementReference(elm: any, ref: string) {
|
export type ElementRef = 'child' | 'parent' | 'body' | 'document' | 'window';
|
||||||
|
|
||||||
|
export function getElementReference(elm: any, ref: ElementRef) {
|
||||||
if (ref === 'child') {
|
if (ref === 'child') {
|
||||||
return elm.firstElementChild;
|
return elm.firstElementChild;
|
||||||
}
|
}
|
||||||
@ -139,9 +161,16 @@ export function getToolbarHeight(toolbarTagName: string, pageChildren: HTMLEleme
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hidden */
|
|
||||||
export type Side = 'left' | 'right' | 'start' | 'end';
|
export type Side = 'left' | 'right' | 'start' | 'end';
|
||||||
|
|
||||||
|
export function checkEdgeSide(posX: number, isRightSide: boolean, maxEdgeStart: number): boolean {
|
||||||
|
if (isRightSide) {
|
||||||
|
return posX >= window.innerWidth - maxEdgeStart;
|
||||||
|
} else {
|
||||||
|
return posX <= maxEdgeStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
* Given a side, return if it should be on the right
|
* Given a side, return if it should be on the right
|
||||||
|
@ -19,7 +19,7 @@ exports.config = {
|
|||||||
{ components: ['ion-item', 'ion-item-divider', 'ion-item-sliding', 'ion-item-options', 'ion-item-option', 'ion-label', 'ion-list', 'ion-list-header', 'ion-skeleton-text'] },
|
{ components: ['ion-item', 'ion-item-divider', 'ion-item-sliding', 'ion-item-options', 'ion-item-option', 'ion-label', 'ion-list', 'ion-list-header', 'ion-skeleton-text'] },
|
||||||
{ components: ['ion-input', 'ion-textarea'] },
|
{ components: ['ion-input', 'ion-textarea'] },
|
||||||
{ components: ['ion-loading', 'ion-loading-controller'] },
|
{ components: ['ion-loading', 'ion-loading-controller'] },
|
||||||
{ components: ['ion-menu'], priority: 'low' },
|
{ components: ['ion-menu', 'ion-menu-controller'], priority: 'low' },
|
||||||
{ components: ['ion-modal', 'ion-modal-controller'] },
|
{ components: ['ion-modal', 'ion-modal-controller'] },
|
||||||
{ components: ['ion-popover', 'ion-popover-controller'] },
|
{ components: ['ion-popover', 'ion-popover-controller'] },
|
||||||
{ components: ['ion-radio', 'ion-radio-group'] },
|
{ components: ['ion-radio', 'ion-radio-group'] },
|
||||||
|
Reference in New Issue
Block a user