mirror of
https://github.com/Hopiu/vue-material.git
synced 2026-05-09 16:04:45 +00:00
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:
commit
bb3d27be6a
7 changed files with 172 additions and 82 deletions
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
17
src/core/utils/transitionEndEventName.js
Normal file
17
src/core/utils/transitionEndEventName.js
Normal 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();
|
||||
Loading…
Reference in a new issue