diff --git a/components.json b/components.json index 76515c414..fcee75b73 100644 --- a/components.json +++ b/components.json @@ -87,5 +87,6 @@ "empty": "./packages/empty/index.js", "descriptions": "./packages/descriptions/index.js", "descriptions-item": "./packages/descriptions-item/index.js", - "result": "./packages/result/index.js" + "result": "./packages/result/index.js", + "tree-select": "./packages/tree-select/index.js" } diff --git a/examples/docs/en-US/tree-select.md b/examples/docs/en-US/tree-select.md new file mode 100644 index 000000000..8eefb8160 --- /dev/null +++ b/examples/docs/en-US/tree-select.md @@ -0,0 +1,817 @@ +## TreeSelect + +The tree selector of the dropdown menu, it combines the functions of components el-tree and el-select. + +### Basic usage + +:::demo +```html + + + +``` +::: + +### Select any level + +When using the check-strictly=true attribute, any node can be checked, otherwise only leaf nodes are supported. + +:::demo +```html + + +

click text to select

+ +
+ +

show-checkbox, only click checkbox to select

+ +
+
+ + +``` +::: + +### Multiple Selection + +Multiple selection using clicks or checkbox. +:::demo +```html + + +

click text to select

+ +
+ +

show checkbox

+ +
+ +

show checkbox with `check-strictly`

+ +
+
+ + +``` +::: + +### Clearable + +You can clear tree-select using a clear icon. + +Set clearable attribute for el-tree-select and a clear icon will appear. + +:::demo +```html + + + + + + + + + + +``` +::: + +### Disabled Selection + +Disable options using the disabled field. + +:::demo +```html + + + +``` +::: + +### Filterable + +Use keyword filtering or custom filtering methods. filterMethod can custom filter method for data. + +:::demo +```html + + +

filterable

+ +
+ +

filterMethod

+ +
+
+ + +``` +::: + +### LazyLoad + +Lazy loading of tree nodes, suitable for large data lists. + +:::demo +```html + + + +``` +::: + + +### Attributes + +| Name | Description | Type | Accepted Values | Default| +| --------------------- | ------------------------------------------------------------------------------------------------- | --------------------------- | ----------------- | ------ | +| value / v-model | binding value | boolean / string / number | — | — | +| data | tree data | array | — | — | +| multiple | whether multiple-select is activated | boolean | — | false | +| disabled | whether Select is disabled | boolean | — | false | +| size | size of Input | string | medium/small/mini | — | +| clearable | whether select can be cleared | boolean | — | false | +| collapse-tags | whether to collapse tags to a text when multiple selecting | boolean | — | false | +| name | the name attribute of Tree-select input | string | — | — | +| placeholder | placeholder | string | — | 请选择 | +| filterable | whether Tree-select is filterable | boolean | — | false | +| empty-text | displayed text when there is no data | String | — | — | +| node-key | unique identity key name for nodes, its value should be unique across the whole tree | String | — | — | +| props | configuration options, see the following table | object | — | — | +| render-after-expand | whether to render child nodes only after a parent node is expanded for the first time | boolean | — | true | +| load | method for loading subtree data, only works when lazy is true | function(node, resolve) | — | — | +| default-expand-all | whether to expand all nodes by default | boolean | — | false | +| auto-expand-parent | whether to expand father node when a child node is expanded | boolean | — | true | +| show-checkbox | whether node is selectable | boolean | — | false | +| check-strictly | whether checked state of a node not affects its father and child nodes when show-checkbox is true | boolean | — | false | +| filter-method | custom filter method | Function(value, data, node) | — | — | +| accordion | whether only one node among the same level can be expanded at one time | boolean | — | false | +| indent | horizontal indentation of nodes in adjacent levels in pixels | number | — | 16 | +| lazy | whether to lazy load leaf node, used with load attribute | boolean | — | false | +| popper-append-to-body | whether to append the popper menu to body. If the positioning of the popper is wrong, you can try to set this prop to false | boolean | - | true | + +### props + +| Attribute| Description | Type | Accepted Values | Default | +| -------- | ----------------------------------------------------------------------------- | ----------------------------- | -------------------| ----------------| +| label | specify which key of node object is used as the node's label | string, function(data, node) | — | — | +| children | specify which node object is used as the node's subtree | string | — | — | +| disabled | specify which key of node object represents if node's checkbox is disabled | boolean, function(data, node) | — | — | +| isLeaf | specify whether the node is a leaf node, only works when lazy load is enabled | boolean, function(data, node) | — | — | + +### Events + +| Method | Description | Parameters | +| -------------- | --------------------------------------------------------------- | ------------------------------------------ | +| change | triggers when the selected value changes | current selected value | +| visible-change | triggers when the dropdown appears/disappears | true when it appears, and false otherwise | +| remove-tag | triggers when a tag is removed in multiple mode | removed tag value | +| clear | triggers when the clear icon is clicked in a clearable Select | — | +| blur | triggers when Input blurs | (event: Event) | +| focus | triggers when Input focuses | (event: Event) | + diff --git a/examples/docs/es/tree-select.md b/examples/docs/es/tree-select.md new file mode 100644 index 000000000..c364c8d02 --- /dev/null +++ b/examples/docs/es/tree-select.md @@ -0,0 +1 @@ +## TreeSelect diff --git a/examples/docs/fr-FR/tree-select.md b/examples/docs/fr-FR/tree-select.md new file mode 100644 index 000000000..c364c8d02 --- /dev/null +++ b/examples/docs/fr-FR/tree-select.md @@ -0,0 +1 @@ +## TreeSelect diff --git a/examples/docs/zh-CN/tree-select.md b/examples/docs/zh-CN/tree-select.md new file mode 100644 index 000000000..a8bb45fb6 --- /dev/null +++ b/examples/docs/zh-CN/tree-select.md @@ -0,0 +1,821 @@ +## TreeSelect 树形选择器 + +含有下拉菜单的树形选择器,结合了 el-tree 和 el-select 两个组件的功能。 + +### 基础单选 + +:::demo +```html + + + +``` +::: + +### 选择任意级别 + +当属性 check-strictly=true 时,任何节点都可以被选择,否则只有子节点可被选择。 + +:::demo +```html + + +

click text to select

+ +
+ +

show-checkbox, only click checkbox to select

+ +
+
+ + +``` +::: + +### 多选 + +通过点击或复选框选择多个选项。 +:::demo +```html + + +

click text to select

+ +
+ +

show checkbox

+ +
+ +

show checkbox with `check-strictly`

+ +
+
+ + +``` +::: + +### 可清空选项 + +包含清空按钮,可将选择器清空为初始状态,为`el-tree-select`设置`clearable`属性,则可将选择器清空 + +:::demo +```html + + + + + + + + + + +``` +::: + +### 禁用选项 + +使用 disabled 字段禁用选项。 + +:::demo +```html + + + +``` +::: + +### 可筛选 + +使用关键字筛选或自定义筛选方法。 filterMethod可以自定义数据筛选的方法。 + +:::demo +```html + + +

filterable

+ +
+ +

filterMethod

+ +
+
+ + +``` +::: + +### 懒加载 + +树节点懒加载,更加适合于数据量大的列表。 + +:::demo +```html + + + +``` +::: + + +### Attributes + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| --------------------- | ----------------------------------------------------------------------------------------------- | --------------------------- | ----------------- | ------ | +| value / v-model | 绑定值 | boolean / string / number | — | — | +| data | 展示数据 | array | — | — | +| multiple | 是否多选 | boolean | — | false | +| disabled | 是否禁用 | boolean | — | false | +| size | 输入框尺寸 | string | medium/small/mini | — | +| clearable | 是否可以清空选项 | boolean | — | false | +| collapse-tags | 多选时是否将选中值按文字的形式展示 | boolean | — | false | +| name | tree-select input 的 name 属性 | string | — | — | +| placeholder | 占位符 | string | — | 请选择 | +| filterable | 是否可搜索 | boolean | — | false | +| empty-text | 内容为空的时候展示的文本 | String | — | — | +| node-key | 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 | String | — | — | +| props | 配置选项,具体看下表 | object | — | — | +| render-after-expand | 是否在第一次展开某个树节点后才渲染其子节点 | boolean | — | true | +| load | 加载子树数据的方法,仅当 lazy 属性为 true 时生效 | function(node, resolve) | — | — | +| default-expand-all | 是否默认展开所有节点 | boolean | — | false | +| auto-expand-parent | 展开子节点的时候是否自动展开父节点 | boolean | — | true | +| show-checkbox | 节点是否可被选择 | boolean | — | false | +| check-strictly | 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 false | boolean | — | false | +| filter-method | 对树节点进行筛选时执行的方法,返回 true 表示这个节点可以显示,返回 false 则表示这个节点会被隐藏 | Function(value, data, node) | — | — | +| accordion | 是否每次只打开一个同级树节点展开 | boolean | — | false | +| indent | 相邻级节点间的水平缩进,单位为像素 | number | — | 16 | +| lazy | 是否懒加载子节点,需与 load 方法结合使用 | boolean | — | false | +| popper-append-to-body | 是否将弹出框插入至 body 元素。在弹出框的定位出现问题时,可将该属性设置为 false | boolean | - | true | + +### props + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| -------- | -------------------------------------------------------- | ----------------------------- | ------ | ------ | +| label | 指定节点标签为节点对象的某个属性值 | string, function(data, node) | — | — | +| children | 指定子树为节点对象的某个属性值 | string | — | — | +| disabled | 指定节点选择框是否禁用为节点对象的某个属性值 | boolean, function(data, node) | — | — | +| isLeaf | 指定节点是否为叶子节点,仅在指定了 lazy 属性的情况下生效 | boolean, function(data, node) | — | — | + +### Events + +| 事件名称 | 说明 | 回调参数 | +| -------------- | ---------------------------------------- | ----------------------------- | +| change | 选中值发生变化时触发 | 目前的选中值 | +| visible-change | 下拉框出现/隐藏时触发 | 出现则为 true,隐藏则为 false | +| remove-tag | 多选模式下移除 tag 时触发 | 移除的 tag 值 | +| clear | 可清空的单选模式下用户点击清空按钮时触发 | — | +| blur | 当 input 失去焦点时触发 | (event: Event) | +| focus | 当 input 获得焦点时触发 | (event: Event) | + +### Methods + +| 方法名 | 说明 | 参数 | +| ------ | ------------------------------- | ---- | +| focus | 使 input 获取焦点 | - | +| blur | 使 input 失去焦点,并隐藏下拉框 | - | diff --git a/examples/nav.config.json b/examples/nav.config.json index f648024cd..a2451f1ef 100644 --- a/examples/nav.config.json +++ b/examples/nav.config.json @@ -104,6 +104,10 @@ "path": "/cascader", "title": "Cascader 级联选择器" }, + { + "path": "/tree-select", + "title": "TreeSelect 树形选择器" + }, { "path": "/switch", "title": "Switch 开关" @@ -422,6 +426,10 @@ "path": "/cascader", "title": "Cascader" }, + { + "path": "/tree-select", + "title": "TreeSelect" + }, { "path": "/switch", "title": "Switch" @@ -1271,4 +1279,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/packages/theme-chalk/src/common/var.scss b/packages/theme-chalk/src/common/var.scss index ad9a1aaa2..0c11ccbc6 100644 --- a/packages/theme-chalk/src/common/var.scss +++ b/packages/theme-chalk/src/common/var.scss @@ -6,19 +6,20 @@ /* Transition -------------------------- */ -$--all-transition: all .3s cubic-bezier(.645,.045,.355,1) !default; +$--all-transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1) !default; $--fade-transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) !default; $--fade-linear-transition: opacity 200ms linear !default; -$--md-fade-transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) !default; -$--border-transition-base: border-color .2s cubic-bezier(.645,.045,.355,1) !default; -$--color-transition-base: color .2s cubic-bezier(.645,.045,.355,1) !default; +$--md-fade-transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), + opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) !default; +$--border-transition-base: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) !default; +$--color-transition-base: color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) !default; /* Color -------------------------- */ /// color|1|Brand Color|0 -$--color-primary: #409EFF !default; +$--color-primary: #409eff !default; /// color|1|Background Color|4 -$--color-white: #FFFFFF !default; +$--color-white: #ffffff !default; /// color|1|Background Color|4 $--color-black: #000000 !default; $--color-primary-light-1: mix($--color-white, $--color-primary, 10%) !default; /* 53a8ff */ @@ -31,11 +32,11 @@ $--color-primary-light-7: mix($--color-white, $--color-primary, 70%) !default; / $--color-primary-light-8: mix($--color-white, $--color-primary, 80%) !default; /* d9ecff */ $--color-primary-light-9: mix($--color-white, $--color-primary, 90%) !default; /* ecf5ff */ /// color|1|Functional Color|1 -$--color-success: #67C23A !default; +$--color-success: #67c23a !default; /// color|1|Functional Color|1 -$--color-warning: #E6A23C !default; +$--color-warning: #e6a23c !default; /// color|1|Functional Color|1 -$--color-danger: #F56C6C !default; +$--color-danger: #f56c6c !default; /// color|1|Functional Color|1 $--color-info: #909399 !default; @@ -55,19 +56,19 @@ $--color-text-regular: #606266 !default; /// color|1|Font Color|2 $--color-text-secondary: #909399 !default; /// color|1|Font Color|2 -$--color-text-placeholder: #C0C4CC !default; +$--color-text-placeholder: #c0c4cc !default; /// color|1|Border Color|3 -$--border-color-base: #DCDFE6 !default; +$--border-color-base: #dcdfe6 !default; /// color|1|Border Color|3 -$--border-color-light: #E4E7ED !default; +$--border-color-light: #e4e7ed !default; /// color|1|Border Color|3 -$--border-color-lighter: #EBEEF5 !default; +$--border-color-lighter: #ebeef5 !default; /// color|1|Border Color|3 -$--border-color-extra-light: #F2F6FC !default; +$--border-color-extra-light: #f2f6fc !default; // Background /// color|1|Background Color|4 -$--background-color-base: #F5F7FA !default; +$--background-color-base: #f5f7fa !default; /* Link -------------------------- */ @@ -91,9 +92,9 @@ $--border-radius-zero: 0 !default; // Box-shadow /// boxShadow|1|Shadow|1 -$--box-shadow-base: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04) !default; +$--box-shadow-base: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04) !default; // boxShadow|1|Shadow|1 -$--box-shadow-dark: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .12) !default; +$--box-shadow-dark: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.12) !default; /// boxShadow|1|Shadow|1 $--box-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.1) !default; @@ -103,8 +104,8 @@ $--fill-base: $--color-white !default; /* Typography -------------------------- */ -$--font-path: 'fonts' !default; -$--font-display: 'auto' !default; +$--font-path: "fonts" !default; +$--font-display: "auto" !default; /// fontSize|1|Font Size|0 $--font-size-extra-large: 20px !default; /// fontSize|1|Font Size|0 @@ -210,8 +211,6 @@ $--checkbox-button-checked-font-color: $--color-white !default; /// color||Color|0 $--checkbox-button-checked-border-color: $--color-primary !default; - - /* Radio -------------------------- */ /// fontSize||Font|1 @@ -478,8 +477,8 @@ $--cascader-menu-radius: $--border-radius-base !default; $--cascader-menu-border: solid 1px $--border-color-light !default; $--cascader-menu-shadow: $--box-shadow-light !default; $--cascader-node-background-hover: $--background-color-base !default; -$--cascader-node-color-disabled:$--color-text-placeholder !default; -$--cascader-color-empty:$--color-text-placeholder !default; +$--cascader-node-color-disabled: $--color-text-placeholder !default; +$--cascader-color-empty: $--color-text-placeholder !default; $--cascader-tag-background: #f0f2f5; /* Group @@ -589,7 +588,6 @@ $--button-info-background-color: $--color-info !default; $--button-hover-tint-percent: 20% !default; $--button-active-shade-percent: 10% !default; - /* cascader -------------------------- */ $--cascader-height: 200px !default; @@ -637,7 +635,7 @@ $--table-row-hover-background-color: $--background-color-base !default; $--table-current-row-background-color: $--color-primary-light-9 !default; /// color||Color|0 $--table-header-background-color: $--color-white !default; -$--table-fixed-box-shadow: 0 0 10px rgba(0, 0, 0, .12) !default; +$--table-fixed-box-shadow: 0 0 10px rgba(0, 0, 0, 0.12) !default; /* Pagination -------------------------- */ @@ -825,8 +823,8 @@ $--loading-fullscreen-spinner-size: 50px !default; /* Scrollbar --------------------------*/ -$--scrollbar-background-color: rgba($--color-text-secondary, .3) !default; -$--scrollbar-hover-background-color: rgba($--color-text-secondary, .5) !default; +$--scrollbar-background-color: rgba($--color-text-secondary, 0.3) !default; +$--scrollbar-hover-background-color: rgba($--color-text-secondary, 0.5) !default; /* Carousel --------------------------*/ @@ -935,7 +933,7 @@ $--link-info-font-color: $--color-info !default; /// border||Other|4 $--calendar-border: $--table-border !default; /// color||Other|4 -$--calendar-selected-background-color: #F2F8FE !default; +$--calendar-selected-background-color: #f2f8fe !default; $--calendar-cell-width: 85px !default; /* Form @@ -948,7 +946,7 @@ $--form-label-font-size: $--font-size-base !default; /// color||Color|0 $--avatar-font-color: #fff !default; /// color||Color|0 -$--avatar-background-color: #C0C4CC !default; +$--avatar-background-color: #c0c4cc !default; /// fontSize||Font Size|1 $--avatar-text-font-size: 14px !default; /// fontSize||Font Size|1 @@ -983,7 +981,7 @@ $--skeleton-to-color: #e6e6e6 !default; /* Svg --------------- */ -$--svg-monochrome-grey: #DCDDE0 !default; +$--svg-monochrome-grey: #dcdde0 !default; /* Result -------------------------- */ @@ -998,6 +996,23 @@ $--result-success-color: $--color-success !default; $--result-warning-color: $--color-warning !default; $--result-danger-color: $--color-danger !default; +/* Tree-select +-------------------------- */ +$--tree-select-font-size: $--font-size-base !default; +$--tree-select-dropdown-border: solid 1px $--border-color-light !default; +$--tree-select-multiple-input-color: $--color-text-placeholder !default; +$--tree-select-dropdown-background: $--color-white !default; +$--tree-select-option-selected-hover: $--background-color-base !default; +$--tree-select-dropdown-max-height: 272px !default; +$--tree-select-dropdown-padding: 6px 0 !default; +$--tree-select-dropdown-empty-color: #999 !default; +$--tree-select-dropdown-empty-padding: 10px 0 !default; + +$--tree-select-mini-dropdown-max-height: 176px !default; +$--tree-select-small-dropdown-max-height: 216px !default; +$--tree-select-large-dropdown-max-height: 386px !default; + + /* Break-point --------------------------*/ $--sm: 768px !default; @@ -1006,23 +1021,49 @@ $--lg: 1200px !default; $--xl: 1920px !default; $--breakpoints: ( - 'xs' : (max-width: $--sm - 1), - 'sm' : (min-width: $--sm), - 'md' : (min-width: $--md), - 'lg' : (min-width: $--lg), - 'xl' : (min-width: $--xl) + "xs": ( + max-width: $--sm - 1, + ), + "sm": ( + min-width: $--sm, + ), + "md": ( + min-width: $--md, + ), + "lg": ( + min-width: $--lg, + ), + "xl": ( + min-width: $--xl, + ), ); $--breakpoints-spec: ( - 'xs-only' : (max-width: $--sm - 1), - 'sm-and-up' : (min-width: $--sm), - 'sm-only': "(min-width: #{$--sm}) and (max-width: #{$--md - 1})", - 'sm-and-down': (max-width: $--md - 1), - 'md-and-up' : (min-width: $--md), - 'md-only': "(min-width: #{$--md}) and (max-width: #{$--lg - 1})", - 'md-and-down': (max-width: $--lg - 1), - 'lg-and-up' : (min-width: $--lg), - 'lg-only': "(min-width: #{$--lg}) and (max-width: #{$--xl - 1})", - 'lg-and-down': (max-width: $--xl - 1), - 'xl-only' : (min-width: $--xl), + "xs-only": ( + max-width: $--sm - 1, + ), + "sm-and-up": ( + min-width: $--sm, + ), + "sm-only": "(min-width: #{$--sm}) and (max-width: #{$--md - 1})", + "sm-and-down": ( + max-width: $--md - 1, + ), + "md-and-up": ( + min-width: $--md, + ), + "md-only": "(min-width: #{$--md}) and (max-width: #{$--lg - 1})", + "md-and-down": ( + max-width: $--lg - 1, + ), + "lg-and-up": ( + min-width: $--lg, + ), + "lg-only": "(min-width: #{$--lg}) and (max-width: #{$--xl - 1})", + "lg-and-down": ( + max-width: $--xl - 1, + ), + "xl-only": ( + min-width: $--xl, + ), ); diff --git a/packages/theme-chalk/src/index.scss b/packages/theme-chalk/src/index.scss index 00183c6fd..6d292b4c8 100644 --- a/packages/theme-chalk/src/index.scss +++ b/packages/theme-chalk/src/index.scss @@ -85,3 +85,4 @@ @import "./descriptions.scss"; @import "./descriptions-item.scss"; @import "./result.scss"; +@import "./tree-select.scss"; diff --git a/packages/theme-chalk/src/tree-select.scss b/packages/theme-chalk/src/tree-select.scss new file mode 100644 index 000000000..e59234aee --- /dev/null +++ b/packages/theme-chalk/src/tree-select.scss @@ -0,0 +1,165 @@ +@import "mixins/mixins"; +@import "common/var"; + +@include b(tree-select) { + display: inline-block; + position: relative; + width: 240px; + + .el-tree-select__tags > span { + display: contents; + } + + .el-input__inner { + cursor: pointer; + padding-right: 35px; + transition: none; + } + + .el-input { + display: block; + & .el-tree-select__caret { + transition: transform 0.3s; + transform: rotateZ(180deg); + cursor: pointer; + + @include when(reverse) { + transform: rotateZ(0deg); + } + } + + & .el-icon-reload { + animation: spin 1s linear infinite; + } + + &.is-disabled { + & .el-input__inner { + cursor: not-allowed; + } + } + } + + @include e(input) { + border: none; + outline: none; + padding: 0; + margin-left: 15px; + color: $--tree-select-multiple-input-color; + font-size: $--tree-select-font-size; + appearance: none; + height: 28px; + background-color: transparent; + flex-grow: 1; + } + + @include e(tags) { + position: absolute; + line-height: normal; + white-space: normal; + z-index: $--index-normal; + top: 50%; + transform: translateY(-50%); + display: flex; + align-items: center; + flex-wrap: wrap; + } + + .el-tag { + box-sizing: border-box; + border-color: transparent; + margin: 2px 0 2px 6px; + background-color: #f0f2f5; + display: flex; + max-width: 100%; + align-items: center; + + &__close.el-icon-close { + background-color: $--color-text-placeholder; + top: 0; + color: $--color-white; + flex-shrink: 0; + + &:hover { + background-color: $--color-text-secondary; + } + + &::before { + display: block; + transform: translate(0, .5px); + } + } + } +} + +@include b(tree-select-dropdown) { + position: absolute; + z-index: #{$--index-top + 1}; + border-radius: $--border-radius-base; + border: $--tree-select-dropdown-border; + box-sizing: border-box; + background-color: $--tree-select-dropdown-background; + margin: 0 0 5px; + + @include b(tree-select-dropdown__wrap) { + max-height: $--tree-select-dropdown-max-height; + } + + @include b(tree-select-dropdown__list) { + list-style: none; + padding: $--tree-select-dropdown-padding; + margin: 0; + box-sizing: border-box; + .el-tree-node { + &:focus { + > .el-tree-node__content:not(.is-disabled) { + background: $--tree-select-option-selected-hover; + } + } + .el-tree-node__content { + transition: background-color 0.3s; + color: $--color-text-primary; + .el-tree-node__content-text { + font-size: $--tree-select-font-size; + } + &:hover:not(.is-disabled), + &:focus:not(.is-disabled) { + background: $--tree-select-option-selected-hover; + color: $--color-text-primary; + } + > .is-disabled { + cursor: not-allowed; + color: $--color-text-secondary; + } + > .is-selected { + color: $--color-primary; + } + } + } + } + + @include b(tree-select-dropdown__empty) { + padding: $--tree-select-dropdown-empty-padding; + margin: 0; + text-align: center; + color: $--tree-select-dropdown-empty-color; + font-size: $--tree-select-font-size; + } + + @include m(mini) { + @include b(tree-select-dropdown__wrap) { + max-height: $--tree-select-mini-dropdown-max-height; + } + } + + @include m(small) { + @include b(tree-select-dropdown__wrap) { + max-height: $--tree-select-small-dropdown-max-height; + } + } + + @include m(large) { + @include b(tree-select-dropdown__wrap) { + max-height: $--tree-select-large-dropdown-max-height; + } + } +} diff --git a/packages/tree-select/index.js b/packages/tree-select/index.js new file mode 100644 index 000000000..74eb00e69 --- /dev/null +++ b/packages/tree-select/index.js @@ -0,0 +1,8 @@ +import TreeSelect from './src/main'; + +/* istanbul ignore next */ +TreeSelect.install = function(Vue) { + Vue.component(TreeSelect.name, TreeSelect); +}; + +export default TreeSelect; diff --git a/packages/tree-select/src/main.vue b/packages/tree-select/src/main.vue new file mode 100644 index 000000000..0d96c00f7 --- /dev/null +++ b/packages/tree-select/src/main.vue @@ -0,0 +1,771 @@ + + + diff --git a/packages/tree-select/src/tree-select-dropdown.vue b/packages/tree-select/src/tree-select-dropdown.vue new file mode 100644 index 000000000..363936d3f --- /dev/null +++ b/packages/tree-select/src/tree-select-dropdown.vue @@ -0,0 +1,76 @@ + + + diff --git a/packages/tree/src/tree-node.vue b/packages/tree/src/tree-node.vue index 3c922dbe6..d7c071964 100644 --- a/packages/tree/src/tree-node.vue +++ b/packages/tree/src/tree-node.vue @@ -211,7 +211,7 @@ checkedKeys: store.getCheckedKeys(), halfCheckedNodes: store.getHalfCheckedNodes(), halfCheckedKeys: store.getHalfCheckedKeys(), - }); + }, this.node); }); }, diff --git a/packages/tree/src/tree.vue b/packages/tree/src/tree.vue index 5b1c1b4c4..433cde25c 100644 --- a/packages/tree/src/tree.vue +++ b/packages/tree/src/tree.vue @@ -291,6 +291,16 @@ this.treeItems[0] && this.treeItems[0].setAttribute('tabindex', 0); }, + initFocusedItem(direction) { + if (!this.treeItemArray.length) return; + if (direction === 'next') { + this.treeItemArray[0].focus(); + } + if (direction === 'prev') { + this.treeItemArray[this.treeItemArray.length - 1].focus(); + } + }, + handleKeydown(ev) { const currentItem = ev.target; if (currentItem.className.indexOf('el-tree-node') === -1) return; diff --git a/src/index.js b/src/index.js index b5d91bccd..0a7cd1f06 100644 --- a/src/index.js +++ b/src/index.js @@ -89,6 +89,7 @@ import Empty from '../packages/empty/index.js'; import Descriptions from '../packages/descriptions/index.js'; import DescriptionsItem from '../packages/descriptions-item/index.js'; import Result from '../packages/result/index.js'; +import TreeSelect from '../packages/tree-select/index.js'; import locale from 'element-ui/src/locale'; import CollapseTransition from 'element-ui/src/transitions/collapse-transition'; @@ -177,6 +178,7 @@ const components = [ Descriptions, DescriptionsItem, Result, + TreeSelect, CollapseTransition ]; @@ -305,5 +307,6 @@ export default { Empty, Descriptions, DescriptionsItem, - Result + Result, + TreeSelect }; diff --git a/test/unit/specs/tree-select.spec.js b/test/unit/specs/tree-select.spec.js new file mode 100644 index 000000000..ecb2fa061 --- /dev/null +++ b/test/unit/specs/tree-select.spec.js @@ -0,0 +1,95 @@ +import { createTest, createVue, destroyVM } from '../util'; +import TreeSelect from 'packages/tree-select'; + +const data = [ + { + value: 1, + label: '一级 1', + children: [ + { + value: 11, + label: '二级 1-1', + children: [ + { + value: 111, + label: '三级 1-1' + } + ] + } + ] + } +]; + +describe('TreeSelect', () => { + const getTreeSelectVm = (configs = {}, options) => { + ['multiple', 'checkStrictly', 'showCheckbox'].forEach(config => { + configs[config] = configs[config] || false; + }); + const vm = createVue(Object.assign({ + template: ` + + `, + data() { + return { + data, + value: '', + multiple: configs.multiple, + checkStrictly: configs.checkStrictly, + showCheckbox: configs.showCheckbox + }; + } + }, options), true); + return vm; + }; + + let vm; + afterEach(() => { + destroyVM(vm); + }); + + it('create', () => { + vm = createTest(TreeSelect, true); + expect(vm.$el).to.exist; + }); + + it('render dropdown', async() => { + vm = getTreeSelectVm(); + expect(vm.$el.querySelector('.el-tree')).to.exist; + expect(vm.$el.querySelectorAll('.el-tree > .el-tree-node').length).to.equal(1); + expect(vm.$el.querySelectorAll('.el-tree .el-tree-node').length).to.equal(3); + }); + + it('default value', done => { + vm = createVue({ + template: ` +
+ +
+ `, + data() { + return { + data: data, + value: 111 + }; + } + }, true); + setTimeout(() => { + expect(vm.$el.querySelector('.el-input__inner').value).to.equal('三级 1-1'); + done(); + }, 100); + }); + + it('disabled select', () => { + vm = createTest(TreeSelect, { disabled: true, value: '' }, true); + expect(vm.$el.querySelector('.el-input').classList.contains('is-disabled')).to.true; + }); +}); diff --git a/types/element-ui.d.ts b/types/element-ui.d.ts index 62c4f2ad4..34d832ccd 100644 --- a/types/element-ui.d.ts +++ b/types/element-ui.d.ts @@ -89,7 +89,8 @@ import { ElDescriptions } from './descriptions' import { ElDescriptionsItem } from './descriptions-item' import { ElResult } from './result' import { ElStatistic } from './statistic' - + import { ElTreeSelect } from './tree-select' + export interface InstallationOptions { locale: any, i18n: any, @@ -379,4 +380,6 @@ export class DescriptionsItem extends ElDescriptionsItem {} export class Result extends ElResult {} /** Statistic Component */ -export class Statistic extends ElStatistic {} \ No newline at end of file +export class Statistic extends ElStatistic {} +/** TreeSelect Component */ +export class TreeSelect extends ElTreeSelect {} diff --git a/types/tree-select.d.ts b/types/tree-select.d.ts new file mode 100644 index 000000000..d2e20f877 --- /dev/null +++ b/types/tree-select.d.ts @@ -0,0 +1,111 @@ +import { ElementUIComponent, ElementUIComponentSize } from "./component"; + +export interface TreeData { + value?: any; + label?: string; + disabled?: boolean; + isLeaf?: boolean; + children?: TreeData[]; +} + +export interface TreeProps { + label: string; + disabled: string; + isLeaf: string; + children: string; +} + +export interface QueryChangeHandler { + /** + * @param queryString Current value of the text input + */ + (queryString: string): void; +} + +/** TreeSelect Component */ +export declare class ElTreeSelect extends ElementUIComponent { + /** The form input value */ + value: V | V[]; + + /** Tree data */ + data: D[]; + + /** Whether multiple-select is activated */ + multiple: boolean; + + /** Whether Select is disabled */ + disabled: boolean; + + /** Unique identity key name for value, required when value is an object */ + nodeKey: string; + + /** Configuration options, see the following table */ + props: TreeProps; + + /** Size of Input */ + size: ElementUIComponentSize; + + /** Whether single select can be cleared */ + clearable: boolean; + + /** The name attribute of select input */ + name: string; + + /** Placeholder */ + placeholder: string; + + /** Whether Select is filterable */ + filterable: boolean; + + /** Custom filter method */ + filterMethod: QueryChangeHandler; + + /** Whether to append the popper menu to body */ + popperAppendToBody: boolean; + + /** Method for loading subtree data */ + load: (data: D, resolve: Function) => void; + + /** Displayed text when data is empty */ + emptyText: string; + + /** Horizontal indentation of nodes in adjacent levels in pixels */ + indent: number; + + /** Whether checked state of a node not affects its father and child nodes when show-checkbox is true */ + checkStrictly: boolean; + + /** Whether only one node among the same level can be expanded at one time */ + accordion: boolean; + + /** Whether node is selectable */ + showCheckbox: boolean; + + /**whether to collapse tags to a text when multiple selecting */ + collapseTags: boolean; + + /** Whether Select is loading data from server */ + loading: boolean; + + /** Method for loading subtree data */ + load: (data: D, resolve: Function) => void; + + /** whether to lazy load leaf node, used with load attribute */ + lazy: boolean; + + /** Whether to expand all nodes by default */ + defaultExpandAll: boolean; + + /** Whether to expand father node when a child node is expanded */ + autoExpandParent: boolean; + + /** + * Focus the Input component + */ + focus(): void; + + /** + * Blur the Input component, and hide the dropdown + */ + blur(): void; +}