15 | * ```
16 | */
17 | export default {
18 | bind(el, binding, vnode) {
19 | const id = nodeList.push(el) - 1;
20 | const documentHandler = function(e) {
21 | if (!vnode.context ||
22 | el.contains(e.target) ||
23 | (vnode.context.popperElm &&
24 | vnode.context.popperElm.contains(e.target))) return;
25 |
26 | if (binding.expression) {
27 | el[ctx].methodName &&
28 | vnode.context[el[ctx].methodName] &&
29 | vnode.context[el[ctx].methodName]();
30 | } else {
31 | el[ctx].bindingFn && el[ctx].bindingFn();
32 | }
33 | };
34 | el[ctx] = {
35 | id,
36 | documentHandler,
37 | methodName: binding.expression,
38 | bindingFn: binding.value
39 | };
40 | },
41 |
42 | update(el, binding) {
43 | el[ctx].methodName = binding.expression;
44 | el[ctx].bindingFn = binding.value;
45 | },
46 |
47 | unbind(el) {
48 | let len = nodeList.length;
49 |
50 | for (let i = 0; i < len; i++) {
51 | if (nodeList[i][ctx].id === el[ctx].id) {
52 | nodeList.splice(i, 1);
53 | break;
54 | }
55 | }
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/packages/tabs/src/tab-pane.vue:
--------------------------------------------------------------------------------
1 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: dist test
2 | default: help
3 |
4 | # build all theme
5 | build-theme:
6 | npm run build:theme
7 |
8 | install:
9 | npm install
10 |
11 | install-cn:
12 | npm install --registry=http://registry.npm.taobao.org
13 |
14 | dev:
15 | npm run dev
16 |
17 | play:
18 | npm run dev:play
19 |
20 | new:
21 | node build/bin/new.js $(filter-out $@,$(MAKECMDGOALS))
22 |
23 | new-lang:
24 | node build/bin/new-lang.js $(filter-out $@,$(MAKECMDGOALS))
25 |
26 | dist: install
27 | npm run dist
28 |
29 | dist-all:
30 | npm run dist:all
31 |
32 | deploy:
33 | @npm run deploy
34 |
35 | pub:
36 | npm run pub
37 |
38 | pub-all:
39 | npm run pub:all
40 |
41 | test:
42 | npm run test:watch
43 |
44 | help:
45 | @echo " \033[35mmake\033[0m \033[1m命令使用说明\033[0m"
46 | @echo " \033[35mmake install\033[0m\t\033[0m\t\033[0m\t\033[0m\t--- 安装依赖"
47 | @echo " \033[35mmake new
[中文名]\033[0m\t--- 创建新组件 package. 例如 'make new button 按钮'"
48 | @echo " \033[35mmake dev\033[0m\t\033[0m\t\033[0m\t\033[0m\t--- 开发模式"
49 | @echo " \033[35mmake dist\033[0m\t\033[0m\t\033[0m\t\033[0m\t--- 编译项目,生成目标文件"
50 | @echo " \033[35mmake dist-all\033[0m\t\033[0m\t\033[0m\t--- 分别编译每个组件项目"
51 | @echo " \033[35mmake deploy\033[0m\t\033[0m\t\033[0m\t\033[0m\t--- 部署 demo"
52 | @echo " \033[35mmake pub\033[0m\t\033[0m\t\033[0m\t\033[0m\t--- 发布到 npm 上"
53 | @echo " \033[35mmake pub-all\033[0m\t\033[0m\t\033[0m\t\033[0m\t--- 发布各组件到 npm 上"
54 | @echo " \033[35mmake new-lang \033[0m\t\033[0m\t\033[0m\t--- 为网站添加新语言. 例如 'make new-lang fr'"
55 |
--------------------------------------------------------------------------------
/packages/radio/src/radio.vue:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
60 |
--------------------------------------------------------------------------------
/src/mixins/migrating.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Show migrating guide in browser console.
3 | *
4 | * Usage:
5 | * import Migrating from 'element-ui/src/mixins/migrating';
6 | *
7 | * mixins: [Migrating]
8 | *
9 | * add getMigratingConfig method for your component.
10 | * getMigratingConfig() {
11 | * return {
12 | * props: {
13 | * 'allow-no-selection': 'allow-no-selection is removed.',
14 | * 'selection-mode': 'selection-mode is removed.'
15 | * },
16 | * events: {
17 | * selectionchange: 'selectionchange is renamed to selection-change.'
18 | * }
19 | * };
20 | * },
21 | */
22 | export default {
23 | mounted() {
24 | if (process.env.NODE_ENV === 'production') return;
25 | if (!this.$vnode) return;
26 | const { props, events } = this.getMigratingConfig();
27 | const { data, componentOptions } = this.$vnode;
28 | const definedProps = data.attrs || {};
29 | const definedEvents = componentOptions.listeners || {};
30 |
31 | for (let propName in definedProps) {
32 | if (definedProps.hasOwnProperty(propName) && props[propName]) {
33 | console.warn(`[Element Migrating][Attribute]: ${props[propName]}`);
34 | }
35 | }
36 |
37 | for (let eventName in definedEvents) {
38 | if (definedEvents.hasOwnProperty(eventName) && events[eventName]) {
39 | console.warn(`[Element Migrating][Event]: ${events[eventName]}`);
40 | }
41 | }
42 | },
43 | methods: {
44 | getMigratingConfig() {
45 | return {
46 | props: {},
47 | events: {}
48 | };
49 | }
50 | }
51 | };
52 |
--------------------------------------------------------------------------------
/src/utils/sync.js:
--------------------------------------------------------------------------------
1 | const SYNC_HOOK_PROP = '$v-sync';
2 |
3 | /**
4 | * v-sync directive
5 | *
6 | * Usage:
7 | * v-sync:component-prop="context prop name"
8 | *
9 | * If your want to sync component's prop "visible" to context prop "myVisible", use like this:
10 | * v-sync:visible="myVisible"
11 | */
12 | export default {
13 | bind(el, binding, vnode) {
14 | const context = vnode.context;
15 | const component = vnode.child;
16 | const expression = binding.expression;
17 | const prop = binding.arg;
18 |
19 | if (!expression || !prop) {
20 | console.warn('v-sync should specify arg & expression, for example: v-sync:visible="myVisible"');
21 | return;
22 | }
23 |
24 | if (!component || !component.$watch) {
25 | console.warn('v-sync is only available on Vue Component');
26 | return;
27 | }
28 |
29 | const unwatchContext = context.$watch(expression, (val) => {
30 | component[prop] = val;
31 | });
32 |
33 | const unwatchComponent = component.$watch(prop, (val) => {
34 | context[expression] = val;
35 | });
36 |
37 | Object.defineProperty(component, SYNC_HOOK_PROP, {
38 | value: {
39 | unwatchContext,
40 | unwatchComponent
41 | },
42 | enumerable: false
43 | });
44 | },
45 |
46 | unbind(el, binding, vnode) {
47 | const component = vnode.child;
48 | if (component && component[SYNC_HOOK_PROP]) {
49 | const { unwatchContext, unwatchComponent } = component[SYNC_HOOK_PROP];
50 | unwatchContext && unwatchContext();
51 | unwatchComponent && unwatchComponent();
52 | }
53 | }
54 | };
55 |
--------------------------------------------------------------------------------
/examples/docs/en-US/i18n.md:
--------------------------------------------------------------------------------
1 | ## Internationalization
2 |
3 | The default language of Element is Chinese. If you wish to use another language, you'll need to do some i18n configuration. In your entry file, if you are importing Element entirely:
4 |
5 | ```javascript
6 | import Vue from 'vue'
7 | import ElementUI from 'element-ui'
8 | import locale from 'element-ui/lib/locale/lang/en'
9 |
10 | Vue.use(ElementUI, { locale })
11 | ```
12 |
13 | Or if you are importing Element on demand:
14 |
15 | ```javascript
16 | import Vue from 'vue'
17 | import { Button, Select } from 'element-ui'
18 | import lang from 'element-ui/lib/locale/lang/en'
19 | import locale from 'element-ui/lib/locale'
20 |
21 | // configure language
22 | locale.use(lang)
23 |
24 | // import components
25 | Vue.component(Button.name, Button)
26 | Vue.component(Select.name, Select)
27 | ```
28 |
29 | The Chinese language pack is imported by default, even if you're using another language. But with `IgnorePlugin` provided by webpack you can ignore it when building:
30 |
31 | webpack.config.js
32 | ```javascript
33 | {
34 | plugins: [
35 | new webpack.IgnorePlugin(/element-ui\/lib\/locale\/lang\/zh-CN/)
36 | ]
37 | }
38 | ```
39 |
40 | Currently Element ships with the following languages:
41 |
42 | - Chinese (zh-CN)
43 | - English (en)
44 | - German (de)
45 | - Portuguese (pt)
46 |
47 |
48 | If your target language is not included, you are more than welcome to contribute: just add another language config [here](https://github.com/ElemeFE/element/tree/master/src/locale/lang) and create a pull request.
49 |
--------------------------------------------------------------------------------
/packages/message/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import { PopupManager } from 'vue-popup';
3 | let MessageConstructor = Vue.extend(require('./main.vue'));
4 |
5 | let instance;
6 | let instances = [];
7 | let seed = 1;
8 |
9 | var Message = function(options) {
10 | options = options || {};
11 | if (typeof options === 'string') {
12 | options = {
13 | message: options
14 | };
15 | }
16 | let userOnClose = options.onClose;
17 | let id = 'message_' + seed++;
18 |
19 | options.onClose = function() {
20 | Message.close(id, userOnClose);
21 | };
22 |
23 | instance = new MessageConstructor({
24 | data: options
25 | });
26 | instance.id = id;
27 | instance.vm = instance.$mount();
28 | document.body.appendChild(instance.vm.$el);
29 | instance.vm.visible = true;
30 | instance.dom = instance.vm.$el;
31 | instance.dom.style.zIndex = PopupManager.nextZIndex();
32 | instances.push(instance);
33 | return instance.vm;
34 | };
35 |
36 | ['success', 'warning', 'info', 'error'].forEach(type => {
37 | Message[type] = options => {
38 | if (typeof options === 'string') {
39 | options = {
40 | message: options
41 | };
42 | }
43 | options.type = type;
44 | return Message(options);
45 | };
46 | });
47 |
48 | Message.close = function(id, userOnClose) {
49 | for (let i = 0, len = instances.length; i < len; i++) {
50 | if (id === instances[i].id) {
51 | if (typeof userOnClose === 'function') {
52 | userOnClose(instances[i]);
53 | }
54 | instances.splice(i, 1);
55 | break;
56 | }
57 | }
58 | };
59 |
60 | export default Message;
61 |
--------------------------------------------------------------------------------
/packages/theme-default/src/select-dropdown.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | @import "./common/var.css";
3 |
4 | @component-namespace el {
5 |
6 | @b select-dropdown {
7 | position: absolute;
8 | z-index: 1001;
9 | border: var(--select-dropdown-border);
10 | border-radius: var(--border-radius-small);
11 | background-color: var(--select-dropdown-background);
12 | box-shadow: var(--select-dropdown-shadow);
13 | box-sizing: border-box;
14 | margin: 5px 0;
15 |
16 | @when multiple {
17 | & .el-select-dropdown__item.selected {
18 | color: var(--select-option-selected);
19 | background-color: var(--select-dropdown-background);
20 |
21 | &.hover {
22 | background-color: var(--select-option-hover-background);
23 | }
24 |
25 | &::after {
26 | position: absolute;
27 | right: 10px;
28 | font-family: 'element-icons';
29 | content: "\E608";
30 | font-size: 11px;
31 | -webkit-font-smoothing: antialiased;
32 | -moz-osx-font-smoothing: grayscale;
33 | }
34 | }
35 | }
36 | }
37 |
38 | @b select-dropdown__empty {
39 | padding: var(--select-dropdown-empty-padding);
40 | margin: 0;
41 | text-align: center;
42 | color: var(--select-dropdown-empty-color);
43 | font-size: var(--select-font-size);
44 | }
45 |
46 | @b select-dropdown__list {
47 | list-style: none;
48 | padding: var(--select-dropdown-padding);
49 | margin: 0;
50 | width: 100%;
51 | max-height: var(--select-dropdown-max-height);
52 | box-sizing: border-box;
53 | overflow-y: auto;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/examples/pages/template/component.tpl:
--------------------------------------------------------------------------------
1 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
65 |
--------------------------------------------------------------------------------
/packages/theme-default/src/mixins/_button.css:
--------------------------------------------------------------------------------
1 | @define-mixin button-variant $color, $background-color, $border-color {
2 | color: $color;
3 | background-color: $background-color;
4 | border-color: $border-color;
5 |
6 | &:hover,
7 | &:focus {
8 | background: tint($background-color, var(--button-hover-tint-percent));
9 | border-color: tint($border-color, var(--button-hover-tint-percent));
10 | color: $color;
11 | }
12 |
13 | &:active {
14 | background: shade($background-color, var(--button-active-shade-percent));
15 | border-color: shade($border-color, var(--button-active-shade-percent));
16 | color: $color;
17 | outline: none;
18 | }
19 |
20 | &.is-active {
21 | background: shade($background-color, var(--button-active-shade-percent));
22 | border-color: shade($border-color, var(--button-active-shade-percent));
23 | color: $color;
24 | }
25 |
26 | &.is-plain {
27 | background: var(--button-default-fill);
28 | border: var(--border-base);
29 | color: var(--button-default-color);
30 |
31 | &:hover,
32 | &:focus {
33 | background: #fff;
34 | border-color: $border-color;
35 | color: $background-color;
36 | }
37 |
38 | &:active {
39 | background: #fff;
40 | border-color: shade($border-color, var(--button-active-shade-percent));
41 | color: shade($background-color, var(--button-active-shade-percent));
42 | outline: none;
43 | }
44 | }
45 | }
46 |
47 | @define-mixin button-size $padding-vertical, $padding-horizontal, $font-size, $border-radius {
48 | padding: $padding-vertical $padding-horizontal;
49 | font-size: $font-size;
50 | border-radius: $border-radius;
51 | }
52 |
--------------------------------------------------------------------------------
/examples/docs/en-US/installation.md:
--------------------------------------------------------------------------------
1 | ## Installation
2 |
3 | ### npm
4 | Installing with npm is recommended, for it works seamlessly with [webpack](https://webpack.js.org/).
5 |
6 | ```shell
7 | npm i element-ui -D
8 | ```
9 |
10 | ### CDN
11 | Get the latest version from [unpkg.com/element-ui](https://unpkg.com/element-ui/) , and import JavaScript and CSS file in your page.
12 |
13 | ```html
14 |
15 |
16 |
17 |
18 | ```
19 |
20 | ### Hello world
21 | If you are using CDN, a hello-world page is easy with Element. [Online Demo](http://codepen.io/QingWei-Li/pen/vXwJrY)
22 |
23 | ```html
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
Button
34 |
35 | Try Element
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
51 |
52 | ```
53 | If you are using npm and wish to apply webpack, please continue to the next page: Quick Start.
--------------------------------------------------------------------------------
/packages/theme-default/src/dropdown.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | @import "./common/var.css";
3 | @import "./button.css";
4 |
5 | @component-namespace el {
6 | @b dropdown {
7 | display: inline-block;
8 | position: relative;
9 | color: #475669;
10 | font-size: var(--font-size-base);
11 |
12 | .el-button-group {
13 | display: block;
14 | }
15 |
16 | & .el-dropdown__caret-button {
17 | padding: * 5px;
18 |
19 | & .el-dropdown__icon {
20 | padding-left: 0;
21 | }
22 | }
23 | @e icon {
24 | font-size: 12px;
25 | margin: 0 3px;
26 | }
27 | }
28 | @b dropdown-menu {
29 | margin: 5px 0;
30 | background-color: #fff;
31 | border: 1px solid #D3DCE6;
32 | box-shadow: var(--dropdown-menu-box-shadow);
33 | padding: 6px 0;
34 | z-index: 10;
35 | position: absolute;
36 | top: 0;
37 | left: 0;
38 | min-width: 100px;
39 |
40 | @e item {
41 | list-style: none;
42 | line-height: 36px;
43 | padding: 0 10px;
44 | margin: 0;
45 | cursor: pointer;
46 |
47 | &:not(.is-disabled):hover {
48 | background-color: var(--dropdown-menuItem-hover-fill);
49 | color: var(--dropdown-menuItem-hover-color);
50 | }
51 | @m divided {
52 | position: relative;
53 | margin-top: 6px;
54 | border-top: 1px solid #D3DCE6;
55 |
56 | &:before {
57 | content: '';
58 | height: 6px;
59 | display: block;
60 | margin: 0 -10px;
61 | background-color: #fff;
62 | }
63 | }
64 | @when disabled {
65 | cursor: default;
66 | color: #c0ccda;
67 | pointer-events: none;
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/packages/col/src/col.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'ElCol',
3 |
4 | props: {
5 | span: {
6 | type: Number,
7 | default: 24
8 | },
9 | offset: Number,
10 | pull: Number,
11 | push: Number,
12 | xs: [Number, Object],
13 | sm: [Number, Object],
14 | md: [Number, Object],
15 | lg: [Number, Object]
16 | },
17 |
18 | computed: {
19 | gutter() {
20 | return this.$parent.gutter;
21 | },
22 |
23 | style() {
24 | var ret = {};
25 |
26 | if (this.gutter) {
27 | ret.paddingLeft = this.gutter / 2 + 'px';
28 | ret.paddingRight = ret.paddingLeft;
29 | }
30 |
31 | return ret;
32 | }
33 | },
34 | render(h) {
35 | let { style } = this;
36 | let classList = [];
37 |
38 | ['span', 'offset', 'pull', 'push'].forEach(prop => {
39 | if (this[prop]) {
40 | classList.push(
41 | prop !== 'span'
42 | ? `el-col-${prop}-${this[prop]}`
43 | : `el-col-${this[prop]}`
44 | );
45 | }
46 | });
47 |
48 | ['xs', 'sm', 'md', 'lg'].forEach(size => {
49 | if (typeof this[size] === 'number') {
50 | classList.push(`el-col-${size}-${this[size]}`);
51 | } else if (typeof this[size] === 'object') {
52 | let props = this[size];
53 | Object.keys(props).forEach(prop => {
54 | classList.push(
55 | prop !== 'span'
56 | ? `el-col-${size}-${prop}-${props[prop]}`
57 | : `el-col-${size}-${props[prop]}`
58 | );
59 | });
60 | }
61 | });
62 |
63 | return (
64 |
67 | {this.$slots.default}
68 |
69 | );
70 | }
71 | };
72 |
--------------------------------------------------------------------------------
/packages/theme-default/src/loading.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | @import "./common/var.css";
3 |
4 | @component-namespace el {
5 | @b loading-mask {
6 | position: absolute;
7 | z-index: 10000;
8 | background-color: rgba(255, 255, 255, .9);
9 | margin: 0;
10 | top: 0;
11 | right: 0;
12 | bottom: 0;
13 | left: 0;
14 |
15 | @when fullscreen {
16 | position: fixed;
17 |
18 | .el-loading-spinner {
19 | margin-top: calc(- var(--loading-fullscreen-spinner-size) / 2);
20 |
21 | .circular {
22 | width: var(--loading-fullscreen-spinner-size);
23 | }
24 | }
25 | }
26 | }
27 |
28 | @b loading-spinner {
29 | top: 50%;
30 | margin-top: calc(- var(--loading-spinner-size) / 2);
31 | width: 100%;
32 | text-align: center;
33 | position: absolute;
34 |
35 | .el-loading-text {
36 | color: var(--color-primary);
37 | margin: 3px 0;
38 | font-size: 14px;
39 | }
40 |
41 | .circular {
42 | width: var(--loading-spinner-size);
43 | animation: rotate 2s linear infinite;
44 | }
45 |
46 | .path {
47 | animation: dash 1.5s ease-in-out infinite;
48 | stroke-dasharray: 1, 100;
49 | stroke-dashoffset: 0;
50 | stroke-width: 2;
51 | stroke: var(--color-primary);
52 | stroke-linecap: round;
53 | }
54 | }
55 | }
56 |
57 | @keyframes rotate {
58 | 100% {
59 | transform: rotate(360deg);
60 | }
61 | }
62 |
63 | @keyframes dash {
64 | 0% {
65 | stroke-dasharray: 1, 100;
66 | stroke-dashoffset: 0;
67 | }
68 | 50% {
69 | stroke-dasharray: 45, 100;
70 | stroke-dashoffset: -35px;
71 | }
72 | 100% {
73 | stroke-dasharray: 45, 100;
74 | stroke-dashoffset: -124px;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/packages/tooltip/src/main.vue:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 |
75 |
--------------------------------------------------------------------------------
/packages/message/assets/success.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/theme-default/src/date-picker/date-table.css:
--------------------------------------------------------------------------------
1 | @import "../common/var.css";
2 |
3 | @component-namespace el {
4 | @b date-table {
5 | font-size: 12px;
6 | min-width: 224px;
7 | user-select: none;
8 |
9 | @when week-mode {
10 | .el-date-table__row {
11 | &:hover {
12 | background-color: var(--datepicker-cell-hover-color);
13 | }
14 |
15 | &.current {
16 | background-color: var(--datepicker-inrange-color);
17 | }
18 | }
19 | }
20 |
21 | td {
22 | width: 32px;
23 | height: 32px;
24 | box-sizing: border-box;
25 | text-align: center;
26 | cursor: pointer;
27 |
28 | &.next-month,
29 | &.prev-month {
30 | color: var(--datepicker-off-color);
31 | }
32 |
33 | &.today {
34 | color: var(--datepicker-text-hover-color);
35 | }
36 |
37 | &.available:hover {
38 | background-color: var(--datepicker-cell-hover-color);
39 | }
40 |
41 | &.in-range {
42 | background-color: var(--datepicker-inrange-color);
43 | &:hover {
44 | background-color: var(--datepicker-inrange-hover-color);
45 | }
46 | }
47 |
48 | &.current,
49 | &.start-date,
50 | &.end-date {
51 | background-color: var(--datepicker-active-color) !important;
52 | color: #fff;
53 | }
54 |
55 | &.disabled {
56 | background-color: #f4f4f4;
57 | opacity: 1;
58 | cursor: not-allowed;
59 | color: #ccc;
60 | }
61 |
62 | &.week {
63 | font-size: 80%;
64 | color: var(--datepicker-header-color);
65 | }
66 | }
67 |
68 | th {
69 | padding: 5px;
70 | color: var(--datepicker-header-color);
71 | font-weight: 400;
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/packages/theme-default/src/core/option.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | @import "../common/var.css";
3 |
4 | @component-namespace element {
5 |
6 | @b option {
7 | box-sizing: border-box;
8 | color: var(--dropdown-color);
9 | cursor: pointer;
10 | display: block;
11 | font-size: var(--dropdown-font-size);
12 | padding: 9px;
13 |
14 | @e remark {
15 | color: var(--dropdown-option-pinyin-color);
16 | float: right;
17 | }
18 |
19 | @m arrow {
20 |
21 | &:not(.is-last)::after {
22 | border-left: 1px solid var(--dropdown-border-color);
23 | border-top: 1px solid var(--dropdown-border-color);
24 | content: " ";
25 | height: 4px;
26 | margin-top: 6px;
27 | position: absolute;
28 | right: 12px;
29 | transform: rotate(135deg);
30 | width: 4px;
31 | }
32 | }
33 |
34 | @when disabled {
35 | background-color: transparent;
36 | color: var(--dropdown-option-color-disabled);
37 | cursor: not-allowed;
38 | }
39 |
40 | &:hover,
41 | &.is-hover {
42 | background-color: var(--dropdown-option-fill-hover);
43 | color: var(--dropdown-option-color-hover);
44 | }
45 |
46 | @when selected {
47 | background-color: var(--dropdown-option-fill-active);
48 | color: var(--dropdown-option-color-active);
49 | }
50 | }
51 |
52 | @b optiongroup {
53 | list-style: none;
54 | padding-left: 0;
55 |
56 | & .element-option {
57 | padding-left: 21px;
58 | }
59 |
60 | @e title {
61 | box-sizing: border-box;
62 | color: var(--dropdown-group-color);
63 | display: inline-block;
64 | font-size: var(--dropdown-font-size);
65 | padding: 8px;
66 |
67 | &:hover {
68 | background-color: inherit;
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Element UI Contributing Guide
2 |
3 | Hi! 首先感谢你使用 Element UI。
4 |
5 | Element UI 是一套为开发者、设计师和产品经理准备的开源组件库,旨在快速搭建页面。它基于 Vue 2.0 开发,并提供了配套的设计资源,充分满足可定制化的需求。
6 |
7 | Element UI 的成长离不开大家的支持,如果你愿意为 Element UI 贡献代码或提供建议,请阅读以下内容。
8 |
9 | ## Issue 规范
10 | - issue 仅用于提交 Bug 或 Feature 以及设计相关的内容,其它内容可能会被直接关闭。如果你在使用时产生了疑问,请到 Slack 或 [Gitter](https://gitter.im/ElemeFE/element) 里咨询。
11 |
12 | - 在提交 issue 之前,请搜索相关内容是否已被提出。
13 |
14 | - 请说明 Element UI 和 Vue 的版本号,并提供操作系统和浏览器信息。推荐使用 [JSFiddle](https://jsfiddle.net/) 生成在线 demo,这能够更直观地重现问题。
15 |
16 | ## Pull Request 规范
17 | - 请先 fork 一份到自己的项目下,不要直接在仓库下建分支。
18 |
19 | - commit 信息要以`[组件名]: 描述信息` 的形式填写,例如 `Button: fix xxx bug`。
20 |
21 | - **不要提交** `lib` 里面打包的文件。
22 |
23 | - 执行 `npm run dist` 后可以正确打包文件。
24 |
25 | - 为了兼容性以及最终打包的文件体积考虑,我们的 babel 只引入了 `preset-2015`,所以不建议使用 ES2015 的 API,例如 `Array.prototype.find`、`Object.assign`等。如果有需要,请引入第三方的 polyfill。
26 |
27 | - 提交 PR 前请 rebase,确保 commit 记录的整洁。
28 |
29 | - 确保 PR 是提交到 `dev` 分支,而不是 `master` 分支。
30 |
31 | - 如果是修复 bug,请在 PR 中给出描述信息。
32 |
33 | - 合并代码需要两名维护人员参与:一人进行 review 后 approve,另一人再次 review,通过后即可合并。
34 |
35 | ## 开发环境搭建
36 | 首先你需要 Node.js 4+ 和 NPM 3+
37 | ```shell
38 | git clone git@github.com:ElemeFE/element.git
39 | npm run dev
40 |
41 | # open http://localhost:8085
42 | ```
43 |
44 | 如果国内用户觉得安装慢可以使用 [yarn](https://github.com/yarnpkg/yarn) 搭配 taobao registry
45 | ```shell
46 | npm i yarn -g
47 | yarn config set registry https://registry.npm.taobao.org
48 | yarn
49 | npm run dev
50 |
51 | # open http://localhost:8085
52 | ```
53 |
54 | To build:
55 |
56 | ```shell
57 | npm run dist
58 | ```
59 |
60 | ## 组件开发规范
61 | - 通过 `npm run new` 创建组件目录结构,包含测试代码、入口文件、cooking 配置、package.json、文档
62 | - 如果包含父子组件,需要更改目录结构,参考 `Button`
63 | - 组件内如果依赖了其他组件,需要在当前组件内引入,参考 `Select`
64 |
65 | ## 代码规范
66 | 遵循饿了么前端的 [ESLint](https://github.com/ElemeFE/eslint-config-elemefe) 即可
67 |
--------------------------------------------------------------------------------
/packages/theme-default/src/message.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | @import "./common/var.css";
3 |
4 | @component-namespace el {
5 |
6 | @b message {
7 | box-shadow: var(--message-shadow);
8 | min-width: var(--message-min-width);
9 | padding: var(--message-padding);
10 | box-sizing: border-box;
11 | border-radius: var(--border-radius-small);
12 | position: fixed;
13 | left: 50%;
14 | top: 20px;
15 | transform: translateX(-50%);
16 | background-color: #fff;
17 | transition: opacity 0.3s, transform .4s;
18 | overflow: hidden;
19 |
20 | @e group {
21 | margin-left: 38px;
22 | position: relative;
23 |
24 | & p {
25 | font-size: var(--font-size-base);
26 | line-height: 20px;
27 | margin: 0 34px 0 0;
28 | white-space: nowrap;
29 | color: var(--message-content-color);
30 | text-align: justify;
31 | }
32 | }
33 |
34 | @e icon {
35 | size: 40px;
36 | position: absolute;
37 | left: 0;
38 | top: 0;
39 | }
40 |
41 | @e closeBtn {
42 | position: absolute 3px 0 * *;
43 | cursor: pointer;
44 | color: var(--message-close-color);
45 | font-size: var(--font-size-base);
46 |
47 | &:hover {
48 | color: var(--message-close-hover-color);
49 | }
50 | }
51 |
52 | & .el-icon-circle-check {
53 | color: var(--message-success-color);
54 | }
55 |
56 | & .el-icon-circle-cross {
57 | color: var(--message-danger-color);
58 | }
59 |
60 | & .el-icon-information {
61 | color: var(--message-info-color);
62 | }
63 |
64 | & .el-icon-warning {
65 | color: var(--message-warning-color);
66 | }
67 | }
68 |
69 | .el-message-fade-enter,
70 | .el-message-fade-leave-active {
71 | opacity: 0;
72 | transform: translate(-50%, -100%);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/examples/pages/template/guide.tpl:
--------------------------------------------------------------------------------
1 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
83 |
--------------------------------------------------------------------------------
/packages/theme-default/src/date-picker/time-picker.css:
--------------------------------------------------------------------------------
1 | @import "../common/var.css";
2 |
3 | @component-namespace el {
4 | @b time-panel {
5 | margin: 5px 0;
6 | border: solid 1px #d3dce6;
7 | background-color: #fff;
8 | box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px 0 rgba(0, 0, 0, .04);
9 | border-radius: 2px;
10 | position: absolute;
11 | width: 180px;
12 | left: 0;
13 | z-index: 1000;
14 | user-select: none;
15 |
16 | @e content {
17 | font-size: 0;
18 | display: flex;
19 | position: relative;
20 | overflow: hidden;
21 |
22 | &::after, &::before {
23 | content: ":";
24 | top: 50%;
25 | color: #fff;
26 | position: absolute;
27 | font-size: 14px;
28 | margin-top: -15px;
29 | line-height: 16px;
30 | background-color: #20a0ff;
31 | height: 32px;
32 | z-index: -1;
33 | left: 0;
34 | right: 0;
35 | box-sizing: border-box;
36 | padding-top: 6px;
37 | text-align: left;
38 | }
39 |
40 | &::after {
41 | left: calc(100%/3*2);
42 | margin-left: -2px;
43 | }
44 |
45 | &::before {
46 | padding-left: calc(100%/3);
47 | margin-right: -2px;
48 | }
49 | }
50 |
51 | @e footer {
52 | border-top: 1px solid var(--datepicker-inner-border-color);
53 | padding: 4px;
54 | height: 36px;
55 | line-height: 25px;
56 | text-align: right;
57 | box-sizing: border-box;
58 | }
59 |
60 | @e btn {
61 | border: none;
62 | line-height: 28px;
63 | padding: 0 5px;
64 | margin: 0 5px;
65 | cursor: pointer;
66 | background-color: transparent;
67 | outline: none;
68 | font-size: 12px;
69 | color: #8492a6;
70 |
71 | &.confirm {
72 | font-weight: 800;
73 | color: #20a0ff;
74 | }
75 | }
76 |
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/test/unit/specs/message.spec.js:
--------------------------------------------------------------------------------
1 | import { triggerEvent } from '../util';
2 | import Message from 'packages/message';
3 |
4 | describe('Message', () => {
5 | afterEach(() => {
6 | const el = document.querySelector('.el-message');
7 | if (!el) return;
8 | if (el.parentNode) {
9 | el.parentNode.removeChild(el);
10 | }
11 | if (el.__vue__) {
12 | el.__vue__.$destroy();
13 | }
14 | });
15 |
16 | it('automatically close', done => {
17 | Message({
18 | message: '灰风',
19 | duration: 500
20 | });
21 | const message = document.querySelector('.el-message__group').childNodes[0];
22 | expect(document.querySelector('.el-message')).to.exist;
23 | expect(message.textContent).to.equal('灰风');
24 | setTimeout(() => {
25 | expect(document.querySelector('.el-message')).to.not.exist;
26 | done();
27 | }, 1000);
28 | });
29 |
30 | it('manually close', done => {
31 | Message({
32 | message: '夏天',
33 | showClose: true
34 | });
35 | setTimeout(() => {
36 | document.querySelector('.el-message__closeBtn').click();
37 | setTimeout(() => {
38 | expect(document.querySelector('.el-message')).to.not.exist;
39 | done();
40 | }, 500);
41 | }, 500);
42 | });
43 |
44 | it('create', () => {
45 | Message('娜梅莉亚');
46 | expect(document.querySelector('.el-message')).to.exist;
47 | });
48 |
49 | it('invoke with type', () => {
50 | Message.success('毛毛狗');
51 | expect(document.querySelector('.el-message').__vue__.type).to.equal('success');
52 | });
53 |
54 | it('reset timer', done => {
55 | Message({
56 | message: '白灵',
57 | duration: 1000
58 | });
59 | setTimeout(() => {
60 | triggerEvent(document.querySelector('.el-message'), 'mouseenter');
61 | setTimeout(() => {
62 | expect(document.querySelector('.el-message')).to.exist;
63 | done();
64 | }, 700);
65 | }, 500);
66 | });
67 | });
68 |
--------------------------------------------------------------------------------
/packages/theme-default/src/table-column.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | @import "./checkbox.css";
3 | @import "./tag.css";
4 | @import "./common/var.css";
5 |
6 | @component-namespace el {
7 | @b table-filter {
8 | border: solid 1px #d3dce6;
9 | border-radius: 2px;
10 | background-color: #fff;
11 | box-shadow: var(--dropdown-menu-box-shadow);
12 | box-sizing: border-box;
13 | margin: 2px 0;
14 |
15 | /** used for dropdown mode */
16 | @e list {
17 | padding: 5px 0;
18 | margin: 0;
19 | list-style: none;
20 | min-width: 100px;
21 | }
22 |
23 | @e list-item {
24 | line-height: 36px;
25 | padding: 0 10px;
26 | cursor: pointer;
27 | font-size: var(--font-size-base);
28 |
29 | &:hover {
30 | background-color: var(--dropdown-menuItem-hover-fill);
31 | color: var(--dropdown-menuItem-hover-color);
32 | }
33 |
34 | @when active {
35 | background-color: #20a0ff;
36 | color: #fff;
37 | }
38 | }
39 |
40 | @e content {
41 | min-width: 100px;
42 | }
43 |
44 | @e bottom {
45 | border-top: 1px solid #d3dce6;
46 | padding: 8px;
47 |
48 | button {
49 | background: transparent;
50 | border: none;
51 | color: #8492a6;
52 | cursor: pointer;
53 | font-size: var(--font-size-base);
54 | padding: 0 3px;
55 |
56 | &:hover {
57 | color: #20a0ff;
58 | }
59 |
60 | &:focus {
61 | outline: none;
62 | }
63 |
64 | &.is-disabled {
65 | color: #c0ccda;
66 | cursor: not-allowed;
67 | }
68 | }
69 | }
70 |
71 | @e checkbox-group {
72 | padding: 10px;
73 |
74 | .el-checkbox {
75 | display: block;
76 | margin-bottom: 8px;
77 | margin-left: 5px;
78 | }
79 |
80 | .el-checkbox:last-child {
81 | margin-bottom: 0;
82 | }
83 | }
84 | }
85 | }
--------------------------------------------------------------------------------
/src/locale/lang/zh-CN.js:
--------------------------------------------------------------------------------
1 | export default {
2 | el: {
3 | datepicker: {
4 | now: '此刻',
5 | today: '今天',
6 | cancel: '取消',
7 | clear: '清空',
8 | confirm: '确定',
9 | selectDate: '选择日期',
10 | selectTime: '选择时间',
11 | startDate: '开始日期',
12 | startTime: '开始时间',
13 | endDate: '结束日期',
14 | endTime: '结束时间',
15 | year: '年',
16 | month1: '1 月',
17 | month2: '2 月',
18 | month3: '3 月',
19 | month4: '4 月',
20 | month5: '5 月',
21 | month6: '6 月',
22 | month7: '7 月',
23 | month8: '8 月',
24 | month9: '9 月',
25 | month10: '10 月',
26 | month11: '11 月',
27 | month12: '12 月',
28 | // week: '周次',
29 | weeks: {
30 | sun: '日',
31 | mon: '一',
32 | tue: '二',
33 | wed: '三',
34 | thu: '四',
35 | fri: '五',
36 | sat: '六'
37 | },
38 | months: {
39 | jan: '一月',
40 | feb: '二月',
41 | mar: '三月',
42 | apr: '四月',
43 | may: '五月',
44 | jun: '六月',
45 | jul: '七月',
46 | aug: '八月',
47 | sep: '九月',
48 | oct: '十月',
49 | nov: '十一月',
50 | dec: '十二月'
51 | }
52 | },
53 | select: {
54 | loading: '加载中',
55 | noMatch: '无匹配数据',
56 | noData: '无数据',
57 | placeholder: '请选择'
58 | },
59 | pagination: {
60 | goto: '前往',
61 | pagesize: '条/页',
62 | total: '共 {total} 条',
63 | pageClassifier: '页'
64 | },
65 | messagebox: {
66 | title: '提示',
67 | confirm: '确定',
68 | cancel: '取消',
69 | error: '输入的数据不合法!'
70 | },
71 | upload: {
72 | delete: '删除',
73 | preview: '查看图片',
74 | continue: '继续上传'
75 | },
76 | table: {
77 | emptyText: '暂无数据',
78 | confirmFilter: '筛选',
79 | resetFilter: '重置',
80 | clearFilter: '全部'
81 | },
82 | tree: {
83 | emptyText: '暂无数据'
84 | }
85 | }
86 | };
87 |
--------------------------------------------------------------------------------
/src/locale/lang/zh-TW.js:
--------------------------------------------------------------------------------
1 | export default {
2 | el: {
3 | datepicker: {
4 | now: '現在',
5 | today: '今天',
6 | cancel: '取消',
7 | clear: '清空',
8 | confirm: '確認',
9 | selectDate: '選擇日期',
10 | selectTime: '選擇時間',
11 | startDate: '開始日期',
12 | startTime: '開始時間',
13 | endDate: '結束日期',
14 | endTime: '結束時間',
15 | year: '年',
16 | month1: '1 月',
17 | month2: '2 月',
18 | month3: '3 月',
19 | month4: '4 月',
20 | month5: '5 月',
21 | month6: '6 月',
22 | month7: '7 月',
23 | month8: '8 月',
24 | month9: '9 月',
25 | month10: '10 月',
26 | month11: '11 月',
27 | month12: '12 月',
28 | // week: '周次',
29 | weeks: {
30 | sun: '日',
31 | mon: '一',
32 | tue: '二',
33 | wed: '三',
34 | thu: '四',
35 | fri: '五',
36 | sat: '六'
37 | },
38 | months: {
39 | jan: '一月',
40 | feb: '二月',
41 | mar: '三月',
42 | apr: '四月',
43 | may: '五月',
44 | jun: '六月',
45 | jul: '七月',
46 | aug: '八月',
47 | sep: '九月',
48 | oct: '十月',
49 | nov: '十一月',
50 | dec: '十二月'
51 | }
52 | },
53 | select: {
54 | loading: '加載中',
55 | noMatch: '無匹配資料',
56 | noData: '無資料',
57 | placeholder: '請選擇'
58 | },
59 | pagination: {
60 | goto: '前往',
61 | pagesize: '項/頁',
62 | total: '共 {total} 項',
63 | pageClassifier: '頁'
64 | },
65 | messagebox: {
66 | title: '提示',
67 | confirm: '確定',
68 | cancel: '取消',
69 | error: '輸入的資料不符規定!'
70 | },
71 | upload: {
72 | delete: '刪除',
73 | preview: '查看圖片',
74 | continue: '繼續上傳'
75 | },
76 | table: {
77 | emptyText: '暫無資料',
78 | confirmFilter: '篩選',
79 | resetFilter: '重置',
80 | clearFilter: '全部'
81 | },
82 | tree: {
83 | emptyText: '暫無資料'
84 | }
85 | }
86 | };
87 |
--------------------------------------------------------------------------------
/packages/theme-default/src/form.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | @import "./common/var.css";
3 |
4 | @component-namespace el {
5 | @b form {
6 | @m label-left {
7 | & .el-form-item__label {
8 | text-align: left;
9 | }
10 | }
11 | @m label-top {
12 | & .el-form-item__label {
13 | float: none;
14 | display: inline-block;
15 | padding: 0 0 10px 0;
16 | }
17 | }
18 | @m inline {
19 | & .el-form-item {
20 | display: inline-block;
21 | margin-right: 10px;
22 | vertical-align: top;
23 | }
24 | }
25 | }
26 | @b form-item {
27 | margin-bottom: 22px;
28 | @utils-clearfix;
29 |
30 | & .el-form-item {
31 | margin-bottom: 0;
32 |
33 | & .el-form-item__content {
34 | margin-left: 0 !important;
35 | }
36 | }
37 |
38 | @e label {
39 | text-align: right;
40 | vertical-align: middle;
41 | float: left;
42 | font-size: 14px;
43 | color: #5e6d82;
44 | line-height: 1;
45 | padding: 11px 12px 11px 0;
46 | box-sizing: border-box;
47 | }
48 | @e content {
49 | line-height: 36px;
50 | position: relative;
51 | font-size: 14px;
52 | @utils-clearfix;
53 | }
54 | @e error {
55 | color: #ff4949;
56 | font-size: 12px;
57 | line-height: 1;
58 | padding-top: 4px;
59 | position: absolute;
60 | top: 100%;
61 | left: 0;
62 | }
63 | & .el-button + .el-button,
64 | & .el-checkbox + .el-checkbox,
65 | & .el-radio + .el-radio {
66 | margin-left: 10px;
67 | }
68 |
69 | @when required {
70 | .el-form-item__label:before {
71 | content: '*';
72 | color: var(--color-danger);
73 | margin-right: 4px;
74 | }
75 | }
76 |
77 | @when error {
78 | & .el-input__inner,
79 | & .el-textarea__inner {
80 | border-color: var(--color-danger);
81 | }
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/packages/form/src/form.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
76 |
--------------------------------------------------------------------------------
/src/locale/lang/ko.js:
--------------------------------------------------------------------------------
1 | export default {
2 | el: {
3 | datepicker: {
4 | now: '지금',
5 | today: '오늘',
6 | cancel: '취소',
7 | clear: '초기화',
8 | confirm: '확인',
9 | selectDate: '날짜 선택',
10 | selectTime: '시간 선택',
11 | startDate: '시작 날짜',
12 | startTime: '시작 시간',
13 | endDate: '종료 날짜',
14 | endTime: '종료 시간',
15 | year: '년',
16 | month1: '1월',
17 | month2: '2월',
18 | month3: '3월',
19 | month4: '4월',
20 | month5: '5월',
21 | month6: '6월',
22 | month7: '7월',
23 | month8: '8월',
24 | month9: '9월',
25 | month10: '10월',
26 | month11: '11월',
27 | month12: '12월',
28 | // week: 'week',
29 | weeks: {
30 | sun: '일',
31 | mon: '월',
32 | tue: '화',
33 | wed: '수',
34 | thu: '목',
35 | fri: '금',
36 | sat: '토'
37 | },
38 | months: {
39 | jan: '1월',
40 | feb: '2월',
41 | mar: '3월',
42 | apr: '4월',
43 | may: '5월',
44 | jun: '6월',
45 | jul: '7월',
46 | aug: '8월',
47 | sep: '9월',
48 | oct: '10월',
49 | nov: '11월',
50 | dec: '12월'
51 | }
52 | },
53 | select: {
54 | loading: '불러오는 중',
55 | noMatch: '맞는 데이터가 없습니다',
56 | noData: '데이터 없음',
57 | placeholder: '선택'
58 | },
59 | pagination: {
60 | goto: '이동',
61 | pagesize: '/page',
62 | total: '총 {total}',
63 | pageClassifier: ''
64 | },
65 | messagebox: {
66 | title: '메시지',
67 | confirm: '확인',
68 | cancel: '취소',
69 | error: '올바르지 않은 입력'
70 | },
71 | upload: {
72 | delete: '삭제',
73 | preview: '미리보기',
74 | continue: '계속하기'
75 | },
76 | table: {
77 | emptyText: '데이터 없음',
78 | confirmFilter: '확인',
79 | resetFilter: '초기화',
80 | clearFilter: '전체'
81 | },
82 | tree: {
83 | emptyText: '데이터 없음'
84 | }
85 | }
86 | };
87 |
--------------------------------------------------------------------------------