Merge pull request #2754 from ElemeFE/1.2-try

1.2 try
This commit is contained in:
baiyaaaaa 2017-02-09 19:11:14 +08:00 committed by GitHub
commit 5baa8774cb
89 changed files with 3546 additions and 576 deletions

View File

@ -29,6 +29,7 @@
- [awesome-element](https://github.com/ElementUI/awesome-element)
- [FAQ](./FAQ.md)
- [Customize Theme](http://element.eleme.io/#/en-US/component/custom-theme)
- [Preview and generate theme online](https://elementui.github.io/theme-preview)
- Starter Kit
- [element-starter](https://github.com/ElementUI/element-starter)
- [element-cooking-starter](https://github.com/ElementUI/element-cooking-starter)

35
build/bin/build-locale.js Normal file
View File

@ -0,0 +1,35 @@
var fs = require('fs');
var save = require('file-save');
var resolve = require('path').resolve;
var basename = require('path').basename;
var localePath = resolve(__dirname, '../../src/locale/lang');
var fileList = fs.readdirSync(localePath);
var transform = function(filename, name, cb) {
require('babel-core').transformFile(resolve(localePath, filename), {
plugins: [
'add-module-exports',
['transform-es2015-modules-umd', {loose: true}]
],
moduleId: name
}, cb);
};
fileList.forEach(function(file) {
var name = basename(file, '.js');
transform(file, name, function(err, result) {
if (err) {
console.error(err);
} else {
var code = result.code;
code = code
.replace('define(\'', 'define(\'element/locale/')
.replace('global.', 'global.ELEMENT.lang = global.ELEMENT.lang || {}; \n global.ELEMENT.lang.');
save(resolve(__dirname, '../../lib/umd/locale', file)).write(code);
console.log(file);
}
});
});

View File

@ -58,5 +58,6 @@
"scrollbar": "./packages/scrollbar/index.js",
"carousel-item": "./packages/carousel-item/index.js",
"collapse": "./packages/collapse/index.js",
"collapse-item": "./packages/collapse-item/index.js"
"collapse-item": "./packages/collapse-item/index.js",
"cascader": "./packages/cascader/index.js"
}

View File

@ -0,0 +1,487 @@
<script>
module.exports = {
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}],
optionsWithDisabled: [{
value: 'zhejiang',
label: 'Zhejiang',
disabled: true,
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}],
selectedOptions: [],
selectedOptions2: ['jiangsu', 'nanjing', 'zhonghuamen']
};
},
methods: {
handleChange(value) {
console.log(value);
}
}
};
</script>
<style>
.demo-cascader {
.el-cascader {
width: 222px;
}
}
.demo-cascader-size {
.el-cascader {
vertical-align: top;
margin-right: 15px;
}
}
</style>
## Cascader
It's used to select from a set of associated data set. Such as province/city/district, company level, and categories.
### Basic usage
:::demo
```html
<el-cascader
placeholder="Please select"
:options="options"
v-model="selectedOptions"
@change="handleChange"
></el-cascader>
<script>
module.exports = {
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}],
selectedOptions: []
};
},
methods: {
handleChange(value) {
console.log(value);
}
}
};
</script>
```
:::
### Disabled option
:::demo
```html
<el-cascader
placeholder="Please select"
:options="optionsWithDisabled"
></el-cascader>
<script>
module.exports = {
data() {
return {
optionsWithDisabled: [{
value: 'zhejiang',
label: 'Zhejiang',
disabled: true,
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}]
};
}
};
</script>
```
:::
### Default Value
:::demo default value is assigned by an array type value.
```html
<el-cascader
placeholder="Please select"
:options="options"
v-model="selectedOptions2"
></el-cascader>
<script>
module.exports = {
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}],
selectedOptions2: ['jiangsu', 'nanjing', 'zhonghuamen']
};
}
};
</script>
```
:::
### Size
:::demo
```html
<div class="demo-cascader-size">
<el-cascader
placeholder="Please select"
:options="options"
size="large"
></el-cascader>
<el-cascader
placeholder="Please select"
:options="options"
></el-cascader>
<el-cascader
placeholder="Please select"
:options="options"
size="small"
></el-cascader>
</div>
<script>
module.exports = {
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}]
};
}
};
</script>
```
:::
### Hover to expand
Hover to expand the next level options, click to select option.
:::demo
```html
<el-cascader
placeholder="Please select"
:options="options"
expand-trigger="hover"
></el-cascader>
<script>
module.exports = {
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}]
};
}
};
</script>
```
:::
### Change on select
Allow only select parent options.
:::demo
```html
<el-cascader
placeholder="Please select"
:options="options"
change-on-select
></el-cascader>
<script>
module.exports = {
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}]
};
}
};
</script>
```
:::
### Search
Search and select options directly.
:::demo
```html
<el-cascader
placeholder="Please select"
:options="options"
filterable
></el-cascader>
<script>
module.exports = {
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}]
};
}
};
</script>
```
:::
### Attributes
| Attribute | Description | Type | Options | Default|
|---------- |-------------------- |---------|------------- |-------- |
| options | data source of the options | array | — | — |
| value | selected value | array | — | — |
| popper-class | className of popup overlay | string | — | — |
| placeholder | input placeholder | string | — | — |
| disabled | 是否禁用 | boolean | — | false |
| clearable | whether allow clear | boolean | — | false |
| expand-trigger | trigger mode of expandind the current item | string | click / hover | 'click' |
| filterable | whether the options can be searched | boolean | — | — |
| size | size | string | large / small / mini | — |
### Events
| Event Name | Description | Parameters |
|---------- |-------- |---------- |
| change | triggers when the binding value changes | value |

View File

@ -1,5 +1,15 @@
## Custom theme
Element uses BEM-styled CSS so that you can override styles easily. But if you need to replace styles at a large scale, e.g. change the theme color from blue to orange or green, maybe overriding them one by one is not a good idea, and this is where our theme customization tool kicks in.
Element uses BEM-styled CSS so that you can override styles easily. But if you need to replace styles at a large scale, e.g. change the theme color from blue to orange or green, maybe overriding them one by one is not a good idea, and this is where our theme customization tools kick in.
## Changing theme color
If you just want to change the theme color of Element, the [theme preview website](https://elementui.github.io/theme-preview/#/en-US) is recommended.
The main color of Element is bright and friendly blue. By changing it, you can make Element more visually connected to specific projects.
The above website enables you to preview theme of a new theme color in real-time, and it can generate a complete style package based on the new theme color for you to download directly (to import new style files in your project, please refer to the 'Import custom theme' or 'Import component theme on demand' part of this section).
## More customizations
If you need more customization than just changing the theme color, please follow these steps:
### Install related tool
First install the theme generator globally or locally. Local install is recommended because in this way, when others clone your project, npm will automatically install it for them.

View File

@ -164,6 +164,7 @@ The content of Dialog can be anything, even a table or a form. This example show
| size | size of Dialog | string | tiny/small/large/full | small |
| top | value for `top` of Dialog CSS, works when `size` is not `full` | string | — | 15% |
| modal | whether a mask is displayed | boolean | — | true |
| modal-append-to-body | whether to append modal to body element. If false, the modal will be appended to Dialog's parent element | boolean | — | true |
| lock-scroll | whether scroll of body is disabled while Dialog is displayed | boolean | — | true |
| custom-class | custom class names for Dialog | string | — | — |
| close-on-click-modal | whether the Dialog can be closed by clicking the mask | boolean | — | true |

View File

@ -72,6 +72,33 @@ Vue.use(Element, {
})
```
## Import via CDN
```html
<script src="//unpkg.com/vue"></script>
<script src="//unpkg.com/element-ui"></script>
<script src="//unpkg.com/element-ui/lib/umd/locale/en.js"></script>
<script>
ELEMENT.locale(ELEMENT.lang.en)
</script>
```
Compatible with `vue-i18n`
```html
<script src="//unpkg.com/vue"></script>
<script src="//unpkg.com/vue-i18n/dist/vue-i18n.js"></script>
<script src="//unpkg.com/element-ui"></script>
<script src="//unpkg.com/element-ui/lib/umd/locale/zh-CN.js"></script>
<script src="//unpkg.com/element-ui/lib/umd/locale/en.js"></script>
<script>
Vue.locale('en', ELEMENT.lang.en)
Vue.locale('zh-cn', ELEMENT.lang.zhCN)
</script>
```
Currently Element ships with the following languages:
<ul class="language-list">
<li>Simplified Chinese (zh-CN)</li>

View File

@ -120,7 +120,7 @@
}
}
}
.el-autocomplete__suggestions.my-autocomplete {
.el-autocomplete-suggestion.my-autocomplete {
li {
line-height: normal;
padding: 7px *;
@ -133,7 +133,6 @@
font-size: 12px;
color: #b4b4b4;
}
.highlighted .addr {
color: #ddd;
}

View File

@ -65,7 +65,7 @@ Radio should not have too many options. Otherwise, use the Select component inst
### Radio button group
Suitable for choosing from some mutually exclusive options.
Suitable for choosing from some mutually exclusive options.
:::demo Combine `<el-radio-group>` with `<el-radio>` to display a radio group. Bind a variable with `v-model` of `<el-radio-group>` element and set label value in `<el-radio>`. It also provides `change` event with the current value as its parameter.
@ -131,15 +131,15 @@ Radio with button styles.
### Radio Attributes
Attribute | Description | Type | Accepted Values | Default
Attribute | Description | Type | Accepted Values | Default
---- | ---- | ---- | ---- | ----
label | the value of radio | string/number | — | —
label | the value of radio | string/number/boolean | — | —
disabled | whether radio is disabled | boolean | — | false
name | native 'name' attribute | string | — | —
name | native 'name' attribute | string | — | —
### Radio-group Attributes
Attribute | Description | Type | Accepted Values | Default
Attribute | Description | Type | Accepted Values | Default
---- | ---- | ---- | ---- | ----
size | the size of radio buttons | string | large/small | —
fill | border and background color when button is active | string | — | #20a0ff |
@ -153,7 +153,7 @@ change | triggers when the bound value changes | the label value of the chosen r
### Radio-button Attributes
Attribute | Description | Type | Accepted Values | Default
Attribute | Description | Type | Accepted Values | Default
---- | ---- | ---- | ---- | ----
label | the value of radio | string/number | — | —
disabled | whether radio is disabled | boolean | — | false

View File

@ -114,7 +114,7 @@ Vertical step bars.
| Attribute | Description | Type | Accepted Values | Default |
|---------- |-------- |---------- |------------- |-------- |
| space | the spacing of each step, will be responsive if omitted | Number | — | — |
| space | the spacing of each step, will be responsive if omitted. Support percentage. | Number,String | — | — |
| direction | display direction | string | vertical/horizontal | horizontal |
| active | current activation step | number | — | 0 |
| process-status | status of current step | string | wait/process/finish/error/success | process |

View File

@ -2,9 +2,20 @@
export default {
data() {
return {
activeName: 'first',
activeName: 'second',
activeName2: 'first',
tabs: [{
editableTabsValue: '2',
editableTabsValue2: '2',
editableTabs: [{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content'
}, {
title: 'Tab 2',
name: '2',
content: 'Tab 2 content'
}],
editableTabs2: [{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content'
@ -17,11 +28,62 @@
}
},
methods: {
handleRemove(tab) {
console.log(tab);
},
handleClick(tab, event) {
console.log(tab, event);
},
handleTabsEdit(targetName, action) {
if (action === 'add') {
let newTabName = ++this.tabIndex + '';
this.editableTabs.push({
title: 'New Tab',
name: newTabName,
content: 'New Tab content'
});
this.editableTabsValue = newTabName;
}
if (action === 'remove') {
let tabs = this.editableTabs;
let activeName = this.editableTabsValue;
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
let nextTab = tabs[index + 1] || tabs[index - 1];
if (nextTab) {
activeName = nextTab.name;
}
}
});
}
this.editableTabsValue = activeName;
this.editableTabs = tabs.filter(tab => tab.name !== targetName);
}
},
addTab(targetName) {
let newTabName = ++this.tabIndex + '';
this.editableTabs2.push({
title: 'New Tab',
name: newTabName,
content: 'New Tab content'
});
this.editableTabsValue2 = newTabName;
},
removeTab(targetName) {
let tabs = this.editableTabs2;
let activeName = this.editableTabsValue2;
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
let nextTab = tabs[index + 1] || tabs[index - 1];
if (nextTab) {
activeName = nextTab.name;
}
}
});
}
this.editableTabsValue2 = activeName;
this.editableTabs2 = tabs.filter(tab => tab.name !== targetName);
}
}
}
@ -95,37 +157,6 @@ Tabs styled as cards.
```
:::
### Closable
Closable tabs.
:::demo You can set the closable attribute in el-tabs to make all tabs closable. Also, closable can be set in a tab panel to make that specific tab closable.
```html
<template>
<el-tabs type="card" :closable="true" @tab-click="handleClick" @tab-remove="handleRemove">
<el-tab-pane label="User">User</el-tab-pane>
<el-tab-pane label="Config">Config</el-tab-pane>
<el-tab-pane label="Role">Role</el-tab-pane>
<el-tab-pane label="Task">Task</el-tab-pane>
</el-tabs>
</template>
<script>
export default {
methods: {
handleRemove(tab) {
console.log(tab);
},
handleClick(tab, event) {
console.log(tab, event);
}
}
};
</script>
```
:::
### Border card
Border card tabs.
@ -161,11 +192,151 @@ You can use named slot to customize the tab label content.
```
:::
### Add & close tab
Only card type Tabs support addable & closeable.
:::demo
```html
<el-tabs v-model="editableTabsValue" type="card" editable @edit="handleTabsEdit">
<el-tab-pane
v-for="(item, index) in editableTabs"
:label="item.title"
:name="item.name"
>
{{item.content}}
</el-tab-pane>
</el-tabs>
<script>
export default {
data() {
return {
editableTabsValue: '2',
editableTabs: [{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content'
}, {
title: 'Tab 2',
name: '2',
content: 'Tab 2 content'
}],
tabIndex: 2
}
},
methods: {
handleTabsEdit(targetName, action) {
if (action === 'add') {
let newTabName = ++this.tabIndex + '';
this.editableTabs.push({
title: 'New Tab',
name: newTabName,
content: 'New Tab content'
});
this.editableTabsValue = newTabName;
}
if (action === 'remove') {
let tabs = this.editableTabs;
let activeName = this.editableTabsValue;
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
let nextTab = tabs[index + 1] || tabs[index - 1];
if (nextTab) {
activeName = nextTab.name;
}
}
});
}
this.editableTabsValue = activeName;
this.editableTabs = tabs.filter(tab => tab.name !== targetName);
}
}
}
}
</script>
```
:::
### Customized trigger button of new tab
:::demo
```html
<div style="margin-bottom: 20px;">
<el-button
size="small"
@click="addTab(editableTabsValue2)"
>
add tab
</el-button>
</div>
<el-tabs v-model="editableTabsValue2" type="card" closable @tab-remove="removeTab">
<el-tab-pane
v-for="(item, index) in editableTabs2"
:label="item.title"
:name="item.name"
>
{{item.content}}
</el-tab-pane>
</el-tabs>
<script>
export default {
data() {
return {
editableTabsValue2: '2',
editableTabs2: [{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content'
}, {
title: 'Tab 2',
name: '2',
content: 'Tab 2 content'
}],
tabIndex: 2
}
},
methods: {
addTab(targetName) {
let newTabName = ++this.tabIndex + '';
this.editableTabs2.push({
title: 'New Tab',
name: newTabName,
content: 'New Tab content'
});
this.editableTabsValue2 = newTabName;
},
removeTab(targetName) {
let tabs = this.editableTabs2;
let activeName = this.editableTabsValue2;
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
let nextTab = tabs[index + 1] || tabs[index - 1];
if (nextTab) {
activeName = nextTab.name;
}
}
});
}
this.editableTabsValue2 = activeName;
this.editableTabs2 = tabs.filter(tab => tab.name !== targetName);
}
}
}
</script>
```
:::
### Tabs Attributes
| Attribute | Description | Type | Accepted Values | Default |
|---------- |-------- |---------- |------------- |-------- |
| type | type of Tab | string | card/border-card | — |
| closable | whether Tab is closable | boolean | — | false |
| addable | whether Tab is addable | boolean | — | false |
| editable | whether Tab is addable and closable | boolean | — | false |
| active-name(deprecated) | name of the selected tab | string | — | name of first tab |
| value | name of the selected tab | string | — | name of first tab |
@ -173,7 +344,9 @@ You can use named slot to customize the tab label content.
| Event Name | Description | Parameters |
|---------- |-------- |---------- |
| tab-click | triggers when a tab is clicked | clicked tab |
| tab-remove | triggers when a tab is removed | removed tab |
| tab-remove | triggers when tab-remove button is clicked | name of the removed tab |
| tab-add | triggers when tab-add button is clicked | — |
| edit | triggers when tab-add button or tab-remove is clicked | (targetName, action) |
### Tab-pane Attributes
| Attribute | Description | Type | Accepted Values | Default |

View File

@ -22,6 +22,9 @@
showInput() {
this.inputVisible = true;
this.$nextTick(_ => {
this.$refs.saveTagInput.$refs.input.focus();
});
},
handleInputConfirm() {
@ -150,6 +153,9 @@ You can use the `close` event to add and remove tag dynamically.
showInput() {
this.inputVisible = true;
this.$nextTick(_ => {
this.$refs.saveTagInput.$refs.input.focus();
});
},
handleInputConfirm() {

View File

@ -0,0 +1,491 @@
<script>
module.exports = {
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake'
}]
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei'
}]
}]
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men'
}]
}]
}],
optionsWithDisabled: [{
value: 'zhejiang',
label: 'Zhejiang',
disabled: true,
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake'
}]
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei'
}]
}]
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men'
}]
}]
}],
selectedOptions: [],
selectedOptions2: ['jiangsu', 'nanjing', 'zhonghuamen']
};
},
methods: {
handleChange(value) {
console.log(value);
}
}
};
</script>
<style>
.demo-cascader {
.el-cascader {
width: 222px;
}
}
.demo-cascader-size {
.el-cascader {
vertical-align: top;
margin-right: 15px;
}
}
</style>
## 级联选择
需要从一组相关联的数据集合进行选择,例如省市区,公司层级,事物分类等。
从一个较大的数据集合中进行选择时,用多级分类进行分隔,方便选择。
### 基本使用
:::demo
```html
<el-cascader
placeholder="请选择"
:options="options"
v-model="selectedOptions"
@change="handleChange"
></el-cascader>
<script>
module.exports = {
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}],
selectedOptions: []
};
},
methods: {
handleChange(value) {
console.log(value);
}
}
};
</script>
```
:::
### 禁用选项
通过在数据源中设置 `disabled` 字段来声明该选项时禁用的
:::demo
```html
<el-cascader
placeholder="请选择"
:options="optionsWithDisabled"
></el-cascader>
<script>
module.exports = {
data() {
return {
optionsWithDisabled: [{
value: 'zhejiang',
label: 'Zhejiang',
disabled: true,
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}]
};
}
};
</script>
```
:::
### 默认值
:::demo 默认值通过数组的方式指定。
```html
<el-cascader
placeholder="请选择"
:options="options"
v-model="selectedOptions2"
></el-cascader>
<script>
module.exports = {
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}],
selectedOptions2: ['jiangsu', 'nanjing', 'zhonghuamen']
};
}
};
</script>
```
:::
### 尺寸
:::demo 提供三种尺寸的级联选择器
```html
<div class="demo-cascader-size">
<el-cascader
placeholder="请选择"
:options="options"
size="large"
></el-cascader>
<el-cascader
placeholder="请选择"
:options="options"
></el-cascader>
<el-cascader
placeholder="请选择"
:options="options"
size="small"
></el-cascader>
</div>
<script>
module.exports = {
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}]
};
}
};
</script>
```
:::
### 移入展开
在鼠标移入时就展开下级菜单,完成选择仍需要进行点击。
:::demo
```html
<el-cascader
placeholder="请选择"
:options="options"
expand-trigger="hover"
></el-cascader>
<script>
module.exports = {
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}]
};
}
};
</script>
```
:::
### 选择即改变
该模式下允许只选中父级选项。
:::demo
```html
<el-cascader
placeholder="请选择"
:options="options"
change-on-select
></el-cascader>
<script>
module.exports = {
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}]
};
}
};
</script>
```
:::
### 可搜索
可以直接搜索选项并选择。
:::demo
```html
<el-cascader
placeholder="请选择"
:options="options"
filterable
></el-cascader>
<script>
module.exports = {
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}]
};
}
};
</script>
```
:::
### Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|---------- |-------- |---------- |------------- |-------- |
| options | 可选项数据源 | array | — | — |
| value | 指定选中项 | array | — | — |
| popper-class | 自定义浮层类名 | string | — | — |
| placeholder | 输入框占位文本 | string | — | — |
| disabled | 是否禁用 | boolean | — | false |
| clearable | 是否支持清除 | boolean | — | false |
| expand-trigger | 次级菜单的展开方式 | string | click / hover | 'click' |
| filterable | 是否支持搜索选项 | boolean | — | — |
| size | 尺寸 | string | large / small / mini | — |
### Events
| 事件名称 | 说明 | 回调参数 |
|---------- |-------- |---------- |
| change | 当绑定值变化时触发的事件 | 当前值 |

View File

@ -1,5 +1,17 @@
## 自定义主题
Element 默认提供一套主题CSS 命名采用 BEM 的风格方便使用者覆盖样式。如果你想完全替换主题色或者部分样式,可以使用下面方法。
Element 默认提供一套主题CSS 命名采用 BEM 的风格方便使用者覆盖样式。如果你想完全替换主题色或者部分样式,可以使用下面的方法。
## 仅替换主题色
如果仅希望更换 Element 的主题色,推荐使用[在线主题生成工具](https://elementui.github.io/theme-preview)。
Element 默认的主题色是鲜艳、友好的蓝色。通过替换主题色,能够让 Element 的视觉更加符合具体项目的定位。
使用上述工具,可以很方便地实时预览主题色改变之后的视觉,同时它还可以基于新的主题色生成完整的样式文件包,供直接下载使用(关于如何使用下载的主题包,请参考本节「引入自定义主题」和「搭配插件按需引入组件主题」部分)。
如果希望进行除主题色之外的样式定制,请继续阅读。
## 深层次的定制
如果仅仅改变主题色不能满足你的需求,请按以下步骤进行更深层次的主题定制:
### 安装工具
首先安装「主题生成工具」,可以全局安装或者安装在当前项目下,推荐安装在项目里,方便别人 clone 项目时能直接安装依赖并启动,这里以全局安装做演示。

View File

@ -183,6 +183,7 @@ Dialog 组件的内容可以是任意的,甚至可以是表格或表单,下
| size | Dialog 的大小 | string | tiny/small/large/full | small |
| top | Dialog CSS 中的 top 值(仅在 size 不为 full 时有效) | string | — | 15% |
| modal | 是否需要遮罩层 | boolean | — | true |
| modal-append-to-body | 遮罩层是否插入至 body 元素上,若为 false则遮罩层会插入至 Dialog 的父元素上 | boolean | — | true |
| lock-scroll | 是否在 Dialog 出现时将 body 滚动锁定 | boolean | — | true |
| custom-class | Dialog 的自定义类名 | string | — | — |
| close-on-click-modal | 是否可以通过点击 modal 关闭 Dialog | boolean | — | true |

View File

@ -84,6 +84,33 @@ Vue.use(Element, {
})
```
## 通过 CDN 的方式加载语言文件
```html
<script src="//unpkg.com/vue"></script>
<script src="//unpkg.com/element-ui"></script>
<script src="//unpkg.com/element-ui/lib/umd/locale/en.js"></script>
<script>
ELEMENT.locale(ELEMENT.lang.en)
</script>
```
搭配 `vue-i18n` 使用
```html
<script src="//unpkg.com/vue"></script>
<script src="//unpkg.com/vue-i18n/dist/vue-i18n.js"></script>
<script src="//unpkg.com/element-ui"></script>
<script src="//unpkg.com/element-ui/lib/umd/locale/zh-CN.js"></script>
<script src="//unpkg.com/element-ui/lib/umd/locale/en.js"></script>
<script>
Vue.locale('en', ELEMENT.lang.en)
Vue.locale('zh-cn', ELEMENT.lang.zhCN)
</script>
```
目前 Element 内置了以下语言:
<ul class="language-list">
<li>简体中文zh-CN</li>

View File

@ -161,7 +161,7 @@
}
}
}
.el-autocomplete__suggestions.my-autocomplete {
.el-autocomplete-suggestion.my-autocomplete {
li {
line-height: normal;
padding: 7px *;
@ -174,7 +174,6 @@
font-size: 12px;
color: #b4b4b4;
}
.highlighted .addr {
color: #ddd;
}

View File

@ -137,7 +137,7 @@
### Radio Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|---------- |-------- |---------- |------------- |-------- |
| label | Radio 的 value | string,number | — | — |
| label | Radio 的 value | string,number,boolean | — | — |
| disabled | 是否禁用 | boolean | — | false |
| name | 原生 name 属性 | string | — | — |

View File

@ -108,7 +108,7 @@
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|---------- |-------- |---------- |------------- |-------- |
| space | 每个 step 的间距,不填写将自适应间距 | Number | — | — |
| space | 每个 step 的间距,不填写将自适应间距。支持百分比。 | Number,String | — | — |
| direction | 显示方向 | string | vertical/horizontal | horizontal |
| active | 设置当前激活步骤 | number | — | 0 |
| process-status | 设置当前步骤的状态 | string | wait/process/finish/error/success | process |

View File

@ -4,7 +4,18 @@
return {
activeName: 'second',
activeName2: 'first',
tabs: [{
editableTabsValue: '2',
editableTabsValue2: '2',
editableTabs: [{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content'
}, {
title: 'Tab 2',
name: '2',
content: 'Tab 2 content'
}],
editableTabs2: [{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content'
@ -17,11 +28,62 @@
}
},
methods: {
handleRemove(tab) {
console.log(tab);
},
handleClick(tab, event) {
console.log(tab, event);
},
handleTabsEdit(targetName, action) {
if (action === 'add') {
let newTabName = ++this.tabIndex + '';
this.editableTabs.push({
title: 'New Tab',
name: newTabName,
content: 'New Tab content'
});
this.editableTabsValue = newTabName;
}
if (action === 'remove') {
let tabs = this.editableTabs;
let activeName = this.editableTabsValue;
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
let nextTab = tabs[index + 1] || tabs[index - 1];
if (nextTab) {
activeName = nextTab.name;
}
}
});
}
this.editableTabsValue = activeName;
this.editableTabs = tabs.filter(tab => tab.name !== targetName);
}
},
addTab(targetName) {
let newTabName = ++this.tabIndex + '';
this.editableTabs2.push({
title: 'New Tab',
name: newTabName,
content: 'New Tab content'
});
this.editableTabsValue2 = newTabName;
},
removeTab(targetName) {
let tabs = this.editableTabs2;
let activeName = this.editableTabsValue2;
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
let nextTab = tabs[index + 1] || tabs[index - 1];
if (nextTab) {
activeName = nextTab.name;
}
}
});
}
this.editableTabsValue2 = activeName;
this.editableTabs2 = tabs.filter(tab => tab.name !== targetName);
}
}
}
@ -95,36 +157,6 @@
```
:::
### 可关闭
可以关闭标签页。
:::demo 通过设置 `closable` 属性来打开 `Tabs` 的可关闭标签效果, `closable` 也可以设置在 `Tab Panel` 中实现部分标签页的可关闭效果。
```html
<template>
<el-tabs type="card" closable @tab-click="handleClick" @tab-remove="handleRemove">
<el-tab-pane label="用户管理">用户管理</el-tab-pane>
<el-tab-pane label="配置管理">配置管理</el-tab-pane>
<el-tab-pane label="角色管理">角色管理</el-tab-pane>
<el-tab-pane label="定时任务补偿">定时任务补偿</el-tab-pane>
</el-tabs>
</template>
<script>
export default {
methods: {
handleRemove(tab) {
console.log(tab);
},
handleClick(tab, event) {
console.log(tab, event);
}
}
};
</script>
```
:::
### 卡片化
卡片化的标签页。
@ -158,18 +190,141 @@
```
:::
### 动态增标签页
### 动态增标签页
展示如何通过触发器来动态增加标签页
增减标签页按钮只能在选项卡样式的标签页下使用
:::demo
```html
<el-tabs v-model="editableTabsValue" type="card" editable @edit="handleTabsEdit">
<el-tab-pane
v-for="(item, index) in editableTabs"
:label="item.title"
:name="item.name"
>
{{item.content}}
</el-tab-pane>
</el-tabs>
<script>
export default {
data() {
return {
editableTabsValue: '2',
editableTabs: [{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content'
}, {
title: 'Tab 2',
name: '2',
content: 'Tab 2 content'
}],
tabIndex: 2
}
},
methods: {
handleTabsEdit(targetName, action) {
if (action === 'add') {
let newTabName = ++this.tabIndex + '';
this.editableTabs.push({
title: 'New Tab',
name: newTabName,
content: 'New Tab content'
});
this.editableTabsValue = newTabName;
}
if (action === 'remove') {
let tabs = this.editableTabs;
let activeName = this.editableTabsValue;
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
let nextTab = tabs[index + 1] || tabs[index - 1];
if (nextTab) {
activeName = nextTab.name;
}
}
});
}
this.editableTabsValue = activeName;
this.editableTabs = tabs.filter(tab => tab.name !== targetName);
}
}
}
}
</script>
```
:::
### 自定义增加标签页触发器
:::demo
```html
<div style="margin-bottom: 20px;">
<el-button size="small" @click="tabs.push({ name: 'Tab ' + ++tabIndex, title: 'new Tab', content: 'new Tab content' })">add tab</el-button>
<el-button
size="small"
@click="addTab(editableTabsValue2)"
>
add tab
</el-button>
</div>
<el-tabs type="card" closable>
<el-tab-pane v-for="(item, index) in tabs" :label="item.title" :name="item.name">{{item.content}}</el-tab-pane>
<el-tabs v-model="editableTabsValue2" type="card" closable @tab-remove="removeTab">
<el-tab-pane
v-for="(item, index) in editableTabs2"
:label="item.title"
:name="item.name"
>
{{item.content}}
</el-tab-pane>
</el-tabs>
<script>
export default {
data() {
return {
editableTabsValue2: '2',
editableTabs2: [{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content'
}, {
title: 'Tab 2',
name: '2',
content: 'Tab 2 content'
}],
tabIndex: 2
}
},
methods: {
addTab(targetName) {
let newTabName = ++this.tabIndex + '';
this.editableTabs2.push({
title: 'New Tab',
name: newTabName,
content: 'New Tab content'
});
this.editableTabsValue2 = newTabName;
},
removeTab(targetName) {
let tabs = this.editableTabs2;
let activeName = this.editableTabsValue2;
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
let nextTab = tabs[index + 1] || tabs[index - 1];
if (nextTab) {
activeName = nextTab.name;
}
}
});
}
this.editableTabsValue2 = activeName;
this.editableTabs2 = tabs.filter(tab => tab.name !== targetName);
}
}
}
</script>
```
:::
@ -178,14 +333,18 @@
|---------- |-------- |---------- |------------- |-------- |
| type | 风格类型 | string | card/border-card | — |
| closable | 标签是否可关闭 | boolean | — | false |
| addable | 标签是否可增加 | boolean | — | false |
| editable | 标签是否同时可增加和关闭 | boolean | — | false |
| active-name(deprecated) | 选中选项卡的 name | string | — | 第一个选项卡的 name |
| value | 绑定值,选中选项卡的 name | string | — | 第一个选项卡的 name |
### Tabs Events
| 事件名称 | 说明 | 回调参数 |
|---------- |-------- |---------- |
| tab-click | tab 被选中的钩子 | 被选中的标签 tab 实例 |
| tab-remove | tab 被删除的钩子 | 被删除的标签 tab 实例 |
| tab-click | tab 被选中时触发 | 被选中的标签 tab 实例 |
| tab-remove | 点击 tab 移除按钮后触发 | 被删除的标签的 name |
| tab-add | 点击 tabs 的新增按钮后触发 | — |
| edit | 点击 tabs 的新增按钮或 tab 被关闭后触发 | (targetName, action) |
### Tab-pane Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值 |

View File

@ -22,6 +22,9 @@
showInput() {
this.inputVisible = true;
this.$nextTick(_ => {
this.$refs.saveTagInput.$refs.input.focus();
});
},
handleInputConfirm() {
@ -150,6 +153,9 @@
showInput() {
this.inputVisible = true;
this.$nextTick(_ => {
this.$refs.saveTagInput.$refs.input.focus();
});
},
handleInputConfirm() {

View File

@ -76,6 +76,10 @@
"path": "/select",
"title": "Select 选择器"
},
{
"path": "/cascader",
"title": "Cascader 级联选择"
},
{
"path": "/switch",
"title": "Switch 开关"
@ -298,6 +302,10 @@
"path": "/select",
"title": "Select"
},
{
"path": "/cascader",
"title": "Cascader"
},
{
"path": "/switch",
"title": "Switch"

View File

@ -13,13 +13,14 @@
"build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js",
"build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-default/gulpfile.js && cp-cli packages/theme-default/lib lib/theme-default",
"build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",
"build:umd": "node build/bin/build-locale.js",
"clean": "rimraf lib && rimraf packages/*/lib && rimraf test/**/coverage && lerna clean --yes",
"deploy": "npm run deploy:build && gh-pages -d examples/element-ui --remote eleme && del examples/element-ui",
"deploy:build": "npm run build:file && cooking build -c build/cooking.demo.js -p && echo element.eleme.io>>examples/element-ui/CNAME",
"dev": "npm run bootstrap && npm run build:file && cooking watch -c build/cooking.demo.js -p",
"dev:play": "npm run build:file && cross-env PLAY_ENV=true cooking watch -c build/cooking.demo.js -p",
"dist": "npm run clean && npm run build:file && npm run lint && cooking build -c build/cooking.conf.js,build/cooking.common.js,build/cooking.component.js -p && npm run build:utils && npm run build:theme",
"dist:all": "node build/bin/build-all.js",
"dist": "npm run clean && npm run build:file && npm run lint && cooking build -c build/cooking.conf.js,build/cooking.common.js,build/cooking.component.js -p && npm run build:utils && npm run build:umd && npm run build:theme",
"dist:all": "node build/bin/build-all.js && npm run build:theme",
"i18n": "node build/bin/i18n.js",
"lint": "eslint src/**/* test/**/* packages/**/*.{js,vue} build/**/* --quiet",
"pub": "npm run bootstrap && sh build/git-release.sh && sh build/release.sh",
@ -56,6 +57,7 @@
"babel-cli": "^6.14.0",
"babel-core": "^6.14.0",
"babel-loader": "^6.2.5",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-module-resolver": "^2.2.0",
"babel-plugin-syntax-jsx": "^6.8.0",
"babel-plugin-transform-vue-jsx": "^3.3.0",
@ -113,7 +115,7 @@
"uppercamelcase": "^1.1.0",
"url-loader": "^0.5.7",
"vue": "^2.1.8",
"vue-loader": "^10.0.2",
"vue-loader": "^10.3.0",
"vue-markdown-loader": "^0.5.1",
"vue-router": "^2.0.0",
"vue-template-compiler": "^2.1.8",

View File

@ -1,30 +1,36 @@
<template>
<transition name="el-zoom-in-top" @after-leave="doDestroy">
<ul
<div
v-show="showPopper"
class="el-autocomplete__suggestions"
class="el-autocomplete-suggestion"
:class="{ 'is-loading': parent.loading }"
:style="{ width: dropdownWidth }"
>
<li v-if="parent.loading"><i class="el-icon-loading"></i></li>
<template v-for="(item, index) in suggestions" v-else>
<li
v-if="!parent.customItem"
:class="{'highlighted': parent.highlightedIndex === index}"
@click="select(item)"
>
{{item.value}}
</li>
<component
v-else
:class="{'highlighted': parent.highlightedIndex === index}"
@click="select(item)"
:is="parent.customItem"
:item="item"
:index="index">
</component>
</template>
</ul>
<el-scrollbar
tag="ul"
wrap-class="el-autocomplete-suggestion__wrap"
view-class="el-autocomplete-suggestion__list"
>
<li v-if="parent.loading"><i class="el-icon-loading"></i></li>
<template v-for="(item, index) in suggestions" v-else>
<li
v-if="!parent.customItem"
:class="{'highlighted': parent.highlightedIndex === index}"
@click="select(item)"
>
{{item.value}}
</li>
<component
v-else
:class="{'highlighted': parent.highlightedIndex === index}"
@click="select(item)"
:is="parent.customItem"
:item="item"
:index="index">
</component>
</template>
</el-scrollbar>
</div>
</transition>
</template>
<script>

View File

@ -98,6 +98,10 @@
},
handleChange(value) {
this.$emit('input', value);
if (!this.triggerOnFocus && !value) {
this.suggestions = [];
return;
}
this.getData(value);
},
handleFocus() {
@ -129,22 +133,22 @@
},
highlight(index) {
if (!this.suggestionVisible || this.loading) { return; }
if (index < 0) {
index = 0;
} else if (index >= this.suggestions.length) {
if (index < 0) index = 0;
if (index >= this.suggestions.length) {
index = this.suggestions.length - 1;
}
var elSuggestions = this.$refs.suggestions.$el;
const suggestion = this.$refs.suggestions.$el.querySelector('.el-autocomplete-suggestion__wrap');
const suggestionList = suggestion.querySelectorAll('.el-autocomplete-suggestion__list li');
var elSelect = elSuggestions.children[index];
var scrollTop = elSuggestions.scrollTop;
var offsetTop = elSelect.offsetTop;
let highlightItem = suggestionList[index];
let scrollTop = suggestion.scrollTop;
let offsetTop = highlightItem.offsetTop;
if (offsetTop + elSelect.scrollHeight > (scrollTop + elSuggestions.clientHeight)) {
elSuggestions.scrollTop += elSelect.scrollHeight;
if (offsetTop + highlightItem.scrollHeight > (scrollTop + suggestion.clientHeight)) {
suggestion.scrollTop += highlightItem.scrollHeight;
}
if (offsetTop < scrollTop) {
elSuggestions.scrollTop -= elSelect.scrollHeight;
suggestion.scrollTop -= highlightItem.scrollHeight;
}
this.highlightedIndex = index;

View File

@ -0,0 +1,18 @@
var cooking = require('cooking');
var path = require('path');
var config = require('../../build/config');
cooking.set({
entry: {
index: path.join(__dirname, 'index.js')
},
dist: path.join(__dirname, 'lib'),
template: false,
format: 'umd',
moduleName: 'ElCascader',
extends: ['vue2'],
alias: config.alias,
externals: { vue: config.vue }
});
module.exports = cooking.resolve();

View File

@ -0,0 +1,8 @@
import Cascader from './src/main';
/* istanbul ignore next */
Cascader.install = function(Vue) {
Vue.component(Cascader.name, Cascader);
};
export default Cascader;

View File

@ -0,0 +1,15 @@
{
"name": "element-cascader",
"version": "0.0.0",
"description": "A cascader component for Vue.js.",
"keywords": [
"element",
"vue",
"component"
],
"main": "./lib/index.js",
"repository": "https://github.com/ElemeFE/element/tree/master/packages/cascader",
"author": "elemefe",
"license": "MIT",
"dependencies": {}
}

View File

@ -0,0 +1,243 @@
<template>
<span
class="el-cascader"
:class="[
{
'is-opened': menuVisible,
'is-disabled': disabled
},
size ? 'el-cascader--' + size : ''
]"
@click="handleClick"
@mouseenter="inputHover = true"
@mouseleave="inputHover = false"
ref="reference"
v-clickoutside="handleClickoutside"
>
<el-input
ref="input"
:readonly="!filterable"
:placeholder="displayValue ? undefined : placeholder"
v-model="inputValue"
@change="handleInputChange"
:validate-event="false"
:size="size"
:disabled="disabled"
>
<template slot="icon">
<i
key="1"
v-if="inputHover && displayValue !== ''"
class="el-input__icon el-icon-circle-close el-cascader__clearIcon"
@click="clearValue"
></i>
<i
key="2"
v-else
class="el-input__icon el-icon-caret-bottom"
:class="{ 'is-reverse': menuVisible }"
></i>
</template>
</el-input>
<span class="el-cascader__label" v-show="inputValue === ''">{{displayValue}}</span>
</span>
</template>
<script>
import Vue from 'vue';
import ElCascaderMenu from './menu';
import ElInput from 'element-ui/packages/input';
import Popper from 'element-ui/src/utils/vue-popper';
import Clickoutside from 'element-ui/src/utils/clickoutside';
import emitter from 'element-ui/src/mixins/emitter';
import Locale from 'element-ui/src/mixins/locale';
const popperMixin = {
props: {
placement: {
type: String,
default: 'bottom-start'
},
appendToBody: Popper.props.appendToBody,
offset: Popper.props.offset,
boundariesPadding: Popper.props.boundariesPadding,
popperOptions: Popper.props.popperOptions
},
methods: Popper.methods,
data: Popper.data,
beforeDestroy: Popper.beforeDestroy
};
export default {
name: 'ElCascader',
directives: { Clickoutside },
mixins: [popperMixin, emitter, Locale],
components: {
ElInput
},
props: {
options: {
type: Array,
required: true
},
value: {
type: Array,
default() {
return [];
}
},
placeholder: String,
disabled: Boolean,
clearable: {
type: Boolean,
default: true
},
changeOnSelect: Boolean,
popperClass: String,
expandTrigger: {
type: String,
default: 'click'
},
filterable: Boolean,
size: String
},
data() {
return {
currentValue: this.value,
displayValue: this.value.join('/'),
menuVisible: false,
inputHover: false,
inputValue: '',
flatOptions: this.filterable && this.flattenOptions(this.options)
};
},
watch: {
menuVisible(value) {
value ? this.showMenu() : this.hideMenu();
},
value(value) {
this.currentValue = value;
},
currentValue(value) {
this.displayValue = value.join('/');
this.dispatch('ElFormItem', 'el.form.change', [value]);
},
options(value) {
this.menu.options = value;
}
},
methods: {
showMenu() {
if (!this.menu) {
this.menu = new Vue(ElCascaderMenu).$mount();
this.menu.options = this.options;
this.menu.expandTrigger = this.expandTrigger;
this.menu.changeOnSelect = this.changeOnSelect;
this.menu.popperClass = this.popperClass;
this.popperElm = this.menu.$el;
}
this.menu.value = this.currentValue.slice(0);
this.menu.visible = true;
this.menu.options = this.options;
this.menu.$on('pick', this.handlePick);
this.updatePopper();
this.$nextTick(_ => {
this.menu.inputWidth = this.$refs.input.$el.offsetWidth - 2;
});
},
hideMenu() {
this.inputValue = '';
this.menu.visible = false;
},
handlePick(value, close = true) {
this.currentValue = value;
this.$emit('input', value);
this.$emit('change', value);
if (close) {
this.menuVisible = false;
}
},
handleInputChange(value) {
if (!this.menuVisible) return;
const flatOptions = this.flatOptions;
if (!value) {
this.menu.options = this.options;
return;
}
let filteredFlatOptions = flatOptions.filter(optionsStack => {
return optionsStack.some(option => option.label.indexOf(value) > -1);
});
if (filteredFlatOptions.length > 0) {
filteredFlatOptions = filteredFlatOptions.map(optionStack => {
return {
__IS__FLAT__OPTIONS: true,
value: optionStack.map(item => item.value),
label: this.renderFilteredOptionLabel(value, optionStack)
};
});
} else {
filteredFlatOptions = [{
__IS__FLAT__OPTIONS: true,
label: this.t('el.cascader.noMatch'),
value: '',
disabled: true
}];
}
this.menu.options = filteredFlatOptions;
},
renderFilteredOptionLabel(inputValue, optionsStack) {
return optionsStack.map(({ label }, index) => {
const node = label.indexOf(inputValue) > -1 ? this.highlightKeyword(label, inputValue) : label;
return index === 0 ? node : [' / ', node];
});
},
highlightKeyword(label, keyword) {
const h = this._c;
return label.split(keyword)
.map((node, index) => index === 0 ? node : [
h('span', { class: { 'el-cascader-menu__item__keyword': true }}, [this._v(keyword)]),
node
]);
},
flattenOptions(options, ancestor = []) {
let flatOptions = [];
options.forEach((option) => {
const optionsStack = ancestor.concat(option);
if (!option.children) {
flatOptions.push(optionsStack);
} else {
flatOptions = flatOptions.concat(this.flattenOptions(option.children, optionsStack));
}
});
return flatOptions;
},
clearValue(ev) {
ev.stopPropagation();
this.handlePick([], true);
},
handleClickoutside() {
this.menuVisible = false;
},
handleClick() {
if (this.disabled) return;
if (this.filterable) {
this.menuVisible = true;
return;
}
this.menuVisible = !this.menuVisible;
}
}
};
</script>

View File

@ -0,0 +1,148 @@
<script>
export default {
name: 'ElCascaderMenu',
data() {
return {
inputWidth: 0,
options: [],
visible: false,
activeValue: [],
value: [],
expandTrigger: 'click',
changeOnSelect: false,
popperClass: ''
};
},
watch: {
visible(value) {
if (value) {
this.activeValue = this.value;
}
},
value: {
immediate: true,
handler(value) {
this.activeValue = value;
}
}
},
computed: {
activeOptions: {
cache: false,
get() {
const activeValue = this.activeValue;
const loadActiveOptions = (options, activeOptions = []) => {
const level = activeOptions.length;
activeOptions[level] = options;
let active = activeValue[level];
if (active) {
options = options.filter(option => option.value === active)[0];
if (options && options.children) {
loadActiveOptions(options.children, activeOptions);
}
}
return activeOptions;
};
return loadActiveOptions(this.options);
}
}
},
methods: {
select(item, menuIndex) {
if (item.__IS__FLAT__OPTIONS) {
this.activeValue = item.value;
} else {
this.activeValue.splice(menuIndex, 1, item.value);
}
this.$emit('pick', this.activeValue);
},
activeItem(item, menuIndex) {
const len = this.activeOptions.length;
this.activeValue.splice(menuIndex, len, item.value);
this.activeOptions.splice(menuIndex + 1, len, item.children);
if (this.changeOnSelect) this.$emit('pick', this.activeValue, false);
}
},
render(h) {
const {
activeValue,
activeOptions,
visible,
expandTrigger,
popperClass
} = this;
const menus = this._l(activeOptions, (menu, menuIndex) => {
let isFlat = false;
const items = this._l(menu, item => {
const events = {
on: {}
};
if (item.__IS__FLAT__OPTIONS) isFlat = true;
if (!item.disabled) {
if (item.children) {
let triggerEvent = {
click: 'click',
hover: 'mouseenter'
}[expandTrigger];
events.on[triggerEvent] = () => { this.activeItem(item, menuIndex); };
} else {
events.on.click = () => { this.select(item, menuIndex); };
}
}
return (
<li
class={{
'el-cascader-menu__item': true,
'el-cascader-menu__item--extensible': item.children,
'is-active': item.value === activeValue[menuIndex],
'is-disabled': item.disabled
}}
{...events}
>
{item.label}
</li>
);
});
let menuStyle = {};
if (isFlat) {
menuStyle.width = this.inputWidth + 'px';
}
return (
<ul
class={{
'el-cascader-menu': true,
'el-cascader-menu--flexible': isFlat
}}
style={menuStyle}>
{items}
</ul>
);
});
return (
<transition name="el-zoom-in-top">
<div
v-show={visible}
class={[
'el-cascader-menus',
popperClass
]}
>
{menus}
</div>
</transition>
);
}
};
</script>

View File

@ -329,7 +329,7 @@ export default {
created() {
RANGE_SEPARATOR = this.rangeSeparator;
// vue-popper
this.options = {
this.popperOptions = {
boundariesPadding: 0,
gpuAcceleration: false
};

View File

@ -41,6 +41,11 @@
type: Boolean,
default: true
},
modalAppendToBody: {
type: Boolean,
default: true
},
lockScroll: {
type: Boolean,

View File

@ -1,5 +1,16 @@
<template>
<li class="el-menu-item-group">
<div class="el-menu-item-group__title" :style="{paddingLeft: levelPadding + 'px'}">
<template v-if="!$slots.title">{{title}}</template>
<slot v-else name="title"></slot>
</div>
<ul>
<slot></slot>
</ul>
</li>
</template>
<script>
module.exports = {
export default {
name: 'ElMenuItemGroup',
componentName: 'ElMenuItemGroup',
@ -31,14 +42,3 @@
};
</script>
<template>
<li class="el-menu-item-group">
<div class="el-menu-item-group__title" :style="{paddingLeft: levelPadding + 'px'}">
<template v-if="!$slots.title">{{title}}</template>
<slot v-else name="title"></slot>
</div>
<ul>
<slot></slot>
</ul>
</li>
</template>

View File

@ -13,7 +13,7 @@
import Menu from './menu-mixin';
import Emitter from 'element-ui/src/mixins/emitter';
module.exports = {
export default {
name: 'ElMenuItem',
componentName: 'ElMenuItem',

View File

@ -1,4 +1,4 @@
module.exports = {
export default {
computed: {
indexPath() {
var path = [this.index];

View File

@ -30,7 +30,7 @@
import Emitter from 'element-ui/src/mixins/emitter';
import CollapseTransition from 'element-ui/src/transitions/collapse-transition';
module.exports = {
export default {
name: 'ElSubmenu',
componentName: 'ElSubmenu',

View File

@ -14,7 +14,7 @@
mixins: [Emitter],
props: {
value: [String, Number],
value: {},
size: String,
fill: String,
textColor: String,

View File

@ -26,7 +26,7 @@
default: 0
},
options: {
popperOptions: {
default() {
return {
forceAbsolute: true,

View File

@ -69,7 +69,7 @@ export default {
};
},
created() {
beforeCreate() {
this.$parent.steps.push(this);
},
@ -118,9 +118,11 @@ export default {
const isCenter = parent.center;
const len = parent.steps.length;
const isLast = this.isLast = parent.steps[parent.steps.length - 1] === this;
const space = parent.space
const space = typeof parent.space === 'number'
? parent.space + 'px'
: 100 / (isCenter ? len - 1 : len) + '%';
: parent.space
? parent.space
: 100 / (isCenter ? len - 1 : len) + '%';
if (parent.direction === 'horizontal') {
this.style = { width: space };

View File

@ -11,7 +11,7 @@ export default {
name: 'ElSteps',
props: {
space: Number,
space: [Number, String],
active: Number,
direction: {
type: String,
@ -39,13 +39,13 @@ export default {
watch: {
active(newVal, oldVal) {
this.$emit('change', newVal, oldVal);
}
},
},
mounted() {
this.steps.forEach((child, index) => {
child.index = index;
});
steps(steps) {
steps.forEach((child, index) => {
child.index = index;
});
}
}
};
</script>

View File

@ -182,8 +182,7 @@ export default {
created() {
this.customRender = this.$options.render;
this.$options.render = h => h('div', this.$slots.default);
let columnId = this.columnId = this.columnKey || ((this.$parent.tableId || (this.$parent.columnId + '_')) + 'column_' + columnIdSeed++);
this.columnId = (this.$parent.tableId || (this.$parent.columnId + '_')) + 'column_' + columnIdSeed++;
let parent = this.$parent;
let owner = this.owner;
@ -210,7 +209,8 @@ export default {
let isColumnGroup = false;
let column = getDefaultColumn(type, {
id: columnId,
id: this.columnId,
columnKey: this.columnKey,
label: this.label,
className: this.className,
property: this.prop || this.property,

View File

@ -87,14 +87,14 @@ TableStore.prototype.mutations = {
states._data = data;
states.data = sortData((data || []), states);
states.data.forEach((item) => {
if (!item.$extra) {
Object.defineProperty(item, '$extra', {
value: {},
enumerable: false
});
}
});
// states.data.forEach((item) => {
// if (!item.$extra) {
// Object.defineProperty(item, '$extra', {
// value: {},
// enumerable: false
// });
// }
// });
this.updateCurrentRow();
@ -152,15 +152,17 @@ TableStore.prototype.mutations = {
}
const prop = column.property;
const filters = [];
if (prop) {
states.filters[column.id] = values;
filters[column.columnKey || column.id] = values;
}
let data = states._data;
const filters = states.filters;
Object.keys(filters).forEach((columnId) => {
const values = filters[columnId];
Object.keys(states.filters).forEach((columnId) => {
const values = states.filters[columnId];
if (!values || values.length === 0) return;
const column = getColumnById(this.states, columnId);
if (column && column.filterMethod) {

View File

@ -3,9 +3,12 @@
</template>
<script>
export default {
name: 'TabBar',
props: {
tabs: Array
},
computed: {
barStyle: {
cache: false,

View File

@ -0,0 +1,174 @@
<script>
import TabBar from './tab-bar';
function noop() {}
export default {
name: 'TabNav',
components: {
TabBar
},
props: {
panes: Array,
currentName: String,
editable: Boolean,
onTabClick: {
type: Function,
default: noop
},
onTabRemove: {
type: Function,
default: noop
},
type: String
},
data() {
return {
scrollable: false,
navStyle: {
transform: ''
}
};
},
methods: {
scrollPrev() {
const containerWidth = this.$refs.navScroll.offsetWidth;
const currentOffset = this.getCurrentScrollOffset();
if (!currentOffset) return;
let newOffset = currentOffset > containerWidth
? currentOffset - containerWidth
: 0;
this.setOffset(newOffset);
},
scrollNext() {
const navWidth = this.$refs.nav.offsetWidth;
const containerWidth = this.$refs.navScroll.offsetWidth;
const currentOffset = this.getCurrentScrollOffset();
if (navWidth - currentOffset <= containerWidth) return;
let newOffset = navWidth - currentOffset > containerWidth * 2
? currentOffset + containerWidth
: (navWidth - containerWidth);
this.setOffset(newOffset);
},
scrollToActiveTab() {
if (!this.scrollable) return;
const nav = this.$refs.nav;
const activeTab = this.$el.querySelector('.is-active');
const navScroll = this.$refs.navScroll;
const activeTabBounding = activeTab.getBoundingClientRect();
const navScrollBounding = navScroll.getBoundingClientRect();
const navBounding = nav.getBoundingClientRect();
const currentOffset = this.getCurrentScrollOffset();
let newOffset = currentOffset;
if (activeTabBounding.left < navScrollBounding.left) {
newOffset = currentOffset - (navScrollBounding.left - activeTabBounding.left);
}
if (activeTabBounding.right > navScrollBounding.right) {
newOffset = currentOffset + activeTabBounding.right - navScrollBounding.right;
}
if (navBounding.right < navScrollBounding.right) {
newOffset = nav.offsetWidth - navScrollBounding.width;
}
this.setOffset(Math.max(newOffset, 0));
},
getCurrentScrollOffset() {
const { navStyle } = this;
return navStyle.transform
? Number(navStyle.transform.match(/translateX\(-(\d+(\.\d+)*)px\)/)[1])
: 0;
},
setOffset(value) {
this.navStyle.transform = `translateX(-${value}px)`;
}
},
updated() {
const navWidth = this.$refs.nav.offsetWidth;
const containerWidth = this.$refs.navScroll.offsetWidth;
const currentOffset = this.getCurrentScrollOffset();
if (containerWidth < navWidth) {
const currentOffset = this.getCurrentScrollOffset();
this.scrollable = this.scrollable || {};
this.scrollable.prev = currentOffset;
this.scrollable.next = currentOffset + containerWidth < navWidth;
if (navWidth - currentOffset < containerWidth) {
this.setOffset(navWidth - containerWidth);
}
} else if (currentOffset > 0) {
this.setOffset(0);
}
},
render(h) {
const {
type,
panes,
editable,
onTabClick,
onTabRemove,
navStyle,
scrollable,
scrollNext,
scrollPrev
} = this;
const scrollBtn = scrollable
? [
<span class={['el-tabs__nav-prev', scrollable.prev ? '' : 'is-disabled']} on-click={scrollPrev}><i class="el-icon-arrow-left"></i></span>,
<span class={['el-tabs__nav-next', scrollable.next ? '' : 'is-disabled']} on-click={scrollNext}><i class="el-icon-arrow-right"></i></span>
] : null;
const tabs = this._l(panes, (pane, index) => {
let tabName = pane.name || pane.index || index;
const closable = pane.isClosable || editable;
pane.index = `${index}`;
const btnClose = closable
? <span class="el-icon-close" on-click={(ev) => { onTabRemove(pane, ev); }}></span>
: null;
const tabLabelContent = pane.$slots.label || pane.label;
return (
<div
class={{
'el-tabs__item': true,
'is-active': pane.active,
'is-disabled': pane.disabled,
'is-closable': closable
}}
ref="tabs"
refInFor
on-click={(ev) => { onTabClick(pane, tabName, ev); }}
>
{tabLabelContent}
{btnClose}
</div>
);
});
return (
<div class={['el-tabs__nav-wrap', scrollable ? 'is-scrollable' : '']}>
{scrollBtn}
<div class={['el-tabs__nav-scroll']} ref="navScroll">
<div class="el-tabs__nav" ref="nav" style={navStyle}>
{!type ? <tab-bar tabs={panes}></tab-bar> : null}
{tabs}
</div>
</div>
</div>
);
}
};
</script>

View File

@ -4,7 +4,7 @@
</div>
</template>
<script>
module.exports = {
export default {
name: 'ElTabPane',
componentName: 'ElTabPane',

View File

@ -1,25 +1,24 @@
<script>
import TabBar from './tab-bar';
module.exports = {
import TabNav from './tab-nav';
export default {
name: 'ElTabs',
components: {
TabBar
TabNav
},
props: {
type: String,
activeName: String,
closable: {
type: Boolean,
default: false
},
value: {}
closable: Boolean,
addable: Boolean,
value: {},
editable: Boolean
},
data() {
return {
children: null,
currentName: this.value || this.activeName,
panes: []
};
@ -31,57 +30,31 @@
},
value(value) {
this.setCurrentName(value);
}
},
computed: {
currentTab() {
let result;
this.panes.forEach(tab => {
if (this.currentName === (tab.name || tab.index)) {
result = tab;
}
});
return result;
},
currentName(value) {
if (this.$refs.nav) {
this.$nextTick(_ => {
this.$refs.nav.scrollToActiveTab();
});
}
}
},
methods: {
handleTabRemove(pane, event) {
event.stopPropagation();
const panes = this.panes;
const currentTab = this.currentTab;
let index = panes.indexOf(pane);
if (index === -1) return;
panes.splice(index, 1);
pane.$destroy();
this.$emit('tab-remove', pane);
this.$nextTick(_ => {
if (pane.active) {
const panes = this.panes;
let nextChild = panes[index];
let prevChild = panes[index - 1];
let nextActiveTab = nextChild || prevChild || null;
if (nextActiveTab) {
this.setCurrentName(nextActiveTab.name || nextActiveTab.index);
}
return;
} else {
this.setCurrentName(currentTab.name || currentTab.index);
}
});
},
handleTabClick(tab, tabName, event) {
if (tab.disabled) return;
this.setCurrentName(tabName);
this.$emit('tab-click', tab, event);
},
handleTabRemove(pane, ev) {
ev.stopPropagation();
this.$emit('edit', pane.name, 'remove');
this.$emit('tab-remove', pane.name);
},
handleTabAdd() {
this.$emit('edit', null, 'add');
this.$emit('tab-add');
},
setCurrentName(value) {
this.currentName = value;
this.$emit('input', value);
@ -100,42 +73,37 @@
render(h) {
let {
type,
handleTabRemove,
handleTabClick,
handleTabRemove,
handleTabAdd,
currentName,
panes
panes,
editable,
addable
} = this;
const tabs = this._l(panes, (pane, index) => {
let tabName = pane.name || pane.index || index;
if (currentName === undefined && index === 0) {
this.setCurrentName(tabName);
}
const newButton = editable || addable
? (
<span
class="el-tabs__new-tab"
on-click={ handleTabAdd }
>
<i class="el-icon-plus"></i>
</span>
)
: null;
pane.index = index;
const btnClose = pane.isClosable
? <span class="el-icon-close" on-click={(ev) => { handleTabRemove(pane, ev); }}></span>
: null;
const tabLabelContent = pane.$slots.label || pane.label;
return (
<div
class={{
'el-tabs__item': true,
'is-active': pane.active,
'is-disabled': pane.disabled,
'is-closable': pane.isClosable
}}
ref="tabs"
refInFor
on-click={(ev) => { handleTabClick(pane, tabName, ev); }}
>
{tabLabelContent}
{btnClose}
</div>
);
});
const navData = {
props: {
currentName,
onTabClick: handleTabClick,
onTabRemove: handleTabRemove,
editable,
type,
panes
},
ref: 'nav'
};
return (
<div class={{
@ -144,14 +112,19 @@
'el-tabs--border-card': type === 'border-card'
}}>
<div class="el-tabs__header">
{!type ? <tab-bar tabs={panes}></tab-bar> : null}
{tabs}
{newButton}
<tab-nav { ...navData }></tab-nav>
</div>
<div class="el-tabs__content">
{this.$slots.default}
</div>
</div>
);
},
created() {
if (!this.currentName) {
this.setCurrentName('0');
}
}
};
</script>

View File

@ -6,71 +6,73 @@
@b autocomplete {
position: relative;
display: inline-block;
}
@b autocomplete-suggestion {
margin: 5px 0 0;
box-shadow: 0 0 6px 0 rgba(0,0,0,0.04), 0 2px 4px 0 rgba(0,0,0,0.12);
@e suggestions {
position: absolute;
left: 0;
top: 110%;
margin: 5px 0 0;
@e wrap {
max-height: 280px;
overflow: auto;
background-color: var(--color-white);
border: 1px solid var(--color-base-gray);
width: 100%;
padding: 6px 0;
z-index: 10;
border-radius: 2px;
max-height: 280px;
box-sizing: border-box;
overflow: auto;
box-shadow: 0 0 6px 0 rgba(0,0,0,0.04), 0 2px 4px 0 rgba(0,0,0,0.12);
}
& li {
list-style: none;
line-height: 36px;
padding: 0 10px;
margin: 0;
cursor: pointer;
color: var(--color-extra-light-black);
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@e list {
margin: 0;
padding: 0;
}
& li {
list-style: none;
line-height: 36px;
padding: 0 10px;
margin: 0;
cursor: pointer;
color: var(--color-extra-light-black);
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:hover {
background-color: var(--select-option-hover-background);
}
&.highlighted {
background-color: var(--color-primary);
color: var(--color-white);
}
&:active {
background-color: darken(var(--color-primary), 0.2);
}
&.divider {
margin-top: 6px;
border-top: 1px solid var(--color-base-gray);
}
&.divider:last-child {
margin-bottom: -6px;
}
}
@when loading {
li {
text-align: center;
height: 100px;
line-height: 100px;
font-size: 20px;
color: #999;
@utils-vertical-center;
&:hover {
background-color: var(--select-option-hover-background);
}
&.highlighted {
background-color: var(--color-primary);
color: var(--color-white);
}
&:active {
background-color: darken(var(--color-primary), 0.2);
}
&.divider {
margin-top: 6px;
border-top: 1px solid var(--color-base-gray);
}
&.divider:last-child {
margin-bottom: -6px;
background-color: var(--color-white);
}
}
@when loading {
li {
text-align: center;
height: 100px;
line-height: 100px;
font-size: 20px;
color: #999;
@utils-vertical-center;
&:hover {
background-color: var(--color-white);
}
}
& .el-icon-loading {
vertical-align: middle;
}
& .el-icon-loading {
vertical-align: middle;
}
}
}

View File

@ -1,44 +1,163 @@
@charset "UTF-8";
@import "./input.css";
@import "./common/var.css";
/*@import "./core/dropdown.css";*/
@component-namespace element {
@component-namespace el {
@b cascader {
display: inline-block;
position: relative;
background-color: #fff;
@e dropdown {
background-color: var(--cascader-menu-fill);
border: var(--cascader-menu-border);
border-radius: var(--cascader-menu-radius);
box-shadow: var(--cascader-menu-submenu-shadow);
margin-top: 5px;
max-height: var(--cascader-height);
.el-input,
.el-input__inner {
cursor: pointer;
background-color: transparent;
z-index: 1;
}
.el-input__icon {
transition: none;
}
.el-icon-caret-bottom {
transition: transform .3s;
@when reverse {
transform: rotateZ(180deg);
}
}
@e label {
position: absolute;
left: 0;
top: 0;
height: 100%;
line-height: 34px;
padding: 0 15px 0 10px;
color: var(--input-color);
width: 100%;
white-space: nowrap;
z-index: 10;
}
@e wrap {
text-overflow: ellipsis;
overflow: hidden;
box-sizing: border-box;
cursor: pointer;
}
@e menu {
border: 0;
box-shadow: none;
display: inline-block;
margin: 0;
position: relative;
vertical-align: top;
@m large {
font-size: var(--input-large-font-size);
&::before {
border-left: var(--cascader-menu-border);
content: " ";
height: var(--cascader-height);
left: 0;
position: absolute;
.el-cascader__label {
line-height: calc(var(--input-large-height) - 2);
}
}
@m small {
font-size: var(--input-small-font-size);
.el-cascader__label {
line-height: calc(var(--input-small-height) - 2);
}
}
}
@b cascader-menus {
white-space: nowrap;
background: #fff;
position: absolute;
margin: 5px 0;
z-index: 1001;
border: var(--select-dropdown-border);
border-radius: var(--border-radius-small);
overflow: hidden;
box-shadow: var(--select-dropdown-shadow);
}
@b cascader-menu {
display: inline-block;
vertical-align: top;
height: 180px;
overflow: auto;
border-right: var(--select-dropdown-border);
background-color: var(--select-dropdown-background);
box-sizing: border-box;
margin: 0;
padding: 0;
min-width: 110px;
&:last-child {
border-right: 0;
}
@e item {
font-size: var(--select-font-size);
padding: 8px 30px 8px 10px;
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--select-option-color);
height: var(--select-option-height);
line-height: 1.5;
box-sizing: border-box;
cursor: pointer;
@e keyword {
color: var(--color-danger);
}
@m extensible {
&:after {
font-family: 'element-icons';
content: "\e602";
font-size: 12px;
transform: scale(0.8);
color: rgb(191, 203, 217);
position: absolute;
right: 10px;
margin-top: 1px;
}
}
@when disabled {
color: var(--select-option-disabled-color);
background-color: var(--select-option-disabled-background);
cursor: not-allowed;
&:hover {
background-color: var(--color-white);
}
}
@when active {
color: var(--color-white);
background-color: var(--select-option-selected);
&.hover {
background-color: var(--select-option-selected-hover);
}
}
&:hover {
background-color: var(--select-option-hover-background);
}
&.selected {
color: var(--color-white);
background-color: var(--select-option-selected);
&.hover {
background-color: var(--select-option-selected-hover);
}
}
}
@m flexible {
height: auto;
max-height: 180px;
overflow: auto;
.el-cascader-menu__item {
overflow: visible;
}
}
}

View File

@ -39,8 +39,10 @@
}
@when active {
.el-collapse-item__header__arrow {
transform: rotate(90deg);
> .el-collapse-item__header {
.el-collapse-item__header__arrow {
transform: rotate(90deg);
}
}
}

View File

@ -161,6 +161,7 @@
--select-option-color: var(--link-color);
--select-option-disabled-color: var(--color-extra-light-silver);
--select-option-disabled-background: var(--color-white);
--select-option-height: 36px;
--select-option-hover-background: var(--color-light-gray);
--select-option-selected: var(--color-primary);
@ -458,8 +459,8 @@
--tag-gray-border: var(--color-light-gray);
--tag-gray-color: var(--link-color);
--tag-primary-fill: rgba(32,159,255,0.10);
--tag-primary-border: rgba(32,159,255,0.20);
--tag-primary-fill: rgba(var(--color-primary),0.10);
--tag-primary-border: rgba(var(--color-primary),0.20);
--tag-primary-color: var(--color-primary);
--tag-success-fill: rgba(18,206,102,0.10);

View File

@ -44,3 +44,4 @@
@import "./carousel.css";
@import "./carousel-item.css";
@import "./collapse.css";
@import "./cascader.css";

View File

@ -8,7 +8,10 @@
overflow: hidden;
width: 180px;
position: relative;
& .el-input {
display: block;
}
& .el-input__inner {
appearance: none;
padding-right: calc(var(--input-height) * 2 + 10);

View File

@ -127,7 +127,6 @@
}
@b radio-button {
position: relative;
overflow: hidden;
display: inline-block;
@e inner {
@ -138,6 +137,7 @@
cursor: pointer;
background: var(--button-default-fill);
border: var(--border-base);
border-left: 0;
color: var(--button-default-color);
-webkit-appearance: none;
text-align: center;
@ -157,7 +157,7 @@
& [class*="el-icon-"] {
line-height: 0.9;
& + span {
margin-left: 5px;
}
@ -176,6 +176,7 @@
color: var(--radio-button-checked-color);
background-color: var(--radio-button-checked-fill);
border-color: var(--radio-button-checked-border-color);
box-shadow: -1px 0 0 0 var(--radio-button-checked-border-color);
}
}
@ -190,12 +191,11 @@
}
}
&:not(:last-child) {
margin-right: -1px;
}
&:first-child {
.el-radio-button__inner {
border-left: var(--border-base);
border-radius: var(--border-radius-base) 0 0 var(--border-radius-base);
box-shadow: none !important;
}
}
&:last-child {

View File

@ -80,23 +80,11 @@
background-color: transparent;
text-align: center;
user-select: none;
@utils-vertical-center;
.el-tooltip {
line-height: 1;
height: 100%;
display: block;
}
.el-tooltip::after {
content: '';
width: 0;
height: 100%;
vertical-align: middle;
display: inline-block;
vertical-align: middle;
}
.el-tooltip__rel {
vertical-align: middle;
}
&:hover,

View File

@ -38,16 +38,8 @@
z-index: 1;
}
.el-tooltip {
display: block;
}
.el-tooltip__rel {
display: block;
.cell {
white-space: nowrap;
}
.el-tooltip.cell {
white-space: nowrap;
}
@e empty-block {

View File

@ -8,7 +8,6 @@
padding: 0;
position: relative;
margin: 0 0 15px;
@utils-clearfix;
}
@e active-bar {
position: absolute;
@ -20,16 +19,68 @@
transition: transform .3s cubic-bezier(.645,.045,.355,1);
list-style: none;
}
@e new-tab {
float: right;
border: 1px solid #d3dce6;
height: 18px;
width: @height;
line-height: @height;
margin: 12px 0 9px 10px;
border-radius: 3px;
text-align: center;
font-size: 12px;
color: #d3dce6;
cursor: pointer;
transition: all .15s;
.el-icon-plus {
transform: scale(0.8, 0.8);
}
&:hover {
color: var(--color-primary);
}
}
@e nav-wrap {
overflow: hidden;
margin-bottom: -1px;
position: relative;
@when scrollable {
padding: 0 15px;
}
}
@e nav-scroll {
overflow: hidden;
}
@e nav-next, nav-prev {
position: absolute;
cursor: pointer;
line-height: 44px;
font-size: 12px;
color: var(--color-base-silver);
}
@e nav-next {
right: 0;
}
@e nav-prev {
left: 0;
}
@e nav {
white-space: nowrap;
position: relative;
float: left;
transition: transform .3s;
}
@e item {
padding: 0 16px;
height: 42px;
box-sizing: border-box;
line-height: @height;
float: left;
display: inline-block;
list-style: none;
font-size: 14px;
color: var(--color-base-silver);
margin-bottom: -1px;
position: relative;
& .el-icon-close {
@ -67,10 +118,10 @@
position: relative;
}
@m card {
&>.el-tabs__header>.el-tabs__active-bar {
.el-tabs__nav .el-tabs__active-bar {
display: none;
}
&>.el-tabs__header>.el-tabs__item .el-icon-close {
.el-tabs__nav .el-tabs__item .el-icon-close {
position: relative;
font-size: 12px;
width: 0;
@ -82,7 +133,7 @@
right: -2px;
transform-origin: 100% 50%;
}
&>.el-tabs__header>.el-tabs__item {
.el-tabs__nav .el-tabs__item {
border: 1px solid transparent;
transition: all .3s cubic-bezier(.645,.045,.355,1);

View File

@ -3,13 +3,6 @@
@component-namespace el {
@b tooltip {
display: inline-block;
@e rel {
display: inline-block;
position: relative;
}
@e popper {
position: absolute;
border-radius: 4px;

View File

@ -0,0 +1,113 @@
import Popper from 'element-ui/src/utils/vue-popper';
import debounce from 'throttle-debounce/debounce';
import Vue from 'vue';
export default {
name: 'ElTooltip',
mixins: [Popper],
props: {
openDelay: {
type: Number,
default: 0
},
disabled: Boolean,
manual: Boolean,
effect: {
type: String,
default: 'dark'
},
popperClass: String,
content: String,
visibleArrow: {
default: true
},
transition: {
type: String,
default: 'fade-in-linear'
},
popperOptions: {
default() {
return {
boundariesPadding: 10,
gpuAcceleration: false
};
}
}
},
beforeCreate() {
this.popperVM = new Vue({
data: { node: '' },
render(h) {
return this.node;
}
}).$mount();
this.debounceClose = debounce(200, () => this.handleClosePopper());
},
render(h) {
this.popperVM.node = (
<transition
name={ this.transition }
onAfterLeave={ this.doDestroy }>
<div
onMouseleave={ () => { this.debounceClose(); this.togglePreventClose(); } }
onMouseenter= { this.togglePreventClose }
ref="popper"
v-show={!this.disabled && this.showPopper}
class={
['el-tooltip__popper', 'is-' + this.effect, this.popperClass]
}>
{ this.$slots.content || this.content }
</div>
</transition>);
if (!this.$slots.default) return this.$slots.default;
const vnode = this.$slots.default[0];
const data = vnode.data = vnode.data || {};
const on = vnode.data.on = vnode.data.on || {};
on.mouseenter = this.addEventHandle(on.mouseenter, this.handleShowPopper);
on.mouseleave = this.addEventHandle(on.mouseleave, this.debounceClose);
data.staticClass = this.concatClass(data.staticClass, 'el-tooltip');
return vnode;
},
mounted() {
this.referenceElm = this.$el;
},
methods: {
addEventHandle(old, fn) {
return old ? Array.isArray(old) ? old.concat(fn) : [old, fn] : fn;
},
concatClass(a, b) {
if (a && a.indexOf(b) > -1) return a;
return a ? b ? (a + ' ' + b) : a : (b || '');
},
handleShowPopper() {
if (this.manual) return;
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.showPopper = true;
}, this.openDelay);
},
handleClosePopper() {
if (this.preventClose || this.manual) return;
clearTimeout(this.timeout);
this.showPopper = false;
},
togglePreventClose() {
this.preventClose = !this.preventClose;
}
}
};

View File

@ -1,75 +0,0 @@
<template>
<div
class="el-tooltip"
@mouseenter="handleShowPopper"
@mouseleave="handleClosePopper">
<div class="el-tooltip__rel" ref="reference">
<slot></slot>
</div>
<transition :name="transition" @after-leave="doDestroy">
<div
class="el-tooltip__popper"
:class="['is-' + effect, popperClass]"
ref="popper"
v-show="!disabled && showPopper">
<slot name="content"><div v-text="content"></div></slot>
</div>
</transition>
</div>
</template>
<script>
import Popper from 'element-ui/src/utils/vue-popper';
export default {
name: 'ElTooltip',
mixins: [Popper],
props: {
openDelay: {
type: Number,
default: 0
},
disabled: Boolean,
manual: Boolean,
effect: {
type: String,
default: 'dark'
},
popperClass: String,
content: String,
visibleArrow: {
default: true
},
transition: {
type: String,
default: 'fade-in-linear'
},
options: {
default() {
return {
boundariesPadding: 10,
gpuAcceleration: false
};
}
}
},
methods: {
handleShowPopper() {
if (this.manual) return;
this.timeout = setTimeout(() => {
this.showPopper = true;
}, this.openDelay);
},
handleClosePopper() {
if (this.manual) return;
clearTimeout(this.timeout);
this.showPopper = false;
}
}
};
</script>

View File

@ -60,6 +60,7 @@ import Scrollbar from '../packages/scrollbar';
import CarouselItem from '../packages/carousel-item';
import Collapse from '../packages/collapse';
import CollapseItem from '../packages/collapse-item';
import Cascader from '../packages/cascader';
import locale from 'element-ui/src/locale';
const components = [
@ -118,7 +119,8 @@ const components = [
Scrollbar,
CarouselItem,
Collapse,
CollapseItem
CollapseItem,
Cascader
];
const install = function(Vue, opts = {}) {
@ -211,5 +213,6 @@ module.exports = {
Scrollbar,
CarouselItem,
Collapse,
CollapseItem
CollapseItem,
Cascader
};

View File

@ -56,6 +56,10 @@ export default {
noData: 'Няма данни',
placeholder: 'Избери'
},
cascader: {
noMatch: 'Няма намерени',
placeholder: 'Избери'
},
pagination: {
goto: 'Иди на',
pagesize: '/страница',

View File

@ -56,6 +56,10 @@ export default {
noData: 'Ingen data',
placeholder: 'Vælg'
},
cascader: {
noMatch: 'Ingen matchende data',
placeholder: 'Vælg'
},
pagination: {
goto: 'Gå til',
pagesize: '/side',

View File

@ -58,6 +58,10 @@ export default {
noData: 'Keine Datei',
placeholder: 'Datei wählen'
},
cascader: {
noMatch: 'Nichts gefunden.',
placeholder: 'Datei wählen'
},
pagination: {
goto: 'Gehe zu',
pagesize: 'pro Seite',

View File

@ -56,6 +56,10 @@ export default {
noData: 'No data',
placeholder: 'Select'
},
cascader: {
noMatch: 'No matching data',
placeholder: 'Select'
},
pagination: {
goto: 'Go to',
pagesize: '/page',

View File

@ -56,6 +56,10 @@ export default {
noData: 'Sin datos',
placeholder: 'Seleccionar'
},
cascader: {
noMatch: 'No hay datos que coincidan',
placeholder: 'Seleccionar'
},
pagination: {
goto: 'Ir a',
pagesize: '/pagina',

View File

@ -56,6 +56,10 @@ export default {
noData: 'اطلاعاتی وجود ندارد',
placeholder: 'انتخاب کنید'
},
cascader: {
noMatch: 'هیچ داده‌ای پیدا نشد',
placeholder: 'انتخاب کنید'
},
pagination: {
goto: 'برو به',
pagesize: '/صفحه',

View File

@ -56,6 +56,10 @@ export default {
noData: 'Aucun résultat',
placeholder: 'Choisir'
},
cascader: {
noMatch: 'Aucune correspondance',
placeholder: 'Choisir'
},
pagination: {
goto: 'Aller à',
pagesize: '/page',

View File

@ -56,6 +56,10 @@ export default {
noData: 'Tidak ada data',
placeholder: 'Pilih'
},
cascader: {
noMatch: 'Tidak ada data yang cocok',
placeholder: 'Pilih'
},
pagination: {
goto: 'Pergi ke',
pagesize: '/page',

View File

@ -56,6 +56,10 @@ export default {
noData: 'Nessun risultato',
placeholder: 'Seleziona'
},
cascader: {
noMatch: 'Nessuna corrispondenza',
placeholder: 'Seleziona'
},
pagination: {
goto: 'Vai a',
pagesize: '/page',

View File

@ -56,6 +56,10 @@ export default {
noData: 'データなし',
placeholder: '選択してください'
},
cascader: {
noMatch: 'データなし',
placeholder: '選択してください'
},
pagination: {
goto: '',
pagesize: '件/ページ',

View File

@ -56,6 +56,10 @@ export default {
noData: '데이터 없음',
placeholder: '선택'
},
cascader: {
noMatch: '맞는 데이터가 없습니다',
placeholder: '선택'
},
pagination: {
goto: '이동',
pagesize: '/page',

View File

@ -56,6 +56,10 @@ export default {
noData: 'Ingen data',
placeholder: 'Velg'
},
cascader: {
noMatch: 'Ingen samsvarende data',
placeholder: 'Velg'
},
pagination: {
goto: 'Gå til',
pagesize: '/side',

View File

@ -56,6 +56,10 @@ export default {
noData: 'Geen data',
placeholder: 'Selecteer'
},
cascader: {
noMatch: 'Geen overeenkomende resultaten',
placeholder: 'Selecteer'
},
pagination: {
goto: 'Ga naar',
pagesize: '/page',

View File

@ -56,6 +56,10 @@ export default {
noData: 'Brak danych',
placeholder: 'Wybierz'
},
cascader: {
noMatch: 'Brak dopasowań',
placeholder: 'Wybierz'
},
pagination: {
goto: 'Idź do',
pagesize: '/strona',

View File

@ -56,6 +56,10 @@ export default {
noData: 'Sem dados',
placeholder: 'Selecione'
},
cascader: {
noMatch: 'Sem resultados',
placeholder: 'Selecione'
},
pagination: {
goto: 'Ir para',
pagesize: '/pagina',

View File

@ -56,6 +56,10 @@ export default {
noData: 'Sem dados',
placeholder: 'Selecione'
},
cascader: {
noMatch: 'Sem correspondência',
placeholder: 'Selecione'
},
pagination: {
goto: 'Ir para',
pagesize: '/pagina',

View File

@ -56,6 +56,10 @@ export default {
noData: 'Нет данных',
placeholder: 'Выбрать'
},
cascader: {
noMatch: 'Совпадений не найдено',
placeholder: 'Выбрать'
},
pagination: {
goto: 'Перейти',
pagesize: '/page',

View File

@ -56,6 +56,10 @@ export default {
noData: 'ไม่พบข้อมูล',
placeholder: 'เลือก'
},
cascader: {
noMatch: 'ไม่พบข้อมูลที่ตรงกัน',
placeholder: 'เลือก'
},
pagination: {
goto: 'ไปที่',
pagesize: '/หน้า',

View File

@ -56,6 +56,10 @@ export default {
noData: 'Veri yok',
placeholder: 'Seç'
},
cascader: {
noMatch: 'Eşleşen veri bulunamadı',
placeholder: 'Seç'
},
pagination: {
goto: 'Git',
pagesize: '/page',

View File

@ -56,6 +56,10 @@ export default {
noData: 'Không tìm thấy dữ liệu',
placeholder: 'Chọn'
},
cascader: {
noMatch: 'Dữ liệu không phù hợp',
placeholder: 'Chọn'
},
pagination: {
goto: 'Nhảy tới',
pagesize: '/trang',

View File

@ -56,6 +56,10 @@ export default {
noData: '无数据',
placeholder: '请选择'
},
cascader: {
noMatch: '无匹配数据',
placeholder: '请选择'
},
pagination: {
goto: '前往',
pagesize: '条/页',

View File

@ -56,6 +56,10 @@ export default {
noData: '無資料',
placeholder: '請選擇'
},
cascader: {
noMatch: '無匹配資料',
placeholder: '請選擇'
},
pagination: {
goto: '前往',
pagesize: '項/頁',

View File

@ -70,6 +70,10 @@ export default {
default: true
},
modalClass: {},
modalAppendToBody: {
type: Boolean,
default: false
},
lockScroll: {
type: Boolean,
default: true
@ -185,7 +189,7 @@ export default {
PopupManager.closeModal(this._popupId);
this._closing = false;
}
PopupManager.openModal(this._popupId, PopupManager.nextZIndex(), dom, props.modalClass, props.modalFade);
PopupManager.openModal(this._popupId, PopupManager.nextZIndex(), this.modalAppendToBody ? undefined : dom, props.modalClass, props.modalFade);
if (props.lockScroll) {
if (!this.bodyOverflow) {
this.bodyPaddingRight = document.body.style.paddingRight;

View File

@ -36,7 +36,7 @@ export default {
type: Boolean,
default: true
},
options: {
popperOptions: {
type: Object,
default() {
return {
@ -76,7 +76,7 @@ export default {
return;
}
const options = this.options;
const options = this.popperOptions;
const popper = this.popperElm = this.popperElm || this.popper || this.$refs.popper;
let reference = this.referenceElm = this.referenceElm || this.reference || this.$refs.reference;
@ -85,6 +85,7 @@ export default {
this.$slots.reference[0]) {
reference = this.referenceElm = this.$slots.reference[0].elm;
}
if (!popper || !reference) return;
if (this.visibleArrow) this.appendArrow(popper);
if (this.appendToBody) document.body.appendChild(this.popperElm);

View File

@ -57,7 +57,7 @@ describe('Autocomplete', () => {
setTimeout(_ => {
const suggestions = vm.$refs.autocomplete.$refs.suggestions.$el;
expect(suggestions.style.display).to.not.equal('none');
expect(suggestions.children.length).to.be.equal(4);
expect(suggestions.querySelectorAll('.el-autocomplete-suggestion__list li').length).to.be.equal(4);
document.body.click();
setTimeout(_ => {
@ -74,14 +74,12 @@ describe('Autocomplete', () => {
ref="autocomplete"
:fetch-suggestions="querySearch"
placeholder="请输入内容autocomplete2"
@select="handleSelect"
></el-autocomplete>
`,
data() {
return {
restaurants: [],
state: '',
onceSelected: false
state: ''
};
},
methods: {
@ -102,27 +100,29 @@ describe('Autocomplete', () => {
{ 'value': '新旺角茶餐厅', 'address': '上海市普陀区真北路988号创邑金沙谷6号楼113' },
{ 'value': '泷千家(天山西路店)', 'address': '天山西路438号' }
];
},
handleSelect() {
this.onceSelected = true;
}
},
mounted() {
this.restaurants = this.loadAll();
}
}, true);
let elm = vm.$el;
let inputElm = elm.querySelector('input');
const autocomplete = vm.$refs.autocomplete;
const elm = vm.$el;
const inputElm = elm.querySelector('input');
const spy = sinon.spy();
autocomplete.$on('select', spy);
inputElm.focus();
setTimeout(_ => {
let suggestionsList = vm.$refs.autocomplete.$refs.suggestions.$el;
suggestionsList.children[1].click();
const suggestions = autocomplete.$refs.suggestions.$el;
const suggestionList = suggestions.querySelectorAll('.el-autocomplete-suggestion__list li');
suggestionList[1].click();
setTimeout(_ => {
expect(inputElm.value).to.be.equal('Hot honey 首尔炸鸡(仙霞路)');
expect(vm.state).to.be.equal('Hot honey 首尔炸鸡(仙霞路)');
expect(vm.onceSelected).to.be.true;
expect(elm.querySelector('.el-autocomplete__suggestions')).to.not.exist;
expect(spy.withArgs().calledOnce).to.be.true;
expect(suggestions.style.display).to.be.equal('none');
done();
}, 500);
}, 500);
@ -135,14 +135,12 @@ describe('Autocomplete', () => {
v-model="state"
:fetch-suggestions="querySearch"
placeholder="请输入内容autocomplete3"
@select="handleSelect"
></el-autocomplete>
`,
data() {
return {
restaurants: [],
state: '',
onceSelected: false
state: ''
};
},
methods: {
@ -181,28 +179,25 @@ describe('Autocomplete', () => {
{ 'value': '纵食', 'address': '元丰天山花园(东门) 双流路267号' },
{ 'value': '钱记', 'address': '上海市长宁区天山西路' }
];
},
handleSelect() {
this.onceSelected = true;
}
},
mounted() {
this.restaurants = this.loadAll();
}
}, true);
let elm = vm.$el;
let inputElm = elm.querySelector('input');
const autocomplete = vm.$refs.autocomplete;
const inputElm = autocomplete.$el.querySelector('input');
inputElm.focus();
setTimeout(_ => {
vm.$refs.autocomplete.highlight(8);
autocomplete.highlight(8);
vm.$nextTick(_ => {
let suggestionsList = vm.$refs.autocomplete.$refs.suggestions.$el;
let highlightedItem = suggestionsList.children[8];
expect(highlightedItem.className).to.be.equal('highlighted');
expect(suggestionsList.scrollTop === highlightedItem.scrollHeight).to.be.true;
vm.$refs.autocomplete.highlight(8);
const suggestions = autocomplete.$refs.suggestions.$el.querySelector('.el-autocomplete-suggestion__wrap');
let suggestionsList = suggestions.querySelectorAll('.el-autocomplete-suggestion__list li');
let highlightedItem = suggestionsList[8];
expect(highlightedItem.classList.contains('highlighted')).to.be.true;
expect(suggestions.scrollTop === highlightedItem.scrollHeight).to.be.true;
done();
});
}, 500);
@ -215,14 +210,12 @@ describe('Autocomplete', () => {
v-model="state"
:fetch-suggestions="querySearch"
placeholder="请输入内容autocomplete3"
@select="handleSelect"
></el-autocomplete>
`,
data() {
return {
restaurants: [],
state: '',
onceSelected: false
state: ''
};
},
methods: {
@ -251,29 +244,27 @@ describe('Autocomplete', () => {
{ 'value': '阿姨奶茶/豪大大', 'address': '嘉定区曹安路1611号' },
{ 'value': '新麦甜四季甜品炸鸡', 'address': '嘉定区曹安公路2383弄55号' }
];
},
handleSelect() {
this.onceSelected = true;
}
},
mounted() {
this.restaurants = this.loadAll();
}
}, true);
let elm = vm.$el;
let inputElm = elm.querySelector('input');
const autocomplete = vm.$refs.autocomplete;
let inputElm = vm.$el.querySelector('input');
inputElm.focus();
setTimeout(_ => {
vm.$refs.autocomplete.highlight(15);
autocomplete.highlight(15);
vm.$nextTick(_ => {
let suggestionsList = vm.$refs.autocomplete.$refs.suggestions.$el;
let highlightedItem = suggestionsList.children[11];
const suggestions = autocomplete.$refs.suggestions.$el;
const suggestionsList = suggestions.querySelectorAll('.el-autocomplete-suggestion__list li');
let highlightedItem = suggestionsList[11];
expect(highlightedItem.className).to.be.equal('highlighted');
vm.$refs.autocomplete.highlight(-5);
autocomplete.highlight(-5);
vm.$nextTick(_ => {
let highlightedItem = suggestionsList.children[0];
let highlightedItem = suggestionsList[0];
expect(highlightedItem.className).to.be.equal('highlighted');
});
done();
@ -321,13 +312,12 @@ describe('Autocomplete', () => {
this.restaurants = this.loadAll();
}
}, true);
let elm = vm.$el;
let inputElm = elm.querySelector('input');
let inputElm = vm.$el.querySelector('input');
inputElm.focus();
setTimeout(_ => {
let suggestionsList = vm.$refs.autocomplete.$refs.suggestions.$el;
expect(suggestionsList.style.display).to.be.equal('none');
let suggestions = vm.$refs.autocomplete.$refs.suggestions.$el;
expect(suggestions.style.display).to.be.equal('none');
done();
}, 500);
});

View File

@ -0,0 +1,524 @@
import { createVue, destroyVM, triggerEvent } from '../util';
describe('Cascader', () => {
let vm;
afterEach(() => {
destroyVM(vm);
});
it('create', done => {
vm = createVue({
template: `
<el-cascader
ref="cascader"
placeholder="请选择"
:options="options"
v-model="selectedOptions"
></el-cascader>
`,
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake'
}]
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei'
}]
}]
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men'
}]
}]
}],
selectedOptions: []
};
}
}, true);
expect(vm.$el).to.be.exist;
vm.$el.click();
setTimeout(_ => {
expect(document.body.querySelector('.el-cascader-menus')).to.be.exist;
const menu = vm.$refs.cascader.menu;
const menuElm = menu.$el;
const item1 = menuElm.querySelector('.el-cascader-menu__item');
item1.click();
menu.$nextTick(_ => {
expect(menuElm.children.length).to.be.equal(2);
expect(item1.classList.contains('is-active')).to.be.true;
const item2 = menuElm.children[1].querySelector('.el-cascader-menu__item');
item2.click();
menu.$nextTick(_ => {
expect(menuElm.children.length).to.be.equal(3);
expect(item2.classList.contains('is-active')).to.be.true;
const item3 = menuElm.children[2].querySelector('.el-cascader-menu__item');
item3.click();
setTimeout(_ => {
expect(document.body.querySelector('.el-cascader-menus').style.display).to.be.equal('none');
expect(vm.selectedOptions[0]).to.be.equal('zhejiang');
expect(vm.selectedOptions[1]).to.be.equal('hangzhou');
expect(vm.selectedOptions[2]).to.be.equal('xihu');
triggerEvent(vm.$refs.cascader.$el, 'mouseenter');
vm.$nextTick(_ => {
vm.$refs.cascader.$el.querySelector('.el-cascader__clearIcon').click();
vm.$nextTick(_ => {
expect(vm.selectedOptions.length).to.be.equal(0);
done();
});
});
}, 500);
});
});
}, 300);
});
it('not allow clearable', done => {
vm = createVue({
template: `
<el-cascader
ref="cascader"
placeholder="请选择"
:options="options"
:clearable="false"
v-model="selectedOptions"
></el-cascader>
`,
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake'
}]
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei'
}]
}]
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men'
}]
}]
}],
selectedOptions: []
};
}
}, true);
expect(vm.$el).to.be.exist;
triggerEvent(vm.$refs.cascader.$el, 'mouseenter');
vm.$nextTick(_ => {
expect(vm.$refs.cascader.$el.querySelector('.el-cascader__clearIcon')).to.not.exist;
done();
});
});
it('disabled options', done => {
vm = createVue({
template: `
<el-cascader
ref="cascader"
placeholder="请选择"
:options="options"
v-model="selectedOptions"
></el-cascader>
`,
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
disabled: true,
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake'
}]
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei'
}]
}]
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men'
}]
}]
}],
selectedOptions: []
};
}
}, true);
expect(vm.$el).to.be.exist;
vm.$el.click();
setTimeout(_ => {
expect(document.body.querySelector('.el-cascader-menus')).to.be.exist;
const menu = vm.$refs.cascader.menu;
const menuElm = menu.$el;
const item1 = menuElm.querySelector('.el-cascader-menu__item');
item1.click();
menu.$nextTick(_ => {
expect(menuElm.children.length).to.be.equal(1);
expect(item1.classList.contains('is-active')).to.be.false;
done();
});
}, 300);
});
it('default value', done => {
vm = createVue({
template: `
<el-cascader
ref="cascader"
placeholder="请选择"
:options="options"
v-model="selectedOptions"
></el-cascader>
`,
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake'
}]
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei'
}]
}]
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men'
}]
}]
}],
selectedOptions: ['zhejiang', 'hangzhou', 'xihu']
};
}
}, true);
expect(vm.$el).to.be.exist;
vm.$el.click();
setTimeout(_ => {
expect(document.body.querySelector('.el-cascader-menus')).to.be.exist;
const menu = vm.$refs.cascader.menu;
const menuElm = menu.$el;
const item1 = menuElm.children[0].querySelector('.el-cascader-menu__item');
const item2 = menuElm.children[1].querySelector('.el-cascader-menu__item');
const item3 = menuElm.children[2].querySelector('.el-cascader-menu__item');
expect(menuElm.children.length).to.be.equal(3);
expect(item1.classList.contains('is-active')).to.be.true;
expect(item2.classList.contains('is-active')).to.be.true;
expect(item3.classList.contains('is-active')).to.be.true;
document.body.click();
setTimeout(_ => {
expect(document.body.querySelector('.el-cascader-menus').style.display).to.be.equal('none');
done();
}, 500);
}, 300);
});
it('expand by hover', done => {
vm = createVue({
template: `
<el-cascader
ref="cascader"
placeholder="请选择"
:options="options"
expand-trigger="hover"
v-model="selectedOptions"
></el-cascader>
`,
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake'
}]
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei'
}]
}]
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men'
}]
}]
}],
selectedOptions: []
};
}
}, true);
expect(vm.$el).to.be.exist;
vm.$el.click();
setTimeout(_ => {
expect(document.body.querySelector('.el-cascader-menus')).to.be.exist;
const menu = vm.$refs.cascader.menu;
const menuElm = menu.$el;
const item1 = menuElm.querySelector('.el-cascader-menu__item');
triggerEvent(item1, 'mouseenter');
menu.$nextTick(_ => {
expect(menuElm.children.length).to.be.equal(2);
expect(item1.classList.contains('is-active')).to.be.true;
const item2 = menuElm.children[1].querySelector('.el-cascader-menu__item');
triggerEvent(item2, 'mouseenter');
menu.$nextTick(_ => {
expect(menuElm.children.length).to.be.equal(3);
expect(item2.classList.contains('is-active')).to.be.true;
const item3 = menuElm.children[2].querySelector('.el-cascader-menu__item');
item3.click();
setTimeout(_ => {
expect(document.body.querySelector('.el-cascader-menus').style.display).to.be.equal('none');
expect(vm.selectedOptions[0]).to.be.equal('zhejiang');
expect(vm.selectedOptions[1]).to.be.equal('hangzhou');
expect(vm.selectedOptions[2]).to.be.equal('xihu');
done();
}, 500);
});
});
}, 300);
});
it('change on select', done => {
vm = createVue({
template: `
<el-cascader
ref="cascader"
placeholder="请选择"
:options="options"
change-on-select
v-model="selectedOptions"
></el-cascader>
`,
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake'
}]
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei'
}]
}]
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men'
}]
}]
}],
selectedOptions: []
};
}
}, true);
expect(vm.$el).to.be.exist;
vm.$el.click();
setTimeout(_ => {
expect(document.body.querySelector('.el-cascader-menus')).to.be.exist;
const menu = vm.$refs.cascader.menu;
const menuElm = menu.$el;
const item1 = menuElm.querySelector('.el-cascader-menu__item');
item1.click();
menu.$nextTick(_ => {
expect(menuElm.children.length).to.be.equal(2);
expect(item1.classList.contains('is-active')).to.be.true;
expect(vm.selectedOptions[0]).to.be.equal('zhejiang');
const item2 = menuElm.children[1].querySelector('.el-cascader-menu__item');
item2.click();
menu.$nextTick(_ => {
expect(menuElm.children.length).to.be.equal(3);
expect(item2.classList.contains('is-active')).to.be.true;
expect(vm.selectedOptions[1]).to.be.equal('hangzhou');
const item3 = menuElm.children[2].querySelector('.el-cascader-menu__item');
item3.click();
setTimeout(_ => {
expect(document.body.querySelector('.el-cascader-menus').style.display).to.be.equal('none');
expect(vm.selectedOptions[0]).to.be.equal('zhejiang');
expect(vm.selectedOptions[1]).to.be.equal('hangzhou');
expect(vm.selectedOptions[2]).to.be.equal('xihu');
done();
}, 500);
});
});
}, 300);
});
it('filterable', done => {
vm = createVue({
template: `
<el-cascader
ref="cascader"
placeholder="请选择"
:options="options"
filterable
v-model="selectedOptions"
></el-cascader>
`,
data() {
return {
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake'
}]
}, {
value: 'ningbo',
label: 'NingBo',
children: [{
value: 'jiangbei',
label: 'Jiang Bei'
}]
}]
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men'
}]
}]
}],
selectedOptions: []
};
}
}, true);
expect(vm.$el).to.be.exist;
vm.$refs.cascader.inputValue = 'z';
vm.$el.click();
setTimeout(_ => {
expect(document.body.querySelector('.el-cascader-menus')).to.be.exist;
const menu = vm.$refs.cascader.menu;
const menuElm = menu.$el;
const item1 = menuElm.querySelector('.el-cascader-menu__item');
expect(menuElm.children.length).to.be.equal(1);
expect(menuElm.children[0].children.length).to.be.equal(1);
done();
item1.click();
setTimeout(_ => {
expect(document.body.querySelector('.el-cascader-menus').style.display).to.be.equal('none');
expect(vm.selectedOptions[0]).to.be.equal('zhejiang');
expect(vm.selectedOptions[1]).to.be.equal('hangzhou');
expect(vm.selectedOptions[2]).to.be.equal('xihu');
done();
}, 500);
}, 300);
});
});

View File

@ -24,7 +24,7 @@ describe('Tabs', () => {
vm.$refs.tabs.$on('tab-click', spy);
setTimeout(_ => {
const tabList = vm.$refs.tabs.$refs.tabs;
const tabList = vm.$refs.tabs.$refs.nav.$refs.tabs;
expect(tabList[0].classList.contains('is-active')).to.be.true;
expect(paneList[0].style.display).to.not.ok;
@ -60,7 +60,7 @@ describe('Tabs', () => {
}, true);
setTimeout(_ => {
const paneList = vm.$el.querySelector('.el-tabs__content').children;
const tabList = vm.$refs.tabs.$refs.tabs;
const tabList = vm.$refs.tabs.$refs.nav.$refs.tabs;
expect(tabList[1].classList.contains('is-active')).to.be.true;
expect(paneList[1].style.display).to.not.ok;
@ -140,33 +140,172 @@ describe('Tabs', () => {
});
}, 100);
});
it('closable', done => {
it('editable', done => {
vm = createVue({
template: `
<el-tabs type="card" :closable="true" ref="tabs">
<el-tab-pane label="用户管理">A</el-tab-pane>
<el-tab-pane label="配置管理">B</el-tab-pane>
<el-tab-pane label="角色管理">C</el-tab-pane>
<el-tab-pane label="定时任务补偿">D</el-tab-pane>
<el-tabs ref="tabs" v-model="editableTabsValue" type="card" editable @edit="handleTabsEdit">
<el-tab-pane
v-for="(item, index) in editableTabs"
:label="item.title"
:name="item.name"
>
{{item.content}}
</el-tab-pane>
</el-tabs>
`
`,
data() {
return {
editableTabsValue: '2',
editableTabs: [{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content'
}, {
title: 'Tab 2',
name: '2',
content: 'Tab 2 content'
}, {
title: 'Tab 3',
name: '3',
content: 'Tab 3 content'
}],
tabIndex: 3
};
},
methods: {
handleTabsEdit(targetName, action) {
if (action === 'add') {
let newTabName = ++this.tabIndex + '';
this.editableTabs.push({
title: 'New Tab',
name: newTabName,
content: 'New Tab content'
});
this.editableTabsValue = newTabName;
}
if (action === 'remove') {
let tabs = this.editableTabs;
let activeName = this.editableTabsValue;
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
let nextTab = tabs[index + 1] || tabs[index - 1];
if (nextTab) {
activeName = nextTab.name;
}
}
});
}
this.editableTabsValue = activeName;
this.editableTabs = tabs.filter(tab => tab.name !== targetName);
}
}
}
}, true);
let spy = sinon.spy();
vm.$refs.tabs.$on('tab-remove', spy);
setTimeout(_ => {
const tabList = vm.$refs.tabs.$refs.tabs;
const tabList = vm.$refs.tabs.$refs.nav.$refs.tabs;
const paneList = vm.$el.querySelector('.el-tabs__content').children;
tabList[1].querySelector('.el-icon-close').click();
vm.$nextTick(_ => {
expect(tabList.length).to.be.equal(2);
expect(paneList.length).to.be.equal(2);
expect(tabList[1].classList.contains('is-active')).to.be.true;
vm.$refs.tabs.$el.querySelector('.el-tabs__new-tab').click();
vm.$nextTick(_ => {
expect(tabList.length).to.be.equal(3);
expect(paneList.length).to.be.equal(3);
expect(tabList[2].classList.contains('is-active')).to.be.true;
done();
});
});
}, 100);
});
it('addable & closable', done => {
vm = createVue({
template: `
<el-tabs
ref="tabs"
v-model="editableTabsValue"
type="card"
addable
closable
@tab-add="addTab"
@tab-remove="removeTab"
>
<el-tab-pane
v-for="(item, index) in editableTabs"
:label="item.title"
:name="item.name"
>
{{item.content}}
</el-tab-pane>
</el-tabs>
`,
data() {
return {
editableTabsValue: '2',
editableTabs: [{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content'
}, {
title: 'Tab 2',
name: '2',
content: 'Tab 2 content'
}],
tabIndex: 2
};
},
methods: {
addTab(targetName) {
let newTabName = ++this.tabIndex + '';
this.editableTabs.push({
title: 'New Tab',
name: newTabName,
content: 'New Tab content'
});
this.editableTabsValue = newTabName;
},
removeTab(targetName) {
let tabs = this.editableTabs;
let activeName = this.editableTabsValue;
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
let nextTab = tabs[index + 1] || tabs[index - 1];
if (nextTab) {
activeName = nextTab.name;
}
}
});
}
this.editableTabsValue = activeName;
this.editableTabs = tabs.filter(tab => tab.name !== targetName);
}
}
}, true);
setTimeout(_ => {
const tabList = vm.$refs.tabs.$refs.nav.$refs.tabs;
const paneList = vm.$el.querySelector('.el-tabs__content').children;
vm.$refs.tabs.$el.querySelector('.el-tabs__new-tab').click();
vm.$nextTick(_ => {
expect(tabList.length).to.be.equal(3);
expect(paneList.length).to.be.equal(3);
expect(spy.calledOnce).to.true;
expect(tabList[1].innerText.trim()).to.be.equal('角色管理');
expect(paneList[0].innerText.trim()).to.be.equal('A');
done();
expect(tabList[2].classList.contains('is-active')).to.be.true;
tabList[2].querySelector('.el-icon-close').click();
vm.$nextTick(_ => {
expect(tabList.length).to.be.equal(2);
expect(paneList.length).to.be.equal(2);
expect(tabList[1].classList.contains('is-active')).to.be.true;
done();
});
});
}, 100);
});
@ -187,42 +326,6 @@ describe('Tabs', () => {
done();
}, 100);
});
it('closable edge', done => {
vm = createVue({
template: `
<el-tabs type="card" :closable="true" ref="tabs">
<el-tab-pane label="用户管理">A</el-tab-pane>
<el-tab-pane label="配置管理">B</el-tab-pane>
<el-tab-pane label="角色管理">C</el-tab-pane>
<el-tab-pane label="定时任务补偿">D</el-tab-pane>
</el-tabs>
`
}, true);
vm.$nextTick(_ => {
const paneList = vm.$el.querySelector('.el-tabs__content').children;
const tabList = vm.$refs.tabs.$refs.tabs;
tabList[0].querySelector('.el-icon-close').click();
vm.$nextTick(_ => {
expect(tabList.length).to.be.equal(3);
expect(paneList.length).to.be.equal(3);
expect(tabList[0].innerText.trim()).to.be.equal('配置管理');
expect(paneList[0].innerText.trim()).to.be.equal('B');
tabList[2].click();
tabList[2].querySelector('.el-icon-close').click();
setTimeout(_ => {
expect(tabList.length).to.be.equal(2);
expect(paneList.length).to.be.equal(2);
expect(tabList[1].classList.contains('is-active')).to.be.true;
expect(tabList[1].innerText.trim()).to.be.equal('角色管理');
expect(paneList[1].innerText.trim()).to.be.equal('C');
done();
}, 100);
});
});
});
it('disabled', done => {
vm = createVue({
template: `
@ -236,7 +339,7 @@ describe('Tabs', () => {
}, true);
vm.$nextTick(_ => {
const tabList = vm.$refs.tabs.$refs.tabs;
const tabList = vm.$refs.tabs.$refs.nav.$refs.tabs;
tabList[1].click();
vm.$nextTick(_ => {

View File

@ -6,22 +6,27 @@ describe('Tooltip', () => {
destroyVM(vm);
});
it('create', () => {
it('create', done => {
vm = createVue(`
<el-tooltip content="提示文字">
<el-tooltip ref="tooltip" content="提示文字">
<button>click</button>
</el-tooltip>`);
expect(vm.$el.querySelector('.el-tooltip__popper')).to.have.property('textContent', '提示文字');
vm.$nextTick(_ => {
expect(vm.$refs.tooltip.popperVM.$el).to.have.property('textContent', '提示文字');
done();
});
});
it('custom popper class', () => {
it('custom popper class', done => {
vm = createVue(`
<el-tooltip content="提示文字" popper-class="custom-popper">
<el-tooltip ref="tooltip" content="提示文字" popper-class="custom-popper">
<button>click</button>
</el-tooltip>`);
expect(vm.$el.querySelector('.el-tooltip__popper').classList.contains('custom-popper')).to.true;
vm.$nextTick(_ => {
expect(vm.$refs.tooltip.popperVM.$el.classList.contains('custom-popper')).to.true;
done();
});
});
describe('manual', () => {
@ -73,18 +78,24 @@ describe('Tooltip', () => {
triggerEvent(tooltip.$el, 'mouseenter');
it('popperElm is exist', () => expect(tooltip.popperElm).to.exist);
it('showPopper is true', () => expect(tooltip.showPopper).to.true);
it('close popper', () => {
it('close popper', done => {
triggerEvent(tooltip.$el, 'mouseleave');
expect(tooltip.showPopper).to.false;
setTimeout(() => {
expect(tooltip.showPopper).to.false;
done();
}, 300);
});
});
it('light mode', () => {
it('light mode', done => {
vm = createVue(`
<el-tooltip content="abc" effect="light">
<el-tooltip ref="tooltip" content="abc" effect="light">
<button>abc</button>
</el-tooltip>
`);
expect(vm.$el.querySelector('.is-light')).to.exist;
vm.$nextTick(_ => {
expect(vm.$refs.tooltip.popperVM.$el.classList.contains('is-light')).to.exist;
done();
});
});
});

View File

@ -437,6 +437,10 @@ babel-messages@^6.8.0:
dependencies:
babel-runtime "^6.0.0"
babel-plugin-add-module-exports@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz#9ae9a1f4a8dc67f0cdec4f4aeda1e43a5ff65e25"
babel-plugin-check-es2015-constants@^6.3.13:
version "6.8.0"
resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.8.0.tgz#dbf024c32ed37bfda8dee1e76da02386a8d26fe7"
@ -6905,20 +6909,19 @@ vue-hot-reload-api@^2.0.1:
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.0.6.tgz#817d4bfb30f55428aa1012d029499e07f3147d21"
vue-loader@>=9.4.2, vue-loader@^10.0.2:
version "10.0.2"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-10.0.2.tgz#6fe7bcefb32c5439bd0338464aa22673ad62299c"
version "10.3.0"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-10.3.0.tgz#436421736e9ad0f1c481330327c376963db86a19"
dependencies:
consolidate "^0.14.0"
hash-sum "^1.0.2"
js-beautify "^1.6.3"
loader-utils "^0.2.10"
lru-cache "^4.0.1"
object-assign "^4.0.0"
postcss "^5.0.10"
postcss-selector-parser "^2.0.0"
source-map "^0.5.6"
vue-hot-reload-api "^2.0.1"
vue-style-loader "^1.0.0"
vue-style-loader "^2.0.0"
vue-template-es2015-compiler "^1.2.2"
vue-markdown-loader@^0.5.1:
@ -6941,6 +6944,13 @@ vue-style-loader@^1.0.0:
dependencies:
loader-utils "^0.2.7"
vue-style-loader@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-2.0.0.tgz#1a3bb55239ac541ee3af0301d66f16fc86786543"
dependencies:
hash-sum "^1.0.2"
loader-utils "^0.2.7"
vue-template-compiler@*, vue-template-compiler@^2.1.8:
version "2.1.8"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.1.8.tgz#12dd1cc63793f59be580c694a61610cb9369d629"