Merge pull request #108 from marcosmoura/components/mdDialog

Components/md dialog
This commit is contained in:
Marcos Moura 2016-11-22 01:47:22 -02:00 committed by GitHub
commit 92b4d56c4d
29 changed files with 851 additions and 90 deletions

64
.github/CONTRIBUTING.md vendored Normal file
View file

@ -0,0 +1,64 @@
# Vue Material Contributing Guide
Before submitting your contribution please read the guidelines.
- [Issue Reporting Guidelines](#issue-reporting-guidelines)
- [Pull Request Guidelines](#pull-request-guidelines)
- [Development Setup](#development-setup)
## Issue Reporting Guidelines
- Always search for your issue first. It may have already been answered, planned or fixed in some branch. New components and features will be planned on [Milestones](https://github.com/marcosmoura/vue-material/milestones) or on [Projects](https://github.com/marcosmoura/vue-material/projects).
- Only create issues for the newest version. For now. Until 1.0.0.
- Create a declarative title and describe clearly the steps necessary to reproduce the issue. If an issue labeled "need repro" receives no further input from the issue author for more than 3 days, it will be closed.
- If you want to show your code please use [Codepen](http://codepen.io/pen/) or [JSFiddle](https://jsfiddle.net/). You could start with [this template](http://codepen.io/vue-material/pen/WGavBE).
- In case you found a solution by yourself try to explain how you fixed it. It could be useful for somebody else. :)
## Pull Request Guidelines
- The `master` branch is basically just a snapshot of the latest stable release. All development should be done in dedicated branches. **Do not submit PRs against the `master` branch.**
- Work in the `src` or `docs` folder and **DO NOT** add `dist` in the commits.
- Make small commits as you work on the PR. They will be automatically squashed before merging.
- Provide convincing reason to add a new feature. Ideally you should open a suggestion/request issue first and have it greenlighted before working on it.
- If fixing a bug:
- If you are resolving a special issue, add the GitHub ID to your commit. E.g. `(fix something really ugly #xxx)`
- Provide detailed description of the bug in the PR.
## Development Setup
You will need [Node.js](http://nodejs.org) **version 6+**
After cloning the repo, run:
``` bash
$ npm install
```
### Commonly used NPM scripts
``` bash
### Start dev server with hot reload
npm run dev
### Check for errors
npm run lint
### Build everything
npm run build
### Build docs only
npm run build:docs
### Build lib only
npm run build:lib
```
The other tasks on package.json **SHOULD NOT** be executed.

45
.github/ISSUE_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,45 @@
<!--
Reporting a bug?
================
- Always search for your issue first. It may have already been answered, planned or fixed in some branch.
- Make sure to only create issues for the newest version.
- Create a declarative title and describe clearly the steps necessary to reproduce the issue. If an issue labeled "need repro" receives no further input from the issue author for more than 3 days, it will be closed.
- If you want to show your code please use [Codepen](http://codepen.io/pen/) or [JSFiddle](https://jsfiddle.net/). You could start with [this template](http://codepen.io/vue-material/pen/WGavBE).
- In case you found a solution by yourself, it could be helpful to explain how you fixed it.
- For bugs that involves build setups, you can create a reproduction repository with steps in the README.
- If your issue is resolved but still open, dont hesitate to close it. In case you found a solution by yourself, it could be helpful to explain how you fixed it.
Have a feature request?
=======================
- Remove the template from below and provide thoughtful commentary.
- Answer those questions:
- What will it allow you to do that you can't do today?
- How will it make current work-arounds straightforward?
- What potential bugs and edge cases does it help to avoid?
Do not create new features based on a problem that will only solve edge cases for your project. Remember that Vue Material aims to be lightweight and clean. :)
-->
<!-- BUG REPORT TEMPLATE -->
### Steps to reproduce
### Which browser?
<!-- Which versions of Vue, Vue Material, OS, browsers are affected? -->
### What is expected?
### What is actually happening?
<!-- Is there anything else we should know? -->
### Reproduction Link
<!-- If you want to show your code please use Codepen or JSFiddle. -->
<!-- You could start with this template: http://codepen.io/vue-material/pen/WGavBE.

4
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,4 @@
<!--
Please make sure to read the Pull Request Guidelines:
https://github.com/marcosmoura/vue-material/blob/master/.github/CONTRIBUTING.md#pull-request-guidelines
-->

View file

@ -1,6 +1,18 @@
# Vue.js Material
<p align="center">
<a href="https://marcosmoura.github.io/vue-material/" target="_blank">
<img width="150" src="https://marcosmoura.github.io/vue-material/assets/logo-vue-material-blue.png">
</a>
</p>
> Material Design for Vue.js
<p align="center">Material Design for Vue.js</p>
<p align="center">
<a href="https://www.npmjs.com/package/vue-material"><img src="https://img.shields.io/npm/dt/vue-material.svg" alt="Downloads"></a>
<a href="https://www.npmjs.com/package/vue-material"><img src="https://img.shields.io/npm/v/vue-material.svg" alt="Version"></a>
<a href="https://www.npmjs.com/package/vue-material"><img src="https://img.shields.io/npm/l/vue-material.svg" alt="License"></a> <br>
</p>
Vue Material is lightweight framework built exactly according to the <a href="http://material.google.com" target="_blank">Material Design</a> specs. It aims to deliver a collection of reusable components and a series of UI Elements to build applications with support to <a href="https://saucelabs.com/u/vuejs" target="_blank">modern Web Browsers</a> through Vue 2.0. Build powerful and well-designed web apps that can can fit on every screen. You can generate and use themes dynamically, use components on demand, take advantage of UI Elements and Components with an ease-to-use API.
@ -9,7 +21,7 @@ Vue Material is lightweight framework built exactly according to the <a href="ht
## Installation
Import Roboto and Google Icons from Google CDN:
Import Roboto and Material Icons from Google CDN:
``` html
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic">
@ -92,25 +104,9 @@ Apply your theme using <code>v-md-theme</code> directive:
## Changelog
<a href="https://marcosmoura.github.io/vue-material/#/changelog" target="_blank">Changelog</a>
## Build Setup
## Contributing
``` bash
### Install dependencies
npm install
yarn
### Start dev server with hot reload
npm run dev
### Build everything
npm run build
### Build docs only
npm run build:docs
### Build lib only
npm run build:lib
```
Please make sure to read the [Contributing Guide](https://github.com/marcosmoura/vue-material/blob/master/.github/CONTRIBUTING.md) before making a pull request.
## Credits and Thanks
* This library aims to delivery components using almost the same API of <a href="https://material.angularjs.org/latest/" target="_blank">Angular Material</a>

View file

@ -1,27 +0,0 @@
# Vue.js Material Docs
> Material Design for Vue.js
## Build Setup
``` bash
### install dependencies
npm install
### serve with hot reload at localhost:8080
npm run dev
### build for production with minification
npm run build
### run unit tests
npm run unit
### run e2e tests
npm run e2e
### run all tests
npm test
```
For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).

View file

@ -47,6 +47,10 @@
<router-link exact to="/components/checkbox">Checkbox</router-link>
</md-list-item>
<md-list-item class="md-inset">
<router-link exact to="/components/dialog">Dialog</router-link>
</md-list-item>
<md-list-item class="md-inset">
<router-link exact to="/components/icon">Icon</router-link>
</md-list-item>

View file

@ -37,18 +37,18 @@ let router = new VueRouter({
});
let Docs = Vue.component('app', App);
let handleSectionTheme = (route) => {
if (route.name.indexOf('introduction') >= 0) {
let handleSectionTheme = (currentRoute) => {
if (currentRoute.name.indexOf('introduction') >= 0) {
Docs.theme = 'blue';
} else if (route.name.indexOf('components') >= 0) {
} else if (currentRoute.name.indexOf('components') >= 0) {
Docs.theme = 'indigo';
} else if (route.name.indexOf('themes') >= 0) {
} else if (currentRoute.name.indexOf('themes') >= 0) {
Docs.theme = 'teal';
} else if (route.name.indexOf('ui-elements') >= 0) {
} else if (currentRoute.name.indexOf('ui-elements') >= 0) {
Docs.theme = 'blue-grey';
} else if (route.name.indexOf('changelog') >= 0) {
} else if (currentRoute.name.indexOf('changelog') >= 0) {
Docs.theme = 'orange';
} else if (route.name.indexOf('about') >= 0) {
} else if (currentRoute.name.indexOf('about') >= 0) {
Docs.theme = 'green';
} else {
Docs.theme = 'default';
@ -62,7 +62,7 @@ Docs = new Docs({
handleSectionTheme(router.currentRoute);
router.afterEach((currentRoute) => {
router.beforeEach((to, from, next) => {
let mainContent = document.querySelector('.main-content');
if (mainContent) {
@ -71,5 +71,7 @@ router.afterEach((currentRoute) => {
Docs.closeSidenav();
handleSectionTheme(currentRoute);
handleSectionTheme(to);
next();
});

View file

@ -0,0 +1,135 @@
<template>
<demo-page label="Components - Dialog">
<div slot="examples">
<demo-example label="Default" height="500">
<md-dialog-alert
:md-title="alert.title"
:md-content="alert.content"
:md-ok-text="alert.ok"
@open="onOpen"
@close="onClose"
ref="dialog1">
</md-dialog-alert>
<md-dialog-confirm
:md-title="confirm.title"
:md-content-html="confirm.contentHtml"
:md-ok-text="confirm.ok"
:md-cancel-text="confirm.cancel"
@open="onOpen"
@close="onClose"
ref="dialog2">
</md-dialog-confirm>
<md-dialog-prompt
:md-title="prompt.title"
:md-ok-text="prompt.ok"
:md-cancel-text="prompt.cancel"
:md-input-id="prompt.id"
:md-input-name="prompt.name"
:md-input-maxlength="prompt.maxlength"
:md-input-placeholder="prompt.placeholder"
v-model="prompt.value"
@open="onOpen"
@close="onClose"
ref="dialog3">
</md-dialog-prompt>
<md-dialog ref="dialog4" md-open-from="#custom" md-close-to="#custom">
<md-dialog-title>Lorem ipsum dolor sit amet</md-dialog-title>
<md-dialog-content>Nemo, nobis necessitatibus ut illo, ducimus ex.</md-dialog-content>
<md-dialog-actions>
<md-button class="md-primary" @click="closeDialog('dialog4')">Cancel</md-button>
<md-button class="md-primary" @click="closeDialog('dialog4')">Ok</md-button>
</md-dialog-actions>
</md-dialog>
<md-dialog md-open-from="#fab" md-close-to="#fab" ref="dialog5">
<md-dialog-title>Create new note</md-dialog-title>
<md-dialog-content>
<form>
<md-input-container>
<label>Note</label>
<md-textarea></md-textarea>
</md-input-container>
</form>
</md-dialog-content>
<md-dialog-actions>
<md-button class="md-primary" @click="closeDialog('dialog5')">Cancel</md-button>
<md-button class="md-primary" @click="closeDialog('dialog5')">Create</md-button>
</md-dialog-actions>
</md-dialog>
<md-button class="md-primary md-raised" @click="openDialog('dialog1')">Alert</md-button>
<md-button class="md-primary md-raised" @click="openDialog('dialog2')">Confirm</md-button>
<md-button class="md-primary md-raised" @click="openDialog('dialog3')">Prompt</md-button>
<md-button class="md-primary md-raised" id="custom" @click="openDialog('dialog4')">Custom</md-button>
<md-button class="md-fab md-fab-bottom-right" id="fab" @click="openDialog('dialog5')">
<md-icon>add</md-icon>
</md-button>
<div>Prompt Name: {{ prompt.value }}</div>
</demo-example>
</div>
<div slot="code">
<demo-example label="Default">
<code-block lang="xml">
</code-block>
</demo-example>
</div>
<div slot="api">
</div>
</demo-page>
</template>
<style lang="scss" scoped>
</style>
<script>
export default {
data: () => ({
alert: {
content: 'Your post has been deleted!',
ok: 'Cool!'
},
confirm: {
title: 'Use Google\'s location service?',
contentHtml: 'Let Google help apps determine location. <br> This means sending <strong>anonymous</strong> location data to Google, even when no apps are running.',
ok: 'Agree',
cancel: 'Disagree'
},
prompt: {
title: 'What\'s your name?',
ok: 'Done',
cancel: 'Cancel',
id: 'name',
name: 'name',
placeholder: 'Type your name...',
maxlength: 30,
value: ''
}
}),
methods: {
openDialog(ref) {
this.$refs[ref].open();
},
closeDialog(ref) {
this.$refs[ref].close();
},
onOpen() {
console.log('Opened');
},
onClose(type) {
console.log('Closed', type);
}
}
};
</script>

View file

@ -12,6 +12,7 @@ import Buttons from './pages/components/Buttons';
import ButtonToggle from './pages/components/ButtonToggle';
import Card from './pages/components/Card';
import Checkbox from './pages/components/Checkbox';
import Dialog from './pages/components/Dialog';
import Icon from './pages/components/Icon';
import Input from './pages/components/Input';
import List from './pages/components/List';
@ -95,6 +96,11 @@ const components = [
name: 'components:checkbox',
component: Checkbox
},
{
path: '/components/dialog',
name: 'components:dialog',
component: Dialog
},
{
path: '/components/icon',
name: 'components:icon',

View file

@ -0,0 +1,5 @@
import mdBackdrop from './mdBackdrop.vue';
export default function install(Vue) {
Vue.component('md-backdrop', Vue.extend(mdBackdrop));
}

View file

@ -0,0 +1,24 @@
@import '../../core/stylesheets/variables.scss';
.md-backdrop {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 99;
pointer-events: none;
background-color: rgba(#000, .54);
transform: translate3d(0, 0, 0);
opacity: 0;
transition: $swift-ease-in-out;
&.md-active {
opacity: 1;
pointer-events: auto;
}
&.md-transparent {
background: none;
}
}

View file

@ -0,0 +1,15 @@
<template>
<div class="md-backdrop" @click="close" @keyup.esc="close"></div>
</template>
<style lang="scss" src="./mdBackdrop.scss"></style>
<script>
export default {
methods: {
close() {
this.$emit('close');
}
}
};
</script>

View file

@ -0,0 +1,22 @@
import mdDialog from './mdDialog.vue';
import mdDialogTitle from './mdDialogTitle.vue';
import mdDialogContent from './mdDialogContent.vue';
import mdDialogActions from './mdDialogActions.vue';
import mdDialogAlert from './presets/mdDialogAlert.vue';
import mdDialogConfirm from './presets/mdDialogConfirm.vue';
import mdDialogPrompt from './presets/mdDialogPrompt.vue';
import mdDialogTheme from './mdDialog.theme';
export default function install(Vue) {
Vue.component('md-dialog', Vue.extend(mdDialog));
Vue.component('md-dialog-title', Vue.extend(mdDialogTitle));
Vue.component('md-dialog-content', Vue.extend(mdDialogContent));
Vue.component('md-dialog-actions', Vue.extend(mdDialogActions));
/* Presets */
Vue.component('md-dialog-alert', Vue.extend(mdDialogAlert));
Vue.component('md-dialog-confirm', Vue.extend(mdDialogConfirm));
Vue.component('md-dialog-prompt', Vue.extend(mdDialogPrompt));
Vue.material.styles.push(mdDialogTheme);
}

View file

@ -0,0 +1,102 @@
@import '../../core/stylesheets/variables.scss';
.md-dialog-container {
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
pointer-events: none;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 108;
&.md-active {
pointer-events: auto;
.md-dialog {
opacity: 1 !important;
transform: scale(1) !important;
transition: $swift-ease-out;
transition-property: opacity, transform;
}
}
}
.md-dialog-backdrop {
position: fixed;
z-index: 109;
}
.md-dialog {
min-width: 280px;
max-width: 80%;
max-height: 80%;
overflow: hidden;
position: relative;
z-index: 110;
outline: none;
border-radius: 2px;
opacity: 0;
box-shadow: $material-shadow-14dp;
transform: scale(.9, .85);
transform-origin: top center;
transition: opacity $swift-ease-out-duration $swift-ease-out-timing-function,
transform $swift-ease-out-duration .05s $swift-ease-out-timing-function;
will-change: opacity, transform;
&.md-transition-off {
transition: none !important;
}
p {
margin: 0;
}
}
.md-dialog-title {
margin-bottom: 20px;
padding: 24px 24px 0;
}
.md-dialog-content {
padding: 0 24px 24px;
&:first-child {
padding-top: 24px;
}
p:first-child:not(:only-child) {
margin-top: 0;
}
p:last-child:not(:only-child) {
margin-bottom: 0;
}
}
.md-dialog-body {
margin: 0 -24px;
padding: 0 24px;
overflow: auto;
}
.md-dialog-actions {
min-height: 52px;
padding: 8px 8px 8px 24px;
display: flex;
justify-content: flex-end;
align-items: center;
.md-button {
min-width: 64px;
margin: 0;
padding: 0 8px;
+ .md-button {
margin-left: 8px;
}
}
}

View file

@ -0,0 +1,7 @@
.THEME_NAME {
.md-dialog,
&.md-dialog {
background-color: #{'BACKGROUND-COLOR-A100'};
color: #{'BACKGROUND-CONTRAST'};
}
}

View file

@ -0,0 +1,145 @@
<template>
<div class="md-dialog-container" :class="classes" @keyup.esc="mdEscToClose && close()" tabindex="0">
<div class="md-dialog" ref="dialog" :style="styles" :class="dialogClasses">
<slot></slot>
</div>
<md-backdrop class="md-dialog-backdrop" :class="classes" v-if="mdBackdrop" ref="backdrop" @close="mdClickOutsideToClose && close()"></md-backdrop>
</div>
</template>
<style lang="scss" src="./mdDialog.scss"></style>
<script>
import transitionEndEventName from '../../core/utils/transitionEndEventName';
export default {
props: {
mdClickOutsideToClose: {
type: Boolean,
default: true
},
mdEscToClose: {
type: Boolean,
default: true
},
mdBackdrop: {
type: Boolean,
default: true
},
mdOpenFrom: String,
mdCloseTo: String,
mdFullscreen: {
type: Boolean,
default: false
}
},
data: () => ({
active: false,
transitionOff: false,
dialogTransform: ''
}),
computed: {
classes() {
return {
'md-active': this.active
};
},
dialogClasses() {
return {
'md-fullscreen': this.mdFullscreen,
'md-transition-off': this.transitionOff
};
},
styles() {
return {
transform: this.dialogTransform
};
}
},
methods: {
removeDialog() {
if (this.rootElement.contains(this.dialogElement)) {
this.$el.parentNode.removeChild(this.$el);
}
},
calculateDialogPos(ref) {
const reference = document.querySelector(ref);
if (reference) {
const openFromRect = reference.getBoundingClientRect();
const dialogRect = this.dialogInnerElement.getBoundingClientRect();
const widthInScale = openFromRect.width / dialogRect.width;
const heightInScale = openFromRect.height / dialogRect.height;
let distance = {
top: -(dialogRect.top - openFromRect.top),
left: -(dialogRect.left - openFromRect.left + openFromRect.width)
};
if (openFromRect.top > dialogRect.top + dialogRect.height) {
distance.top = openFromRect.top - dialogRect.top;
}
if (openFromRect.left > dialogRect.left + dialogRect.width) {
distance.left = openFromRect.left - dialogRect.left - openFromRect.width;
}
this.dialogTransform = `translate3D(${distance.left}px, ${distance.top}px, 0) scale(${widthInScale}, ${heightInScale})`;
}
},
open() {
this.rootElement.appendChild(this.dialogElement);
this.transitionOff = true;
this.calculateDialogPos(this.mdOpenFrom);
window.setTimeout(() => {
this.dialogElement.focus();
this.transitionOff = false;
this.active = true;
});
this.$emit('open');
},
close() {
if (this.rootElement.contains(this.dialogElement)) {
this.$nextTick(() => {
let cleanElement = () => {
let activeRipple = this.dialogElement.querySelector('.md-ripple.md-active');
if (activeRipple) {
activeRipple.classList.remove('md-active');
}
this.dialogInnerElement.removeEventListener(transitionEndEventName, cleanElement);
this.rootElement.removeChild(this.dialogElement);
this.dialogTransform = '';
};
this.transitionOff = true;
this.dialogTransform = '';
this.calculateDialogPos(this.mdCloseTo);
window.setTimeout(() => {
this.transitionOff = false;
this.active = false;
this.dialogInnerElement.addEventListener(transitionEndEventName, cleanElement);
});
this.$emit('close');
});
}
}
},
mounted() {
this.$nextTick(() => {
this.rootElement = this.$root.$el;
this.dialogElement = this.$el;
this.dialogInnerElement = this.$refs.dialog;
this.removeDialog();
});
},
beforeDestroy() {
this.removeDialog();
}
};
</script>

View file

@ -0,0 +1,5 @@
<template>
<div class="md-dialog-actions">
<slot></slot>
</div>
</template>

View file

@ -0,0 +1,5 @@
<template>
<div class="md-dialog-content">
<slot></slot>
</div>
</template>

View file

@ -0,0 +1,5 @@
<template>
<div class="md-dialog-title md-title">
<slot></slot>
</div>
</template>

View file

@ -0,0 +1,51 @@
<template>
<md-dialog class="md-dialog-alert" ref="dialog" @close="fireCloseEvent()">
<md-dialog-title v-if="mdTitle">{{ mdTitle }}</md-dialog-title>
<md-dialog-content v-if="mdContentHtml" v-html="mdContentHtml"></md-dialog-content>
<md-dialog-content v-else>{{ mdContent }}</md-dialog-content>
<md-dialog-actions>
<md-button class="md-primary" @click="close()">{{ mdOkText }}</md-button>
</md-dialog-actions>
</md-dialog>
</template>
<script>
export default {
props: {
mdTitle: String,
mdContent: String,
mdContentHtml: String,
mdOkText: {
type: String,
default: 'Ok'
}
},
data: () => ({
debounce: false
}),
methods: {
fireCloseEvent() {
if (!this.debounce) {
this.$emit('close');
}
},
open() {
this.$emit('open');
this.debounce = false;
this.$refs.dialog.open();
},
close() {
this.fireCloseEvent();
this.debounce = true;
this.$refs.dialog.close();
}
},
mounted() {
if (!this.mdContent && !this.mdContentHtml) {
throw new Error('Missing md-content or md-content-html attributes');
}
}
};
</script>

View file

@ -0,0 +1,56 @@
<template>
<md-dialog class="md-dialog-confirm" ref="dialog" @close="fireCloseEvent('cancel')">
<md-dialog-title v-if="mdTitle">{{ mdTitle }}</md-dialog-title>
<md-dialog-content v-if="mdContentHtml" v-html="mdContentHtml"></md-dialog-content>
<md-dialog-content v-else>{{ mdContent }}</md-dialog-content>
<md-dialog-actions>
<md-button class="md-primary" @click="close('cancel')">{{ mdCancelText }}</md-button>
<md-button class="md-primary" @click="close('ok')">{{ mdOkText }}</md-button>
</md-dialog-actions>
</md-dialog>
</template>
<script>
export default {
props: {
mdTitle: String,
mdContent: String,
mdContentHtml: String,
mdOkText: {
type: String,
default: 'Ok'
},
mdCancelText: {
type: String,
default: 'Cancel'
}
},
data: () => ({
debounce: false
}),
methods: {
fireCloseEvent(type) {
if (!this.debounce) {
this.$emit('close', type);
}
},
open() {
this.$emit('open');
this.debounce = false;
this.$refs.dialog.open();
},
close(type) {
this.fireCloseEvent(type);
this.debounce = true;
this.$refs.dialog.close();
}
},
mounted() {
if (!this.mdContent && !this.mdContentHtml) {
throw new Error('Missing md-content or md-content-html attributes');
}
}
};
</script>

View file

@ -0,0 +1,80 @@
<template>
<md-dialog class="md-dialog-prompt" ref="dialog" @close="fireCloseEvent('cancel')">
<md-dialog-title v-if="mdTitle">{{ mdTitle }}</md-dialog-title>
<md-dialog-content v-if="mdContentHtml" v-html="mdContentHtml"></md-dialog-content>
<md-dialog-content v-if="mdContent">{{ mdContent }}</md-dialog-content>
<md-dialog-content>
<md-input-container>
<md-input
ref="input"
:id="mdInputId"
:name="mdInputName"
:maxlength="mdInputMaxlength"
:placeholder="mdInputPlaceholder"
:value="value"
@keydown.enter.native="confirmValue"></md-input>
</md-input-container>
</md-dialog-content>
<md-dialog-actions>
<md-button class="md-primary" @click="close('cancel')">{{ mdCancelText }}</md-button>
<md-button class="md-primary" @click="close('ok')">{{ mdOkText }}</md-button>
</md-dialog-actions>
</md-dialog>
</template>
<script>
export default {
props: {
value: {
type: [String, Number],
required: true
},
mdTitle: String,
mdContent: String,
mdContentHtml: String,
mdOkText: {
type: String,
default: 'Ok'
},
mdCancelText: {
type: String,
default: 'Cancel'
},
mdInputId: String,
mdInputName: String,
mdInputMaxlength: [String, Number],
mdInputPlaceholder: String
},
data: () => ({
debounce: false
}),
methods: {
fireCloseEvent(type) {
if (!this.debounce) {
this.$emit('close', type);
}
},
open() {
this.$emit('open');
this.debounce = false;
this.$refs.dialog.open();
window.setTimeout(() => {
this.$refs.input.$el.focus();
});
},
close(type) {
this.fireCloseEvent(type);
this.debounce = true;
this.$refs.dialog.close();
},
confirmValue() {
this.$emit('input', this.$refs.input.$el.value);
this.close('ok');
}
}
};
</script>

View file

@ -276,6 +276,7 @@
position: relative;
z-index: 1;
transform: translate3D(0, 0, 0);
will-change: margin-bottom;
transition: $swift-ease-in-out;
&.md-transition-off {

View file

@ -18,7 +18,9 @@ $menu-base-width: 56px;
z-index: 120;
background-color: #fff;
border-radius: 2px;
filter: drop-shadow(0 1px 1px rgba(#000, $shadow-key-umbra-opacity)) drop-shadow(0 2px 2px rgba(#000, $shadow-key-penumbra-opacity)) drop-shadow(0 1px 1px rgba(#000, $shadow-ambient-shadow-opacity));
filter: drop-shadow(0 1px 1px rgba(#000, $shadow-key-umbra-opacity))
drop-shadow(0 2px 2px rgba(#000, $shadow-key-penumbra-opacity))
drop-shadow(0 1px 1px rgba(#000, $shadow-ambient-shadow-opacity));
opacity: 0;
transition: width $swift-ease-out-duration $swift-ease-out-timing-function,
opacity .25s $swift-ease-in-timing-function,

View file

@ -1,6 +1,8 @@
<template>
<div class="md-menu">
<slot></slot>
<md-backdrop class="md-menu-backdrop md-transparent md-active" ref="backdrop" @close="close"></md-backdrop>
</div>
</template>
@ -69,11 +71,6 @@
addNewDirectionMenuContentClass(direction) {
this.menuContent.classList.add('md-direction-' + direction.replace(' ', '-'));
},
closeOnOffClick(event) {
if (!this.$el.contains(event.target) && !this.menuContent.contains(event.target)) {
this.close();
}
},
getBottomRightPos() {
let menuTriggerRect = this.menuTrigger.getBoundingClientRect();
let position = {
@ -148,12 +145,12 @@
window.requestAnimationFrame(this.calculateMenuContentPos);
},
open() {
if (this.$root.$el.contains(this.menuContent)) {
this.$root.$el.removeChild(this.menuContent);
if (this.rootElement.contains(this.menuContent)) {
this.rootElement.removeChild(this.menuContent);
}
this.$root.$el.appendChild(this.menuContent);
document.addEventListener('click', this.closeOnOffClick);
this.rootElement.appendChild(this.menuContent);
this.rootElement.appendChild(this.backdropElement);
window.addEventListener('resize', this.recalculateOnResize);
this.calculateMenuContentPos();
@ -164,12 +161,11 @@
this.active = true;
},
close() {
let menuContent = this.menuContent;
let close = (event) => {
if (menuContent && event.target === menuContent) {
if (this.menuContent && event.target === this.menuContent) {
let activeRipple = this.menuContent.querySelector('.md-ripple.md-active');
menuContent.removeEventListener(transitionEndEventName, close);
this.menuContent.removeEventListener(transitionEndEventName, close);
this.menuTrigger.focus();
this.active = false;
@ -177,8 +173,8 @@
activeRipple.classList.remove('md-active');
}
this.$root.$el.removeChild(menuContent);
document.removeEventListener('click', this.closeOnOffClick);
this.rootElement.removeChild(this.menuContent);
this.rootElement.removeChild(this.backdropElement);
window.removeEventListener('resize', this.recalculateOnResize);
}
};
@ -195,13 +191,18 @@
}
},
mounted() {
this.menuTrigger = this.$el.querySelector('[md-menu-trigger]');
this.menuContent = this.$el.querySelector('.md-menu-content');
this.validateMenu();
this.addNewSizeMenuContentClass(this.mdSize);
this.addNewDirectionMenuContentClass(this.mdDirection);
this.menuContent.parentNode.removeChild(this.menuContent);
this.menuTrigger.addEventListener('click', this.toggle);
this.$nextTick(() => {
this.rootElement = this.$root.$el;
this.menuTrigger = this.$el.querySelector('[md-menu-trigger]');
this.menuContent = this.$el.querySelector('.md-menu-content');
this.backdropElement = this.$refs.backdrop.$el;
this.validateMenu();
this.addNewSizeMenuContentClass(this.mdSize);
this.addNewDirectionMenuContentClass(this.mdDirection);
this.$el.removeChild(this.$refs.backdrop.$el);
this.menuContent.parentNode.removeChild(this.menuContent);
this.menuTrigger.addEventListener('click', this.toggle);
});
}
};
</script>

View file

@ -3,17 +3,17 @@
.md-sidenav {
&.md-left .md-sidenav-content {
left: 0;
transform: translate3d(-100%, 0, 0);
transform: translate3D(-100%, 0, 0);
}
&.md-right .md-sidenav-content {
right: 0;
transform: translate3d(100%, 0, 0);
transform: translate3D(100%, 0, 0);
}
&.md-fixed {
.md-sidenav-content,
.md-backdrop {
.md-sidenav-backdrop {
position: fixed;
}
}
@ -26,6 +26,9 @@
z-index: 100;
pointer-events: none;
overflow: auto;
-webkit-overflow-scrolling: touch;
box-shadow: $material-shadow-16dp;
will-change: transform;
transition: $swift-ease-out;
}
@ -38,8 +41,8 @@
z-index: 99;
pointer-events: none;
background-color: rgba(#000, .54);
transform: translate3d(0, 0, 0);
opacity: 0;
will-change: opacity;
transition: $swift-ease-in-out;
}
}
@ -47,11 +50,10 @@
.md-sidenav.md-active {
.md-sidenav-content {
pointer-events: auto;
box-shadow: $material-shadow-16dp;
transform: translate3d(0, 0, 0);
transform: translate3D(0, 0, 0);
}
.md-backdrop {
.md-sidenav-backdrop {
opacity: 1;
pointer-events: auto;
}

View file

@ -4,7 +4,7 @@
<slot></slot>
</div>
<div class="md-backdrop" @click="close"></div>
<md-backdrop class="md-sidenav-backdrop" @close="close"></md-backdrop>
</div>
</template>

View file

@ -22,7 +22,7 @@
<script>
export default {
props: {
value: String,
value: [String, Number],
mdLarge: Boolean,
mdId: String,
mdName: String,

View file

@ -1,10 +1,12 @@
import mdCore from './core';
import mdAvatar from './components/mdAvatar';
import mdBackdrop from './components/mdBackdrop';
import mdBottomBar from './components/mdBottomBar';
import mdButton from './components/mdButton';
import mdButtonToggle from './components/mdButtonToggle';
import mdCheckbox from './components/mdCheckbox';
import mdCard from './components/mdCard';
import mdCheckbox from './components/mdCheckbox';
import mdDialog from './components/mdDialog';
import mdDivider from './components/mdDivider';
import mdIcon from './components/mdIcon';
import mdInputContainer from './components/mdInputContainer';
@ -24,11 +26,13 @@ import mdWhiteframe from './components/mdWhiteframe';
const options = {
mdCore,
mdAvatar,
mdBackdrop,
mdBottomBar,
mdButton,
mdButtonToggle,
mdCheckbox,
mdCard,
mdCheckbox,
mdDialog,
mdDivider,
mdIcon,
mdInputContainer,