Merge branch 'components/mdMenu' of https://github.com/marcosmoura/vue-material into components/mdMenu

* 'components/mdMenu' of https://github.com/marcosmoura/vue-material:
  Add keyboard shortcuts to menu
  Add close event on tab
  Close content on esc
  Drop transitionend dependency
  Keep menu content inside viewport
This commit is contained in:
Marcos Moura 2016-10-24 23:24:59 -02:00
commit bb3d27be6a
7 changed files with 172 additions and 82 deletions

View file

@ -49,16 +49,6 @@
<md-menu-item>My Item 3</md-menu-item>
</md-menu-content>
</md-menu>
<md-menu>
<md-button md-menu-trigger>Options</md-button>
<md-menu-content>
<md-menu-item>My Item 1</md-menu-item>
<md-menu-item>My Item 2</md-menu-item>
<md-menu-item>My Item 3</md-menu-item>
</md-menu-content>
</md-menu>
</demo-example>
</div>

View file

@ -44,7 +44,6 @@
"autosize": "^3.0.17",
"element.scrollintoviewifneeded-polyfill": "^1.0.1",
"scopedQuerySelectorShim": "github:lazd/scopedQuerySelectorShim",
"transitionEnd": "^1.0.2",
"vue": "^2.0.2"
},
"devDependencies": {

View file

@ -30,8 +30,8 @@ $menu-base-width: 56px;
opacity: 0;
transform: scale3D(.85, .85, 1);
transition: width $swift-ease-out-duration $swift-ease-out-timing-function,
opacity .2s $swift-ease-in-timing-function,
transform .15s .1s $swift-ease-in-timing-function;
opacity .25s $swift-ease-in-timing-function,
transform .1s .12s $swift-ease-in-timing-function;
color: rgba(#212121, .87);
&.md-active {
@ -40,8 +40,8 @@ $menu-base-width: 56px;
transform: scale3D(1, 1, 1);
transform-origin: center;
transition: width $swift-ease-out-duration $swift-ease-out-timing-function,
opacity .2s $swift-ease-out-timing-function,
transform .15s .1s $swift-ease-out-timing-function;
opacity .3s $swift-ease-out-timing-function,
transform .2s .073s $swift-ease-out-timing-function;
> * {
opacity: 1;
@ -83,7 +83,9 @@ $menu-base-width: 56px;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
&:hover,
&:focus,
&.md-highlighted {
background-color: rgba(#000, .12);
transition: $swift-ease-out;
}

View file

@ -7,9 +7,7 @@
<style lang="scss" src="./mdMenu.scss"></style>
<script>
import 'transitionEnd';
let transitionEnd = window.transitionEnd;
import transitionEndEventName from '../../core/utils/transitionEndEventName';
export default {
props: {
@ -24,7 +22,7 @@
},
data() {
return {
margin: 16,
margin: 4,
active: false
};
},
@ -55,10 +53,61 @@
this.menuContent.classList.add('md-size-' + this.mdSize);
},
closeOnOffClick(event) {
if (!this.$el.contains(event.target)) {
if (!this.$el.contains(event.target) && !this.menuContent.contains(event.target)) {
this.close();
}
},
isAboveOfViewport(pos) {
return pos.top <= this.margin;
},
isBelowOfViewport(pos) {
return pos.top + this.menuContent.offsetHeight + this.margin >= window.innerHeight;
},
isOnTheLeftOfViewport(pos) {
return pos.left <= this.margin;
},
isOnTheRightOfViewport(pos) {
return pos.left + this.menuContent.offsetWidth + this.margin >= window.innerWidth;
},
getInViewPosition(position) {
if (this.isAboveOfViewport(position)) {
position.top = this.margin;
position.origin = 'center top';
if (this.isOnTheLeftOfViewport(position)) {
position.origin = 'left top';
}
if (this.isOnTheRightOfViewport(position)) {
position.origin = 'right top';
}
}
if (this.isOnTheLeftOfViewport(position)) {
position.left = this.margin;
position.origin = 'left';
}
if (this.isOnTheRightOfViewport(position)) {
position.left = window.innerWidth - this.margin - this.menuContent.offsetWidth;
position.origin = 'right';
}
if (this.isBelowOfViewport(position)) {
position.top = window.innerHeight - this.margin - this.menuContent.offsetHeight;
position.origin = 'center bottom';
if (this.isOnTheLeftOfViewport(position)) {
position.origin = 'left bottom';
}
if (this.isOnTheRightOfViewport(position)) {
position.origin = 'right bottom';
}
}
return position;
},
getBottomRightPos() {
let menuTriggerRect = this.menuTrigger.getBoundingClientRect();
let position = {
@ -67,19 +116,7 @@
origin: 'left top'
};
if (position.left <= this.margin) {
position.left = this.margin;
position.origin = 'center top';
}
if (position.top <= this.margin) {
position.top = this.margin;
position.origin = 'left center';
}
if (position.left <= this.margin && position.top <= this.margin) {
position.origin = 'center';
}
this.getInViewPosition(position);
return position;
},
@ -91,19 +128,7 @@
origin: 'right top'
};
if (position.left <= this.margin) {
position.left = this.margin;
position.origin = 'center top';
}
if (top <= this.margin) {
position.top = this.margin;
position.origin = 'right center';
}
if (position.left <= this.margin && top <= this.margin) {
position.origin = 'center';
}
this.getInViewPosition(position);
return position;
},
@ -115,19 +140,7 @@
origin: 'left bottom'
};
if (position.left <= this.margin) {
position.left = this.margin;
position.origin = 'center bottom';
}
if (top <= this.margin) {
top = this.margin;
position.origin = 'left center';
}
if (position.left <= this.margin && position.top <= this.margin) {
position.origin = 'center';
}
this.getInViewPosition(position);
return position;
},
@ -139,19 +152,7 @@
origin: 'right bottom'
};
if (position.left <= this.margin) {
position.left = this.margin;
position.origin = 'center bottom';
}
if (position.top <= this.margin) {
position.top = this.margin;
position.origin = 'right center';
}
if (position.left <= this.margin && position.top <= this.margin) {
position.origin = 'center';
}
this.getInViewPosition(position);
return position;
},
@ -194,17 +195,24 @@
getComputedStyle(this.menuContent).top;
this.menuContent.classList.add('md-active');
this.menuContent.focus();
this.active = true;
},
close() {
transitionEnd(this.menuContent).bind(() => {
document.body.removeChild(this.menuContent);
document.removeEventListener('click', this.closeOnOffClick);
window.removeEventListener('resize', this.recalculateOnResize);
let close = (event) => {
if (this.menuContent && event.target === this.menuContent && event.propertyName === 'transform') {
this.menuContent.removeEventListener(transitionEndEventName, close);
this.menuTrigger.focus();
this.active = false;
transitionEnd(this.menuContent).unbind();
});
document.body.removeChild(this.menuContent);
document.removeEventListener('click', this.closeOnOffClick);
window.removeEventListener('resize', this.recalculateOnResize);
this.active = false;
}
};
this.menuContent.addEventListener(transitionEndEventName, close);
this.menuContent.classList.remove('md-active');
},

View file

@ -1,5 +1,49 @@
<template>
<div class="md-menu-content">
<div
class="md-menu-content"
@keydown.esc.prevent="close"
@keydown.tab.prevent="close"
@keydown.up.prevent="highlightItem(highlighted - 1)"
@keydown.down.prevent="highlightItem(highlighted + 1)"
@keydown.enter.prevent="fireClick"
@keydown.space.prevent="fireClick"
tabindex="-1">
<slot></slot>
</div>
</template>
<script>
export default {
data() {
return {
highlighted: false,
itemsAmount: 0
};
},
methods: {
close() {
this.highlighted = false;
this.$parent.close();
},
highlightItem(factor) {
if (factor >= 1 && factor <= this.itemsAmount) {
this.highlighted = factor;
} else {
this.highlighted = 1;
}
},
fireClick() {
if (this.highlighted > 0) {
this.$children[this.highlighted - 1].$el.click();
}
}
},
mounted() {
if (!this.$parent.$el.classList.contains('md-menu')) {
this.$destroy();
throw new Error('You must wrap the md-menu-content in a md-menu');
}
}
};
</script>

View file

@ -1,5 +1,8 @@
<template>
<div class="md-menu-item" v-md-ink-ripple @click="$emit('click')">
<div
class="md-menu-item"
:class="classes"
@click="close">
<div class="md-menu-item-content">
<slot></slot>
</div>
@ -8,6 +11,33 @@
<script>
export default {
data() {
return {
index: 0
};
},
computed: {
classes() {
return {
'md-highlighted': this.index === this.$parent.highlighted
};
}
},
methods: {
close() {
this.$emit('click');
this.$parent.close();
}
},
mounted() {
if (!this.$parent.$el.classList.contains('md-menu-content')) {
this.$destroy();
throw new Error('You must wrap the md-menu-item in a md-menu-content');
}
this.$parent.itemsAmount++;
this.index = this.$parent.itemsAmount;
}
};
</script>

View file

@ -0,0 +1,17 @@
function transitionEndEventName() {
const el = document.createElement('span');
const transitions = {
transition: 'transitionend',
OTransition: 'oTransitionEnd',
MozTransition: 'transitionend',
WebkitTransition: 'webkitTransitionEnd'
};
for (let transition in transitions) {
if (el.style[transition] !== undefined) {
return transitions[transition];
}
}
}
export default transitionEndEventName();