diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 00000000..89202874
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,13 @@
+const base = require('@umijs/fabric/dist/eslint');
+
+module.exports = {
+ ...base,
+ rules: {
+ ...base.rules,
+ 'default-case': 0,
+ 'eslint-comments/disable-enable-pair': 0,
+ 'jsx-a11y/interactive-supports-focus': 0,
+ 'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
+ '@typescript-eslint/no-object-literal-type-assertion': 0,
+ },
+};
diff --git a/.fatherrc.js b/.fatherrc.js
new file mode 100644
index 00000000..767a2abf
--- /dev/null
+++ b/.fatherrc.js
@@ -0,0 +1,8 @@
+export default {
+ cjs: 'babel',
+ esm: { type: 'babel', importLibToEs: true },
+ preCommit: {
+ eslint: true,
+ prettier: true,
+ },
+};
diff --git a/.gitignore b/.gitignore
index 75edf312..dc0bfbfa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+.storybook
+.doc
*.iml
*.log
.idea
diff --git a/assets/select.less b/assets/select.less
index 908ddb88..2e473db4 100644
--- a/assets/select.less
+++ b/assets/select.less
@@ -1,536 +1,3 @@
-@selectPrefixCls: rc-tree-select;
+@import '~rc-select/assets/index';
-.effect() {
- animation-duration: .3s;
- animation-fill-mode: both;
- transform-origin: 0 0;
-}
-
-.@{selectPrefixCls} {
- box-sizing: border-box;
- display: inline-block;
- position: relative;
- vertical-align: middle;
- color: #666;
-
- &-allow-clear {
- .@{selectPrefixCls}-selection--single .@{selectPrefixCls}-selection__rendered {
- padding-right: 40px;
- }
- }
-
- ul, li {
- margin: 0;
- padding: 0;
- list-style: none;
- }
-
- > ul > li > a {
- padding: 0;
- background-color: #fff;
- }
-
- // arrow
- &-arrow {
- height: 26px;
- position: absolute;
- top: 1px;
- right: 1px;
- width: 20px;
- &:after {
- content: '';
- border-color: #999999 transparent transparent transparent;
- border-style: solid;
- border-width: 5px 4px 0 4px;
- height: 0;
- width: 0;
- margin-left: -4px;
- margin-top: -2px;
- position: absolute;
- top: 50%;
- left: 50%;
- }
- }
-
- &-selection {
- outline: none;
- user-select: none;
- -webkit-user-select: none;
-
- box-sizing: border-box;
- display: block;
-
- background-color: #fff;
- border-radius: 6px;
- border: 1px solid #d9d9d9;
-
- &__clear {
- font-weight: bold;
- position: absolute;
- }
- }
-
- &-enabled {
- .@{selectPrefixCls}-selection {
- &:hover {
- border-color: #23c0fa;
- box-shadow: 0 0 2px fadeout(#2db7f5, 20%);
- }
- &:active {
- border-color: #2db7f5;
- }
- }
-
- &.@{selectPrefixCls}-focused {
- .@{selectPrefixCls}-selection {
- //border-color: #23c0fa;
- border-color: #7700fa;
- box-shadow: 0 0 2px fadeout(#2db7f5, 20%);
- }
- }
- }
-
-
-
- &-selection--single {
- height: 28px;
- cursor: pointer;
- position: relative;
-
- .@{selectPrefixCls}-selection__rendered {
- display: block;
- padding-left: 10px;
- padding-right: 20px;
- line-height: 28px;
- }
-
- .@{selectPrefixCls}-selection-selected-value {
- display: block;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
-
- .@{selectPrefixCls}-selection__clear {
- top: 5px;
- right: 20px;
- &:after {
- content: '×';
- }
- }
- }
-
- &-disabled {
- color: #ccc;
- cursor: not-allowed;
-
- .@{selectPrefixCls}-selection--single,
- .@{selectPrefixCls}-selection__choice__remove {
- cursor: not-allowed;
- color: #ccc;
-
- &:hover{
- cursor: not-allowed;
- color: #ccc;
- }
- }
- }
-
- &-search__field__wrap {
- display: inline-block;
- position: relative;
- }
-
- &-search__field__placeholder {
- display: block;
- position: absolute;
- top: 0;
- left: 3px;
- color: #aaa;
- }
-
- &-search__field__mirror {
- position: absolute;
- top: 0;
- left: -9999px;
- white-space: pre;
- pointer-events: none;
- }
-
- &-search--inline {
- float: left;
- width: 100%;
- .@{selectPrefixCls}-search__field__wrap {
- width: 100%;
- }
- .@{selectPrefixCls}-search__field {
- border: none;
- font-size: 100%;
- //margin-top: 5px;
- background: transparent;
- outline: 0;
- width: 100%;
- }
- > i {
- float: right;
- }
- }
-
- &-enabled&-selection--multiple {
- cursor: text;
- }
-
- &-selection--multiple {
- min-height: 28px;
-
- .@{selectPrefixCls}-search--inline {
- width: auto;
- .@{selectPrefixCls}-search__field {
- width: 0.75em;
- }
- }
-
- .@{selectPrefixCls}-search__field__placeholder {
- top: 5px;
- left: 8px;
- }
-
- .@{selectPrefixCls}-selection__rendered {
- //display: inline-block;
- overflow: hidden;
- text-overflow: ellipsis;
- padding-left: 8px;
- padding-bottom: 2px;
- padding-right: 10px;
- }
-
- > ul > li {
- margin-top: 4px;
- height: 20px;
- line-height: 20px;
- }
-
- .@{selectPrefixCls}-selection__clear {
- top: 5px;
- right: 8px;
- }
- }
-
- &-enabled {
- .@{selectPrefixCls}-selection__choice {
- cursor: default;
- &:hover {
- .@{selectPrefixCls}-selection__choice__remove {
- opacity: 1;
- transform: scale(1);
- }
- .@{selectPrefixCls}-selection__choice__remove +
- .@{selectPrefixCls}-selection__choice__content {
- margin-left: -8px;
- margin-right: 8px;
- }
- }
- }
- }
-
- & &-selection__choice {
- background-color: #f3f3f3;
- border-radius: 4px;
- float: left;
- padding: 0 15px;
- margin-right: 4px;
- position: relative;
- overflow: hidden;
- transition: padding .3s cubic-bezier(0.6, -0.28, 0.735, 0.045), width .3s cubic-bezier(0.6, -0.28, 0.735, 0.045);
-
- &__content {
- margin-left: 0;
- margin-right: 0;
- transition: margin .3s cubic-bezier(0.165, 0.84, 0.44, 1);
- }
-
- &-zoom-enter, &-zoom-appear, &-zoom-leave {
- .effect();
- opacity: 0;
- animation-play-state: paused;
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
- }
-
- &-zoom-leave {
- opacity: 1;
- animation-timing-function: cubic-bezier(0.6, -0.28, 0.735, 0.045);
- }
-
- &-zoom-enter.@{selectPrefixCls}-selection__choice-zoom-enter-active,
- &-zoom-appear.@{selectPrefixCls}-selection__choice-zoom-appear-active {
- animation-play-state: running;
- animation-name: rcSelectChoiceZoomIn;
- }
-
- &-zoom-leave.@{selectPrefixCls}-selection__choice-zoom-leave-active {
- animation-play-state: running;
- animation-name: rcSelectChoiceZoomOut;
- }
-
- @keyframes rcSelectChoiceZoomIn {
- 0% {
- transform: scale(0.6);
- opacity: 0;
- }
- 100% {
- transform: scale(1);
- opacity: 1;
- }
- }
-
- @keyframes rcSelectChoiceZoomOut {
- to {
- transform: scale(0);
- opacity: 0;
- }
- }
-
- &__remove {
- color: #919191;
- cursor: pointer;
- font-weight: bold;
- padding: 0 0 0 8px;
- position: absolute;
- opacity: 0;
- transform: scale(0);
- top: 0;
- right: 2px;
- transition: opacity .3s, transform .3s;
-
- &:before {
- content: '×'
- }
-
- &:hover {
- color: #333;
- }
- }
- }
-
- &-dropdown {
- background-color: white;
- border: 1px solid #d9d9d9;
- box-shadow: 0 0px 4px #d9d9d9;
- border-radius: 4px;
- box-sizing: border-box;
- z-index: 100;
- left: -9999px;
- top: -9999px;
- //border-top: none;
- //border-top-left-radius: 0;
- //border-top-right-radius: 0;
- position: absolute;
- outline: none;
-
- &-hidden {
- display: none;
- }
-
- &-menu {
- outline: none;
- margin: 0;
- padding: 0;
- list-style: none;
- z-index: 9999;
-
- > li {
- margin: 0;
- padding: 0;
- }
-
- &-item-group-list {
- margin: 0;
- padding: 0;
-
- > li.@{selectPrefixCls}-menu-item {
- padding-left: 20px;
- }
- }
-
- &-item-group-title {
- color: #999;
- line-height: 1.5;
- padding: 8px 10px;
- border-bottom: 1px solid #dedede;
- }
-
- li&-item {
- margin: 0;
- position: relative;
- display: block;
- padding: 7px 10px;
- font-weight: normal;
- color: #666666;
- white-space: nowrap;
-
- &-selected {
- background-color: #ddd;
- }
-
- &-active {
- background-color: #5897fb;
- color: white;
- cursor: pointer;
- }
-
- &-disabled {
- color: #ccc;
- cursor: not-allowed;
- }
-
- &-divider {
- height: 1px;
- margin: 1px 0;
- overflow: hidden;
- background-color: #e5e5e5;
- line-height: 0;
- }
- }
- }
-
- &-slide-up-enter, &-slide-up-appear {
- .effect();
- opacity: 0;
- animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
- animation-play-state: paused;
- }
-
- &-slide-up-leave {
- .effect();
- opacity: 1;
- animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34);
- animation-play-state: paused;
- }
-
- &-slide-up-enter&-slide-up-enter-active&-placement-bottomLeft, &-slide-up-appear&-slide-up-appear-active&-placement-bottomLeft {
- animation-name: rcSelectDropdownSlideUpIn;
- animation-play-state: running;
- }
-
- &-slide-up-leave&-slide-up-leave-active&-placement-bottomLeft {
- animation-name: rcSelectDropdownSlideUpOut;
- animation-play-state: running;
- }
-
- &-slide-up-enter&-slide-up-enter-active&-placement-topLeft, &-slide-up-appear&-slide-up-appear-active&-placement-topLeft {
- animation-name: rcSelectDropdownSlideDownIn;
- animation-play-state: running;
- }
-
- &-slide-up-leave&-slide-up-leave-active&-placement-topLeft {
- animation-name: rcSelectDropdownSlideDownOut;
- animation-play-state: running;
- }
-
- @keyframes rcSelectDropdownSlideUpIn {
- 0% {
- opacity: 0;
- transform-origin: 0% 0%;
- transform: scaleY(0);
- }
- 100% {
- opacity: 1;
- transform-origin: 0% 0%;
- transform: scaleY(1);
- }
- }
- @keyframes rcSelectDropdownSlideUpOut {
- 0% {
- opacity: 1;
- transform-origin: 0% 0%;
- transform: scaleY(1);
- }
- 100% {
- opacity: 0;
- transform-origin: 0% 0%;
- transform: scaleY(0);
- }
- }
-
- @keyframes rcSelectDropdownSlideDownIn {
- 0% {
- opacity: 0;
- transform-origin: 0% 100%;
- transform: scaleY(0);
- }
- 100% {
- opacity: 1;
- transform-origin: 0% 100%;
- transform: scaleY(1);
- }
- }
- @keyframes rcSelectDropdownSlideDownOut {
- 0% {
- opacity: 1;
- transform-origin: 0% 100%;
- transform: scaleY(1);
- }
- 100% {
- opacity: 0;
- transform-origin: 0% 100%;
- transform: scaleY(0);
- }
- }
- }
-
- &-dropdown-search {
- display: block;
- padding: 4px;
- .@{selectPrefixCls}-search__field__wrap {
- width: 100%;
- }
- .@{selectPrefixCls}-search__field__placeholder {
- top: 4px;
- }
- .@{selectPrefixCls}-search__field {
- padding: 4px;
- width: 100%;
- box-sizing: border-box;
- border: 1px solid #d9d9d9;
- border-radius: 4px;
- outline: none;
- }
- &.@{selectPrefixCls}-search--hide {
- display: none;
- }
- }
-
- &-open {
- .@{selectPrefixCls}-arrow:after {
- border-color: transparent transparent #888 transparent;
- border-width: 0 4px 5px 4px;
- }
- }
-
- &-not-found {
- display: inline-block;
- padding: 8px;
- }
-}
-
-.custom-icon-demo {
- .@{selectPrefixCls} {
- &-selection__choice__remove {
- &:before {
- content: '';
- }
- }
-
- &-arrow {
- &:after {
- display: none;
- }
- }
-
- &-selection__clear {
- &:after {
- content: '';
- }
- }
- }
-}
\ No newline at end of file
+@select-prefix: ~'rc-tree-select';
\ No newline at end of file
diff --git a/assets/tree.less b/assets/tree.less
index 7a74b213..df4daa8a 100644
--- a/assets/tree.less
+++ b/assets/tree.less
@@ -1,164 +1,3 @@
-@treePrefixCls: rc-tree-select-tree;
-.@{treePrefixCls} {
- margin: 0;
- padding: 5px;
- li {
- padding: 0;
- margin: 0;
- list-style: none;
- white-space: nowrap;
- outline: 0;
- a[draggable],
- a[draggable="true"] {
- color: #333;
- -moz-user-select: none;
- -khtml-user-select: none;
- -webkit-user-select: none;
- user-select: none;
- /* Required to make elements draggable in old WebKit */
- -khtml-user-drag: element;
- -webkit-user-drag: element;
- }
- &.drag-over {
- > a[draggable] {
- background-color: #316ac5;
- color: white;
- border: 1px #316ac5 solid;
- opacity: 0.8;
- }
- }
- &.drag-over-gap-top {
- > a[draggable] {
- border-top: 2px blue solid;
- }
- }
- &.drag-over-gap-bottom {
- > a[draggable] {
- border-bottom: 2px blue solid;
- }
- }
- &.filter-node {
- > .@{treePrefixCls}-node-content-wrapper {
- color: #a60000!important;
- font-weight: bold!important;
- }
- }
- ul {
- margin: 0;
- padding: 0 0 0 18px;
- &.@{treePrefixCls}-line {
- background: url("data:image/gif;base64,R0lGODlhCQACAIAAAMzMzP///yH5BAEAAAEALAAAAAAJAAIAAAIEjI9pUAA7") 0 0 repeat-y;
- }
- }
- a {
- display: inline-block;
- padding: 1px 3px 0 0;
- margin: 0;
- cursor: pointer;
- height: 17px;
- text-decoration: none;
- vertical-align: top;
- }
- span {
- &.@{treePrefixCls}-switcher,
- &.@{treePrefixCls}-checkbox,
- &.@{treePrefixCls}-iconEle {
- line-height: 16px;
- margin-right: 2px;
- width: 16px;
- height: 16px;
- display: inline-block;
- vertical-align: middle;
- border: 0 none;
- cursor: pointer;
- outline: none;
- background-color: transparent;
- background-repeat: no-repeat;
- background-attachment: scroll;
- background-image: url("data:image/png;base64,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");
- }
- &.@{treePrefixCls}-icon_loading {
- margin-right: 2px;
- vertical-align: top;
- background: url("data:image/gif;base64,R0lGODlhEAAQAKIGAMLY8YSx5HOm4Mjc88/g9Ofw+v///wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgAGACwAAAAAEAAQAAADMGi6RbUwGjKIXCAA016PgRBElAVlG/RdLOO0X9nK61W39qvqiwz5Ls/rRqrggsdkAgAh+QQFCgAGACwCAAAABwAFAAADD2hqELAmiFBIYY4MAutdCQAh+QQFCgAGACwGAAAABwAFAAADD1hU1kaDOKMYCGAGEeYFCQAh+QQFCgAGACwKAAIABQAHAAADEFhUZjSkKdZqBQG0IELDQAIAIfkEBQoABgAsCgAGAAUABwAAAxBoVlRKgyjmlAIBqCDCzUoCACH5BAUKAAYALAYACgAHAAUAAAMPaGpFtYYMAgJgLogA610JACH5BAUKAAYALAIACgAHAAUAAAMPCAHWFiI4o1ghZZJB5i0JACH5BAUKAAYALAAABgAFAAcAAAMQCAFmIaEp1motpDQySMNFAgA7") no-repeat scroll 0 0 transparent;
- }
- &.@{treePrefixCls}-switcher {
- &-noop {
- cursor: auto;
- background: none;
- }
- &_open {
- background-position: -93px -56px;
- }
- &_close {
- background-position: -75px -56px;
- }
- }
- &.@{treePrefixCls}-checkbox {
- width: 13px;
- height: 13px;
- margin: 0 3px;
- background-position: 0 0;
- &-checked {
- background-position: -14px 0;
- }
- &-indeterminate {
- background-position: -14px -28px;
- }
- &-disabled {
- background-position: 0 -56px;
- }
- &.@{treePrefixCls}-checkbox-checked.@{treePrefixCls}-checkbox-disabled {
- background-position: -14px -56px;
- }
- &.@{treePrefixCls}-checkbox-indeterminate.@{treePrefixCls}-checkbox-disabled {
- position: relative;
- background: #ccc;
- border-radius: 3px;
- &::after {
- content: ' ';
- -webkit-transform: scale(1);
- transform: scale(1);
- position: absolute;
- left: 3px;
- top: 5px;
- width: 5px;
- height: 0;
- border: 2px solid #fff;
- border-top: 0;
- border-left: 0;
- }
- }
- }
- }
- }
- &-child-tree {
- display: none;
- &-open {
- display: block;
- }
- }
- &-treenode-disabled {
- >span,
- >a,
- >a span {
- color: #ccc;
- cursor: not-allowed;
- }
- }
- &-node-selected {
- background-color: #ffe6b0;
- border: 1px #ffb951 solid;
- opacity: 0.8;
- }
- &-icon__open {
- margin-right: 2px;
- background-position: -110px -16px;
- vertical-align: top;
- }
- &-icon__close {
- margin-right: 2px;
- background-position: -110px 0;
- vertical-align: top;
- }
-}
+@import '~rc-tree/assets/index';
+
+@treePrefixCls: ~'rc-tree-select-tree';
\ No newline at end of file
diff --git a/examples/basic.html b/examples/basic.html
deleted file mode 100644
index b3a42524..00000000
--- a/examples/basic.html
+++ /dev/null
@@ -1 +0,0 @@
-placeholder
\ No newline at end of file
diff --git a/examples/basic.js b/examples/basic.tsx
similarity index 85%
rename from examples/basic.js
rename to examples/basic.tsx
index dca20674..0071c2f5 100644
--- a/examples/basic.js
+++ b/examples/basic.tsx
@@ -1,13 +1,9 @@
-/* eslint react/no-multi-comp:0, no-console:0, no-alert: 0 */
-
-import 'rc-tree-select/assets/index.less';
+import '../assets/index.less';
import React from 'react';
-import ReactDOM from 'react-dom';
import 'rc-dialog/assets/index.css';
import Dialog from 'rc-dialog';
-import TreeSelect, { TreeNode, SHOW_PARENT } from 'rc-tree-select';
-import { gData } from './util';
-import './demo.less';
+import TreeSelect, { TreeNode, SHOW_PARENT } from '../src';
+import { gData } from './utils/dataUtil';
function isLeaf(value) {
if (!value) {
@@ -33,14 +29,14 @@ function isLeaf(value) {
function findPath(value, data) {
const sel = [];
function loop(selected, children) {
- for (let i = 0; i < children.length; i++) {
+ for (let i = 0; i < children.length; i += 1) {
const item = children[i];
if (selected === item.value) {
sel.push(item);
return;
}
if (item.children) {
- loop(selected, item.children, item);
+ loop(selected, item.children);
if (sel.length) {
sel.push(item);
return;
@@ -128,9 +124,9 @@ class Demo extends React.Component {
console.log(args);
};
- onDropdownVisibleChange = (visible, info) => {
+ onDropdownVisibleChange = visible => {
const { value } = this.state;
- console.log(visible, value, info);
+ console.log(visible, value);
if (Array.isArray(value) && value.length > 1 && value.length < 3) {
window.alert('please select more than two item or less than one item.');
return false;
@@ -138,9 +134,7 @@ class Demo extends React.Component {
return true;
};
- filterTreeNode = (input, child) => {
- return String(child.props.title).indexOf(input) === 0;
- };
+ filterTreeNode = (input, child) => String(child.props.title).indexOf(input) === 0;
render() {
const {
@@ -166,8 +160,7 @@ class Demo extends React.Component {
animation="zoom"
maskAnimation="fade"
onClose={this.onClose}
- style={{ width: 600, height: 400, overflow: 'auto' }}
- id="area"
+ // style={{ width: 600, height: 400, overflow: 'auto' }}
>
请下拉选择}
searchPlaceholder="please search"
showSearch
@@ -197,7 +190,7 @@ class Demo extends React.Component {
style={{ width: 300 }}
transitionName="rc-tree-select-dropdown-slide-up"
choiceTransitionName="rc-tree-select-selection__choice-zoom"
- dropdownStyle={{ maxHeight: 200, overflow: 'auto' }}
+ // dropdownStyle={{ maxHeight: 200, overflow: 'auto' }}
placeholder={请下拉选择}
searchPlaceholder="please search"
showSearch
@@ -212,23 +205,13 @@ class Demo extends React.Component {
open={tsOpen}
onChange={(val, ...args) => {
console.log('onChange', val, ...args);
- if (val === '0-0-0-0-value') {
- this.setState({ tsOpen: true });
- } else {
- this.setState({ tsOpen: false });
- }
this.setState({ value: val });
}}
- onDropdownVisibleChange={(v, info) => {
- console.log('single onDropdownVisibleChange', v, info);
- // document clicked
- if (info.documentClickClose && value === '0-0-0-0-value') {
- return false;
- }
+ onDropdownVisibleChange={v => {
+ console.log('single onDropdownVisibleChange', v);
this.setState({
tsOpen: v,
});
- return true;
}}
onSelect={this.onSelect}
/>
@@ -238,7 +221,7 @@ class Demo extends React.Component {
style={{ width: 300 }}
transitionName="rc-tree-select-dropdown-slide-up"
choiceTransitionName="rc-tree-select-selection__choice-zoom"
- dropdownStyle={{ maxHeight: 200, overflow: 'auto' }}
+ // dropdownStyle={{ maxHeight: 200, overflow: 'auto' }}
placeholder={请下拉选择}
searchPlaceholder="please search"
showSearch
@@ -256,7 +239,7 @@ class Demo extends React.Component {
style={{ width: 300 }}
transitionName="rc-tree-select-dropdown-slide-up"
choiceTransitionName="rc-tree-select-selection__choice-zoom"
- dropdownStyle={{ maxHeight: 200, overflow: 'auto' }}
+ // dropdownStyle={{ maxHeight: 200, overflow: 'auto' }}
placeholder={请下拉选择}
searchPlaceholder="please search"
multiple
@@ -273,7 +256,8 @@ class Demo extends React.Component {
className="check-select"
transitionName="rc-tree-select-dropdown-slide-up"
choiceTransitionName="rc-tree-select-selection__choice-zoom"
- dropdownStyle={{ height: 200, overflow: 'auto' }}
+ style={{ width: 300 }}
+ // dropdownStyle={{ height: 200, overflow: 'auto' }}
dropdownPopupAlign={{ overflow: { adjustY: 0, adjustX: 0 }, offset: [0, 2] }}
onDropdownVisibleChange={this.onDropdownVisibleChange}
placeholder={请下拉选择}
@@ -300,7 +284,7 @@ class Demo extends React.Component {
style={{ width: 500 }}
transitionName="rc-tree-select-dropdown-slide-up"
choiceTransitionName="rc-tree-select-selection__choice-zoom"
- dropdownStyle={{ maxHeight: 200, overflow: 'auto' }}
+ // dropdownStyle={{ maxHeight: 200, overflow: 'auto' }}
placeholder={请下拉选择}
searchPlaceholder="please search"
showSearch
@@ -317,10 +301,10 @@ class Demo extends React.Component {
use treeDataSimpleMode
请下拉选择}
- searchPlaceholder="please search"
- treeLine
+ // searchPlaceholder="please search"
+ // treeLine
maxTagTextLength={10}
searchValue={simpleSearchValue}
onSearch={val => {
@@ -333,7 +317,10 @@ class Demo extends React.Component {
treeCheckable
showCheckedStrategy={SHOW_PARENT}
onChange={this.onChange}
- onSelect={this.onSelect}
+ onSelect={(...args) => {
+ this.setState({ simpleSearchValue: '' });
+ this.onSelect(...args);
+ }}
/>
Testing in extreme conditions (Boundary conditions test)
@@ -363,7 +350,7 @@ class Demo extends React.Component {
use TreeNode Component (not recommend)
, document.getElementById('__react-content'));
+export default Demo;
diff --git a/examples/big-data.html b/examples/big-data.html
deleted file mode 100644
index b3a42524..00000000
--- a/examples/big-data.html
+++ /dev/null
@@ -1 +0,0 @@
-placeholder
\ No newline at end of file
diff --git a/examples/big-data.js b/examples/big-data.tsx
similarity index 76%
rename from examples/big-data.js
rename to examples/big-data.tsx
index 5dd0fb9c..b36955d9 100644
--- a/examples/big-data.js
+++ b/examples/big-data.tsx
@@ -1,11 +1,7 @@
-/* eslint react/no-multi-comp:0, no-console:0 */
-
-import 'rc-tree-select/assets/index.less';
+import '../assets/index.less';
import React from 'react';
-import ReactDOM from 'react-dom';
-import TreeSelect, { SHOW_PARENT } from 'rc-tree-select';
-import Gen from './big-data-generator';
-import './demo.less';
+import TreeSelect, { SHOW_PARENT } from '../src';
+import Gen from './utils/big-data-generator';
class Demo extends React.Component {
state = {
@@ -22,7 +18,7 @@ class Demo extends React.Component {
onChangeStrictly = value1 => {
console.log('onChangeStrictly', value1);
- const ind = parseInt(Math.random() * 3, 10);
+ const ind = parseInt(`${Math.random() * 3}`, 10);
value1.push({ value: `0-0-0-${ind}-value`, label: `0-0-0-${ind}-label`, halfChecked: true });
this.setState({
value1,
@@ -38,12 +34,14 @@ class Demo extends React.Component {
{ value: '0-0-value', label: '0-0-label', halfChecked: true },
{ value: '0-0-0-value', label: '0-0-0-label' },
],
- // value: ['0-0-0-0-value', '0-0-0-1-value', '0-0-0-2-value'],
});
};
render() {
const { value1, gData1, value, gData } = this.state;
+
+ // console.log('>>>', gData, gData1, value1);
+
return (
@@ -52,7 +50,7 @@ class Demo extends React.Component {
normal check
checkStrictly
, document.getElementById('__react-content'));
+export default Demo;
diff --git a/examples/controlled.html b/examples/controlled.html
deleted file mode 100644
index b3a42524..00000000
--- a/examples/controlled.html
+++ /dev/null
@@ -1 +0,0 @@
-placeholder
\ No newline at end of file
diff --git a/examples/controlled.js b/examples/controlled.tsx
similarity index 84%
rename from examples/controlled.js
rename to examples/controlled.tsx
index 9c3254da..fd6669ab 100644
--- a/examples/controlled.js
+++ b/examples/controlled.tsx
@@ -1,11 +1,7 @@
-/* eslint react/no-multi-comp:0, no-console:0, no-alert: 0 */
-
-import 'rc-tree-select/assets/index.less';
+import '../assets/index.less';
import React from 'react';
-import ReactDOM from 'react-dom';
import 'rc-dialog/assets/index.css';
-import TreeSelect, { TreeNode } from 'rc-tree-select';
-import './demo.less';
+import TreeSelect, { TreeNode } from '../src';
class Demo extends React.Component {
state = {
@@ -32,7 +28,7 @@ class Demo extends React.Component {
Conrolled treeExpandedKeys
@@ -70,4 +66,4 @@ class Demo extends React.Component {
}
}
-ReactDOM.render(, document.getElementById('__react-content'));
+export default Demo;
diff --git a/examples/custom-icons.html b/examples/custom-icons.html
deleted file mode 100644
index e69de29b..00000000
diff --git a/examples/custom-icons.js b/examples/custom-icons.tsx
similarity index 67%
rename from examples/custom-icons.js
rename to examples/custom-icons.tsx
index 73c9fc99..275cfb31 100644
--- a/examples/custom-icons.js
+++ b/examples/custom-icons.tsx
@@ -1,14 +1,11 @@
-/* eslint react/no-multi-comp:0, no-console:0, no-alert: 0 */
-
-import 'rc-tree-select/assets/index.less';
+import '../assets/index.less';
import React from 'react';
-import ReactDOM from 'react-dom';
import 'rc-dialog/assets/index.css';
-import TreeSelect from 'rc-tree-select';
-import { gData } from './util';
-import './demo.less';
+import TreeSelect from '../src';
+import { gData } from './utils/dataUtil';
-const bubblePath = 'M632 888H392c-4.4 0-8 3.6-8 8v32c0 ' +
+const bubblePath =
+ 'M632 888H392c-4.4 0-8 3.6-8 8v32c0 ' +
'17.7 14.3 32 32 32h192c17.7 0 32-14.3 32-32v-3' +
'2c0-4.4-3.6-8-8-8zM512 64c-181.1 0-328 146.9-3' +
'28 328 0 121.4 66 227.4 164 284.1V792c0 17.7 1' +
@@ -19,44 +16,47 @@ const bubblePath = 'M632 888H392c-4.4 0-8 3.6-8 8v32c0 ' +
' 114.6-256 256-256s256 114.6 256 256c0 92.5-49' +
'.4 176.3-128.1 221.8z';
-const clearPath = 'M793 242H366v-74c0-6.7-7.7-10.4-12.9' +
+const clearPath =
+ 'M793 242H366v-74c0-6.7-7.7-10.4-12.9' +
'-6.3l-142 112c-4.1 3.2-4.1 9.4 0 12.6l142 112c' +
'5.2 4.1 12.9 0.4 12.9-6.3v-74h415v470H175c-4.4' +
' 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h618c35.3 0 64-' +
'28.7 64-64V306c0-35.3-28.7-64-64-64z';
-const arrowPath = 'M765.7 486.8L314.9 134.7c-5.3-4.1' +
+const arrowPath =
+ 'M765.7 486.8L314.9 134.7c-5.3-4.1' +
'-12.9-0.4-12.9 6.3v77.3c0 4.9 2.3 9.6 6.1 12.6l36' +
'0 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6' +
'.7 7.7 10.4 12.9 6.3l450.8-352.1c16.4-12.8 16.4-3' +
'7.6 0-50.4z';
-const getSvg = (path, iStyle = {}, style = {}) => {
- return (
-
-
-
- );
-}
-
+const getSvg = (path, iStyle = {}, style = {}) => (
+
+
+
+);
-const switcherIcon = (obj) => {
+const switcherIcon = obj => {
if (obj.isLeaf) {
- return getSvg(arrowPath,
+ return getSvg(
+ arrowPath,
{ cursor: 'pointer', backgroundColor: 'white' },
- { transform: 'rotate(270deg)' });
+ { transform: 'rotate(270deg)' },
+ );
}
- return getSvg(arrowPath,
+ return getSvg(
+ arrowPath,
{ cursor: 'pointer', backgroundColor: 'white' },
- { transform: `rotate(${obj.expanded ? 90 : 0}deg)` });
+ { transform: `rotate(${obj.expanded ? 90 : 0}deg)` },
+ );
};
const inputIcon = getSvg(bubblePath);
@@ -87,7 +87,8 @@ function Demo() {
transitionName="rc-tree-select-dropdown-slide-up"
style={{ width: 300 }}
dropdownStyle={{ maxHeight: 200, overflow: 'auto', zIndex: 1500 }}
- showSearch allowClear
+ showSearch
+ allowClear
{...iconProps}
/>
@@ -99,11 +100,12 @@ function Demo() {
transitionName="rc-tree-select-dropdown-slide-up"
style={{ width: 300 }}
dropdownStyle={{ maxHeight: 200, overflow: 'auto', zIndex: 1500 }}
- showSearch allowClear
+ showSearch
+ allowClear
{...iconPropsFunction}
/>
);
}
-ReactDOM.render(, document.getElementById('__react-content'));
+export default Demo;
diff --git a/examples/debug.tsx b/examples/debug.tsx
new file mode 100644
index 00000000..30e25a7a
--- /dev/null
+++ b/examples/debug.tsx
@@ -0,0 +1,89 @@
+/* eslint-disable react/no-array-index-key */
+
+import React from 'react';
+import TreeSelect, { TreeNode } from '../src';
+import '../assets/index.less';
+import { RawValueType } from '../src/interface';
+
+const treeData = [
+ { value: 'parent', label: 'Parent', children: [{ key: 'child', label: 'Child' }] },
+ {
+ value: 'parent2',
+ label: 'Parent 2',
+ children: new Array(20).fill(null).map((_, index) => ({
+ key: index,
+ label: `Hello_${index}`,
+ })),
+ },
+ { value: 'disabled', label: 'Disabled', disabled: true },
+ { value: 'disableCheckbox', label: 'No Checkbox', disableCheckbox: true },
+];
+
+const Demo: React.FC<{}> = () => {
+ const [search, setSearch] = React.useState('');
+ const [value, setValue] = React.useState([]);
+
+ return (
+ {
+ console.log('Focus:', target);
+ }}
+ onBlur={({ target }) => {
+ console.log('Blur:', target);
+ }}
+ >
+
Debug
+
+
+
+
+
+
+ {new Array(20).fill(null).map((_, index) => (
+
+ ))}
+
+
+
+
+ {
+ console.log('Search:', str);
+ setSearch(str);
+ }}
+ onChange={(...args) => {
+ console.log('Change:', ...args);
+ }}
+ />
+ {
+ console.log('Change:', newValue, ...args);
+ setValue(newValue);
+ }}
+ placeholder="Control Mode"
+ />
+
+
+
+
+
+
+
+ );
+};
+
+export default Demo;
diff --git a/examples/demo.less b/examples/demo.less
deleted file mode 100644
index 5d46fd9f..00000000
--- a/examples/demo.less
+++ /dev/null
@@ -1,14 +0,0 @@
-.rc-tree-select-selection--multiple {
- max-height: 50px;
- overflow-y: scroll;
-}
-.rc-tree-select-dropdown {
- max-height: 350px;
- overflow-y: scroll;
-}
-.check-select {
- width: 300px;
- .rc-tree-select-selection--multiple {
- min-height: 50px;
- }
-}
\ No newline at end of file
diff --git a/examples/disable.html b/examples/disable.html
deleted file mode 100644
index b3a42524..00000000
--- a/examples/disable.html
+++ /dev/null
@@ -1 +0,0 @@
-placeholder
\ No newline at end of file
diff --git a/examples/disable.js b/examples/disable.tsx
similarity index 83%
rename from examples/disable.js
rename to examples/disable.tsx
index 93e7a9a7..3075fc2a 100644
--- a/examples/disable.js
+++ b/examples/disable.tsx
@@ -1,10 +1,9 @@
-/* eslint react/no-multi-comp:0, no-console:0 */
-import 'rc-tree-select/assets/index.less';
-import TreeSelect from 'rc-tree-select';
+/* eslint-disable import/no-named-as-default-member */
+import '../assets/index.less';
import React from 'react';
-import ReactDOM from 'react-dom';
+import TreeSelect from '../src';
-const SHOW_PARENT = TreeSelect.SHOW_PARENT;
+const { SHOW_PARENT } = TreeSelect;
const treeData = [
{
@@ -83,4 +82,5 @@ class Demo extends React.Component {
}
}
-ReactDOM.render(, document.getElementById('__react-content'));
+export default Demo;
+/* eslint-enable */
diff --git a/examples/dynamic.html b/examples/dynamic.html
deleted file mode 100644
index b3a42524..00000000
--- a/examples/dynamic.html
+++ /dev/null
@@ -1 +0,0 @@
-placeholder
\ No newline at end of file
diff --git a/examples/dynamic.js b/examples/dynamic.js
deleted file mode 100644
index 0c9f1620..00000000
--- a/examples/dynamic.js
+++ /dev/null
@@ -1,60 +0,0 @@
-/* eslint react/no-multi-comp:0, no-console:0 */
-
-import 'rc-tree-select/assets/index.less';
-import React from 'react';
-import ReactDOM from 'react-dom';
-import TreeSelect from 'rc-tree-select';
-import { getNewTreeData, generateTreeNodes } from './util';
-
-class Demo extends React.Component {
- static propTypes = {};
-
- state = {
- treeData: [
- { label: 'pNode 01', value: '0-0', key: '0-0' },
- { label: 'pNode 02', value: '0-1', key: '0-1' },
- { label: 'pNode 03', value: '0-2', key: '0-2', isLeaf: true },
- ],
- // value: '0-0',
- value: { value: '0-0-0-value', label: '0-0-0-label' },
- };
-
- onChange = value => {
- console.log(value);
- this.setState({
- value,
- });
- };
-
- onLoadData = treeNode => {
- console.log(treeNode);
- return new Promise(resolve => {
- setTimeout(() => {
- let { treeData } = this.state;
- treeData = treeData.slice();
- getNewTreeData(treeData, treeNode.props.eventKey, generateTreeNodes(treeNode), 2);
- this.setState({ treeData });
- resolve();
- }, 500);
- });
- };
-
- render() {
- const { treeData, value } = this.state;
- return (
-
-
dynamic render
-
-
- );
- }
-}
-
-ReactDOM.render(, document.getElementById('__react-content'));
diff --git a/examples/dynamic.tsx b/examples/dynamic.tsx
new file mode 100644
index 00000000..5738893c
--- /dev/null
+++ b/examples/dynamic.tsx
@@ -0,0 +1,97 @@
+import '../assets/index.less';
+import React from 'react';
+import TreeSelect from '../src';
+import { getNewTreeData, generateTreeNodes } from './utils/dataUtil';
+
+function getTreeData() {
+ return [
+ { label: 'pNode 01', value: '0-0', key: '0-0' },
+ { label: 'pNode 02', value: '0-1', key: '0-1' },
+ { label: 'pNode 03', value: '0-2', key: '0-2', isLeaf: true },
+ ];
+}
+
+class Demo extends React.Component {
+ static propTypes = {};
+
+ state = {
+ treeData: getTreeData(),
+ // value: '0-0',
+ value: { value: '0-0-0-value', label: '0-0-0-label' },
+ loadedKeys: [],
+ };
+
+ onChange = value => {
+ console.log(value);
+ this.setState({
+ value,
+ });
+ };
+
+ loadData = treeNode => {
+ console.log('trigger load:', treeNode);
+ return new Promise(resolve => {
+ setTimeout(() => {
+ let { treeData } = this.state;
+ treeData = treeData.slice();
+ getNewTreeData(treeData, treeNode.props.eventKey, generateTreeNodes(treeNode), 2);
+ this.setState({ treeData });
+ resolve();
+ }, 500);
+ });
+ };
+
+ onTreeLoad = loadedKeys => {
+ this.setState({ loadedKeys });
+ };
+
+ onResetTree = () => {
+ this.setState({
+ treeData: getTreeData(),
+ });
+ };
+
+ onResetLoadedKeys = () => {
+ this.setState({
+ loadedKeys: [],
+ });
+ };
+
+ render() {
+ const { treeData, value, loadedKeys } = this.state;
+ return (
+
+
dynamic render
+
+ Controlled
+
+
+
+
+
+ );
+ }
+}
+
+export default Demo;
diff --git a/examples/filter.html b/examples/filter.html
deleted file mode 100644
index b3a42524..00000000
--- a/examples/filter.html
+++ /dev/null
@@ -1 +0,0 @@
-placeholder
\ No newline at end of file
diff --git a/examples/filter.js b/examples/filter.tsx
similarity index 89%
rename from examples/filter.js
rename to examples/filter.tsx
index 7ba4d7cb..5e6fe3ac 100644
--- a/examples/filter.js
+++ b/examples/filter.tsx
@@ -1,10 +1,9 @@
-/* eslint react/no-multi-comp:0, no-console:0 */
-
-import 'rc-tree-select/assets/index.less';
+import '../assets/index.less';
import React from 'react';
-import ReactDOM from 'react-dom';
-import TreeSelect, { SHOW_PARENT } from 'rc-tree-select';
-import { gData } from './util';
+import TreeSelect, { SHOW_PARENT } from '../src';
+import { gData } from './utils/dataUtil';
+
+console.log('TreeData:', gData);
class Demo extends React.Component {
state = {
@@ -62,7 +61,7 @@ class Demo extends React.Component {
style={{ width: 300 }}
transitionName="rc-tree-select-dropdown-slide-up"
choiceTransitionName="rc-tree-select-selection__choice-zoom"
- dropdownStyle={{ height: 200, overflow: 'auto' }}
+ // dropdownStyle={{ height: 200, overflow: 'auto' }}
dropdownPopupAlign={{ overflow: { adjustY: 0, adjustX: 0 }, offset: [0, 2] }}
placeholder={请下拉选择}
searchPlaceholder="please search"
@@ -103,4 +102,4 @@ class Demo extends React.Component {
}
}
-ReactDOM.render(, document.getElementById('__react-content'));
+export default Demo;
diff --git a/examples/form.html b/examples/form.html
deleted file mode 100644
index b3a42524..00000000
--- a/examples/form.html
+++ /dev/null
@@ -1 +0,0 @@
-placeholder
\ No newline at end of file
diff --git a/examples/form.js b/examples/form.js
deleted file mode 100644
index afebebeb..00000000
--- a/examples/form.js
+++ /dev/null
@@ -1,129 +0,0 @@
-/* eslint react/no-multi-comp:0, no-console:0 */
-
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import ReactDOM from 'react-dom';
-import TreeSelect from 'rc-tree-select';
-import Select from 'rc-select';
-import { createForm } from 'rc-form';
-import 'rc-select/assets/index.css';
-import 'rc-tree-select/assets/index.less';
-import { regionStyle, errorStyle } from './styles';
-import { gData } from './util';
-import './demo.less';
-
-const { Option } = Select;
-
-class TreeSelectInput extends Component {
- onChange = (value, ...args) => {
- console.log(value, ...args);
- const props = this.props;
- if (props.onChange) {
- props.onChange(value);
- }
- };
-
- render() {
- return ;
- }
-}
-
-// @createForm()
-class Form extends Component {
- static propTypes = {
- form: PropTypes.object,
- };
-
- onSubmit = e => {
- const { form } = this.props;
- console.log('submit');
- e.preventDefault();
- form.validateFields((error, values) => {
- if (!error) {
- console.log('ok', values);
- } else {
- console.log('error', error, values);
- }
- });
- };
-
- reset = e => {
- const { form } = this.props;
- e.preventDefault();
- form.resetFields();
- };
-
- render() {
- const { form } = this.props;
- const { getFieldDecorator, getFieldError } = form;
- const tProps = {
- multiple: true,
- treeData: gData,
- treeCheckable: true,
- // treeDefaultExpandAll: true,
- };
- return (
-
- );
- }
-}
-
-// ReactDOM.render(, document.getElementById('__react-content'));
-const NewForm = createForm()(Form);
-ReactDOM.render(, document.getElementById('__react-content'));
diff --git a/examples/form.tsx b/examples/form.tsx
new file mode 100644
index 00000000..66d27e9e
--- /dev/null
+++ b/examples/form.tsx
@@ -0,0 +1,138 @@
+import React, { Component } from 'react';
+import Select from 'rc-select';
+import Form, { useForm, Field } from 'rc-field-form';
+import TreeSelect from '../src';
+import 'rc-select/assets/index.less';
+import '../assets/index.less';
+import { gData } from './utils/dataUtil';
+
+const { Option } = Select;
+
+const regionStyle = {
+ border: '1px solid red',
+ marginTop: 10,
+ padding: 10,
+};
+
+const errorStyle = {
+ color: 'red',
+ marginTop: 10,
+ padding: 10,
+};
+
+class TreeSelectInput extends Component<{ onChange?: Function; style: React.CSSProperties }> {
+ onChange = (value, ...args) => {
+ console.log(value, ...args);
+ const { props } = this;
+ if (props.onChange) {
+ props.onChange(value);
+ }
+ };
+
+ render() {
+ return ;
+ }
+}
+
+const Demo = () => {
+ const [form] = useForm();
+
+ const onFinish = values => {
+ console.log('Submit:', values);
+ };
+
+ const onReset = () => {
+ form.resetFields();
+ };
+
+ const tProps = {
+ multiple: true,
+ treeData: gData,
+ treeCheckable: true,
+ };
+
+ return (
+
+ );
+};
+
+export default Demo;
diff --git a/examples/styles.js b/examples/styles.js
deleted file mode 100644
index 39b4c0a5..00000000
--- a/examples/styles.js
+++ /dev/null
@@ -1,11 +0,0 @@
-export const regionStyle = {
- border: '1px solid red',
- marginTop: 10,
- padding: 10,
-};
-
-export const errorStyle = {
- color: 'red',
- marginTop: 10,
- padding: 10,
-};
diff --git a/examples/big-data-generator.js b/examples/utils/big-data-generator.tsx
similarity index 70%
rename from examples/big-data-generator.js
rename to examples/utils/big-data-generator.tsx
index 8ec86807..ee293da1 100644
--- a/examples/big-data-generator.js
+++ b/examples/utils/big-data-generator.tsx
@@ -1,16 +1,14 @@
import React from 'react';
-import PropTypes from 'prop-types';
-import { generateData, calcTotal } from './util';
-import { createRef } from '../src/util';
+import { generateData, calcTotal } from './dataUtil';
-class Gen extends React.Component {
- static propTypes = {
- onGen: PropTypes.func,
- x: PropTypes.number,
- y: PropTypes.number,
- z: PropTypes.number,
- };
+interface GenProps {
+ onGen: Function;
+ x: number;
+ y: number;
+ z: number;
+}
+class Gen extends React.Component {
static defaultProps = {
onGen: () => {},
x: 20,
@@ -18,12 +16,11 @@ class Gen extends React.Component {
z: 1,
};
- constructor() {
- super();
- this.xRef = createRef();
- this.yRef = createRef();
- this.zRef = createRef();
- }
+ xRef = React.createRef();
+
+ yRef = React.createRef();
+
+ zRef = React.createRef();
state = {
nums: '',
@@ -45,12 +42,17 @@ class Gen extends React.Component {
});
};
- getVals = () => {
- return {
- x: parseInt(this.xRef.current.value, 10),
- y: parseInt(this.yRef.current.value, 10),
- z: parseInt(this.zRef.current.value, 10),
- };
+ getVals = () => ({
+ x: parseInt(this.xRef.current.value, 10),
+ y: parseInt(this.yRef.current.value, 10),
+ z: parseInt(this.zRef.current.value, 10),
+ });
+
+ getDefaultValue = (key: string) => {
+ if (key in this.props) {
+ return String(this.props[key]);
+ }
+ return null;
};
render() {
@@ -64,7 +66,7 @@ class Gen extends React.Component {
x:{' '}
n >= 0 ? x * (y ** (n--)) + rec(n) : 0;
+ const rec = n => (n >= 0 ? x * y ** n-- + rec(n) : 0);
return rec(z + 1);
}
console.log('总节点数(单个tree):', calcTotal());
@@ -53,9 +52,12 @@ export function generateTreeNodes(treeNode) {
function setLeaf(treeData, curKey, level) {
const loopLeaf = (data, lev) => {
const l = lev - 1;
- data.forEach((item) => {
- if ((item.key.length > curKey.length) ? item.key.indexOf(curKey) !== 0 :
- curKey.indexOf(item.key) !== 0) {
+ data.forEach(item => {
+ if (
+ item.key.length > curKey.length
+ ? item.key.indexOf(curKey) !== 0
+ : curKey.indexOf(item.key) !== 0
+ ) {
return;
}
if (item.children) {
@@ -69,9 +71,9 @@ function setLeaf(treeData, curKey, level) {
}
export function getNewTreeData(treeData, curKey, child, level) {
- const loop = (data) => {
+ const loop = data => {
if (level < 1 || curKey.length - 3 > level * 2) return;
- data.forEach((item) => {
+ data.forEach(item => {
if (curKey.indexOf(item.key) === 0) {
if (item.children) {
loop(item.children);
@@ -85,7 +87,6 @@ export function getNewTreeData(treeData, curKey, child, level) {
setLeaf(treeData, curKey, level);
}
-
function loopData(data, callback) {
const loop = (d, level = 0) => {
d.forEach((item, index) => {
@@ -104,7 +105,7 @@ function isPositionPrefix(smallPos, bigPos) {
return false;
}
// attention: "0-0-1" "0-0-10"
- if ((bigPos.length > smallPos.length) && (bigPos.charAt(smallPos.length) !== '-')) {
+ if (bigPos.length > smallPos.length && bigPos.charAt(smallPos.length) !== '-') {
return false;
}
return bigPos.substr(0, smallPos.length) === smallPos;
@@ -123,8 +124,8 @@ export function getFilterValue(val, sVal, delVal) {
}
});
const newPos = [];
- delPos.forEach((item) => {
- allPos.forEach((i) => {
+ delPos.forEach(item => {
+ allPos.forEach(i => {
if (isPositionPrefix(item, i) || isPositionPrefix(i, item)) {
// 过滤掉 父级节点 和 所有子节点。
// 因为 node节点 不选时,其 父级节点 和 所有子节点 都不选。
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 00000000..0c6601ae
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,4 @@
+module.exports = {
+ setupFiles: ['./tests/setup.js'],
+ snapshotSerializers: [require.resolve('enzyme-to-json/serializer')],
+};
\ No newline at end of file
diff --git a/now.json b/now.json
index 8b975ffc..c62f59c3 100644
--- a/now.json
+++ b/now.json
@@ -6,7 +6,7 @@
{
"src": "package.json",
"use": "@now/static-build",
- "config": { "distDir": "build" }
+ "config": { "distDir": ".doc" }
}
]
}
\ No newline at end of file
diff --git a/package.json b/package.json
index f218adf2..d5180142 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
"tree-select"
],
"homepage": "https://github.com/react-component/tree-select",
- "author": "hualei5280@gmail.com",
+ "author": "smith3816@gmail.com",
"repository": {
"type": "git",
"url": "https://github.com/react-component/tree-select.git"
@@ -21,6 +21,7 @@
"es",
"lib",
"dist",
+ "assets/*.less",
"assets/*.css",
"assets/*.png",
"assets/*.gif"
@@ -28,58 +29,41 @@
"license": "MIT",
"main": "./lib/index",
"module": "./es/index",
- "config": {
- "port": 8007,
- "entry": {
- "rc-tree-select": [
- "./assets/index.less",
- "./src/index.js"
- ]
- }
- },
"scripts": {
- "build": "rc-tools run build",
- "compile": "rc-tools run compile --babel-runtime",
- "gh-pages": "rc-tools run gh-pages",
- "start": "rc-tools run server",
- "pub": "rc-tools run pub",
- "lint": "rc-tools run lint",
- "test": "rc-tools run test",
- "coverage": "rc-tools run test --coverage",
- "pre-commit": "rc-tools run pre-commit",
- "lint-staged": "lint-staged",
+ "start": "cross-env NODE_ENV=development father doc dev --storybook",
+ "build": "father doc build --storybook",
+ "compile": "father build",
+ "prepublishOnly": "npm run compile && np --no-cleanup --yolo --no-publish",
+ "lint": "eslint src/ examples/ --ext .tsx,.ts,.jsx,.js",
+ "test": "father test",
"now-build": "npm run build"
},
- "dependencies": {
- "classnames": "^2.2.1",
- "dom-scroll-into-view": "^1.2.1",
- "prop-types": "^15.5.8",
- "raf": "^3.4.0",
- "rc-animate": "^2.8.2",
- "rc-tree": "~2.0.0",
- "rc-trigger": "^3.0.0-rc.2",
- "rc-util": "^4.5.0",
- "react-lifecycles-compat": "^3.0.4",
- "shallowequal": "^1.0.2",
- "warning": "^4.0.1"
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
},
"devDependencies": {
- "lint-staged": "^8.1.1",
- "pre-commit": "1.x",
- "rc-dialog": "^7.0.0",
- "rc-form": "^2.4.2",
- "rc-select": "^8.8.3",
- "rc-tools": "^9.3.5",
- "react": "^16.0.0",
- "react-dom": "^16.0.0"
+ "@types/react": "^16.8.19",
+ "@types/react-dom": "^16.8.4",
+ "@types/warning": "^3.0.0",
+ "cross-env": "^5.2.0",
+ "enzyme": "^3.10.0",
+ "enzyme-adapter-react-16": "^1.1.1",
+ "enzyme-to-json": "^3.4.0",
+ "father": "^2.13.2",
+ "np": "^5.0.3",
+ "rc-dialog": "^7.5.7",
+ "rc-field-form": "^0.0.0-alpha.21",
+ "rc-trigger": "^2.6.5",
+ "rc-virtual-list": "^0.0.0-alpha.28",
+ "react": "^16.8.0",
+ "react-dom": "^16.8.0",
+ "typescript": "^3.5.2"
},
- "pre-commit": [
- "lint-staged"
- ],
- "lint-staged": {
- "*.{js,jsx,ts,tsx}": [
- "npm run pre-commit",
- "git add"
- ]
+ "dependencies": {
+ "classnames": "2.x",
+ "rc-select": "^10.0.0-alpha.24",
+ "rc-tree": "^3.0.0-alpha.35",
+ "rc-util": "^4.9.0"
}
}
diff --git a/src/Base/BasePopup.jsx b/src/Base/BasePopup.jsx
deleted file mode 100644
index 3f751f43..00000000
--- a/src/Base/BasePopup.jsx
+++ /dev/null
@@ -1,273 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { polyfill } from 'react-lifecycles-compat';
-
-import Tree from 'rc-tree';
-import { createRef } from '../util';
-
-export const popupContextTypes = {
- onPopupKeyDown: PropTypes.func.isRequired,
- onTreeNodeSelect: PropTypes.func.isRequired,
- onTreeNodeCheck: PropTypes.func.isRequired,
-};
-
-class BasePopup extends React.Component {
- static propTypes = {
- prefixCls: PropTypes.string,
- upperSearchValue: PropTypes.string,
- valueList: PropTypes.array,
- searchHalfCheckedKeys: PropTypes.array,
- valueEntities: PropTypes.object,
- keyEntities: PropTypes.object,
- treeIcon: PropTypes.bool,
- treeLine: PropTypes.bool,
- treeNodeFilterProp: PropTypes.string,
- treeCheckable: PropTypes.oneOfType([PropTypes.bool, PropTypes.node]),
- treeCheckStrictly: PropTypes.bool,
- treeDefaultExpandAll: PropTypes.bool,
- treeDefaultExpandedKeys: PropTypes.array,
- treeExpandedKeys: PropTypes.array,
- loadData: PropTypes.func,
- multiple: PropTypes.bool,
- onTreeExpand: PropTypes.func,
-
- treeNodes: PropTypes.node,
- filteredTreeNodes: PropTypes.node,
- notFoundContent: PropTypes.node,
-
- ariaId: PropTypes.string,
- switcherIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
-
- // HOC
- renderSearch: PropTypes.func,
- onTreeExpanded: PropTypes.func,
- };
-
- static contextTypes = {
- rcTreeSelect: PropTypes.shape({
- ...popupContextTypes,
- }),
- };
-
- constructor(props) {
- super();
-
- const { treeDefaultExpandAll, treeDefaultExpandedKeys, keyEntities } = props;
-
- // TODO: make `expandedKeyList` control
- let expandedKeyList = treeDefaultExpandedKeys;
- if (treeDefaultExpandAll) {
- expandedKeyList = Object.keys(keyEntities);
- }
-
- this.state = {
- keyList: [],
- expandedKeyList,
- // Cache `expandedKeyList` when tree is in filter. This is used in `getDerivedStateFromProps`
- cachedExpandedKeyList: [], // eslint-disable-line react/no-unused-state
- loadedKeys: [],
- };
-
- this.treeRef = createRef();
- }
-
- static getDerivedStateFromProps(nextProps, prevState) {
- const { prevProps = {}, loadedKeys, expandedKeyList, cachedExpandedKeyList } = prevState || {};
- const {
- valueList,
- valueEntities,
- keyEntities,
- treeExpandedKeys,
- filteredTreeNodes,
- upperSearchValue,
- } = nextProps;
-
- const newState = {
- prevProps: nextProps,
- };
-
- // Check value update
- if (valueList !== prevProps.valueList) {
- newState.keyList = valueList
- .map(({ value }) => valueEntities[value])
- .filter(entity => entity)
- .map(({ key }) => key);
- }
-
- // Show all when tree is in filter mode
- if (
- !treeExpandedKeys &&
- filteredTreeNodes &&
- filteredTreeNodes.length &&
- filteredTreeNodes !== prevProps.filteredTreeNodes
- ) {
- newState.expandedKeyList = Object.keys(keyEntities);
- }
-
- // Cache `expandedKeyList` when filter set
- if (upperSearchValue && !prevProps.upperSearchValue) {
- newState.cachedExpandedKeyList = expandedKeyList;
- } else if (!upperSearchValue && prevProps.upperSearchValue && !treeExpandedKeys) {
- newState.expandedKeyList = cachedExpandedKeyList || [];
- newState.cachedExpandedKeyList = [];
- }
-
- // Use expandedKeys if provided
- if (prevProps.treeExpandedKeys !== treeExpandedKeys) {
- newState.expandedKeyList = treeExpandedKeys;
- }
-
- // Clean loadedKeys if key not exist in keyEntities anymore
- if (nextProps.loadData) {
- newState.loadedKeys = loadedKeys.filter(key => key in keyEntities);
- }
-
- return newState;
- }
-
- onTreeExpand = expandedKeyList => {
- const { treeExpandedKeys, onTreeExpand, onTreeExpanded } = this.props;
-
- // Set uncontrolled state
- if (!treeExpandedKeys) {
- this.setState({ expandedKeyList }, onTreeExpanded);
- }
-
- if (onTreeExpand) {
- onTreeExpand(expandedKeyList);
- }
- };
-
- onLoad = loadedKeys => {
- this.setState({ loadedKeys });
- };
-
- getTree = () => {
- return this.treeRef.current;
- };
-
- /**
- * Not pass `loadData` when searching. To avoid loop ajax call makes browser crash.
- */
- getLoadData = () => {
- const { loadData, upperSearchValue } = this.props;
- if (upperSearchValue) return null;
- return loadData;
- };
-
- /**
- * This method pass to Tree component which is used for add filtered class
- * in TreeNode > li
- */
- filterTreeNode = treeNode => {
- const { upperSearchValue, treeNodeFilterProp } = this.props;
-
- const filterVal = treeNode.props[treeNodeFilterProp];
- if (typeof filterVal === 'string') {
- return upperSearchValue && filterVal.toUpperCase().indexOf(upperSearchValue) !== -1;
- }
-
- return false;
- };
-
- renderNotFound = () => {
- const { prefixCls, notFoundContent } = this.props;
-
- return {notFoundContent};
- };
-
- render() {
- const { keyList, expandedKeyList, loadedKeys } = this.state;
- const {
- prefixCls,
- treeNodes,
- filteredTreeNodes,
- treeIcon,
- treeLine,
- treeCheckable,
- treeCheckStrictly,
- multiple,
- ariaId,
- renderSearch,
- switcherIcon,
- searchHalfCheckedKeys,
- } = this.props;
- const {
- rcTreeSelect: { onPopupKeyDown, onTreeNodeSelect, onTreeNodeCheck },
- } = this.context;
-
- const loadData = this.getLoadData();
-
- const treeProps = {};
-
- if (treeCheckable) {
- treeProps.checkedKeys = keyList;
- } else {
- treeProps.selectedKeys = keyList;
- }
-
- let $notFound;
- let $treeNodes;
- if (filteredTreeNodes) {
- if (filteredTreeNodes.length) {
- treeProps.checkStrictly = true;
- $treeNodes = filteredTreeNodes;
-
- // Fill halfCheckedKeys
- if (treeCheckable && !treeCheckStrictly) {
- treeProps.checkedKeys = {
- checked: keyList,
- halfChecked: searchHalfCheckedKeys,
- };
- }
- } else {
- $notFound = this.renderNotFound();
- }
- } else if (!treeNodes || !treeNodes.length) {
- $notFound = this.renderNotFound();
- } else {
- $treeNodes = treeNodes;
- }
-
- let $tree;
- if ($notFound) {
- $tree = $notFound;
- } else {
- $tree = (
-
- {$treeNodes}
-
- );
- }
-
- return (
-
- {renderSearch ? renderSearch() : null}
- {$tree}
-
- );
- }
-}
-
-polyfill(BasePopup);
-
-export default BasePopup;
diff --git a/src/Base/BaseSelector.jsx b/src/Base/BaseSelector.jsx
deleted file mode 100644
index ba07d30e..00000000
--- a/src/Base/BaseSelector.jsx
+++ /dev/null
@@ -1,203 +0,0 @@
-/**
- * Input Box is in different position for different mode.
- * This not the same design as `Select` cause it's followed by antd 0.x `Select`.
- * We will not follow the new design immediately since antd 3.x is already released.
- *
- * So this file named as Selector to avoid confuse.
- */
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import classNames from 'classnames';
-import { polyfill } from 'react-lifecycles-compat';
-import { createRef } from '../util';
-
-export const selectorPropTypes = {
- prefixCls: PropTypes.string,
- className: PropTypes.string,
- style: PropTypes.object,
- open: PropTypes.bool,
- selectorValueList: PropTypes.array,
- allowClear: PropTypes.bool,
- showArrow: PropTypes.bool,
- onClick: PropTypes.func,
- onBlur: PropTypes.func,
- onFocus: PropTypes.func,
- removeSelected: PropTypes.func,
-
- // Pass by component
- ariaId: PropTypes.string,
- inputIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
- clearIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
-};
-
-export const selectorContextTypes = {
- onSelectorFocus: PropTypes.func.isRequired,
- onSelectorBlur: PropTypes.func.isRequired,
- onSelectorKeyDown: PropTypes.func.isRequired,
- onSelectorClear: PropTypes.func.isRequired,
-};
-
-export default function (modeName) {
- class BaseSelector extends React.Component {
- static propTypes = {
- ...selectorPropTypes,
-
- // Pass by HOC
- renderSelection: PropTypes.func.isRequired,
- renderPlaceholder: PropTypes.func,
- tabIndex: PropTypes.number,
- };
-
- static contextTypes = {
- rcTreeSelect: PropTypes.shape({
- ...selectorContextTypes,
- }),
- };
-
- static defaultProps = {
- tabIndex: 0,
- }
-
- constructor() {
- super();
-
- this.domRef = createRef();
- }
-
- onFocus = (...args) => {
- const { onFocus, focused } = this.props;
- const { rcTreeSelect: { onSelectorFocus } } = this.context;
-
- if (!focused) {
- onSelectorFocus();
- }
-
- if (onFocus) {
- onFocus(...args);
- }
- };
-
- onBlur = (...args) => {
- const { onBlur } = this.props;
- const { rcTreeSelect: { onSelectorBlur } } = this.context;
-
- // TODO: Not trigger when is inner component get focused
- onSelectorBlur();
-
- if (onBlur) {
- onBlur(...args);
- }
- };
-
- focus = () => {
- this.domRef.current.focus();
- }
-
- blur = () => {
- this.domRef.current.focus();
- }
-
- renderClear() {
- const { prefixCls, allowClear, selectorValueList, clearIcon } = this.props;
- const { rcTreeSelect: { onSelectorClear } } = this.context;
-
- if (!allowClear || !selectorValueList.length || !selectorValueList[0].value) {
- return null;
- }
-
- return (
-
- {typeof clearIcon === 'function' ?
- React.createElement(clearIcon, { ...this.props }) : clearIcon}
-
- );
- }
-
- renderArrow() {
- const { prefixCls, showArrow, inputIcon } = this.props;
- if (!showArrow) {
- return null;
- }
-
- return (
-
- {typeof inputIcon === 'function' ?
- React.createElement(inputIcon, { ...this.props }) : inputIcon}
-
- );
- }
-
- render() {
- const {
- prefixCls, className, style,
- open, focused, disabled, allowClear,
- onClick,
- ariaId,
- renderSelection, renderPlaceholder,
- tabIndex,
- } = this.props;
- const { rcTreeSelect: { onSelectorKeyDown } } = this.context;
-
- let myTabIndex = tabIndex;
- if (disabled) {
- myTabIndex = null;
- }
-
- return (
-
-
- {renderSelection()}
- {this.renderClear()}
- {this.renderArrow()}
-
- {renderPlaceholder && renderPlaceholder()}
-
-
- );
- }
- }
-
- polyfill(BaseSelector);
-
- return BaseSelector;
-}
diff --git a/src/Context.tsx b/src/Context.tsx
new file mode 100644
index 00000000..7a81bb52
--- /dev/null
+++ b/src/Context.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import { IconType } from 'rc-tree/lib/interface';
+import { Key, LegacyDataNode } from './interface';
+
+interface ContextProps {
+ checkable: boolean;
+ checkedKeys: Key[];
+ halfCheckedKeys: Key[];
+ treeExpandedKeys: Key[];
+ treeDefaultExpandedKeys: Key[];
+ onTreeExpand: (keys: Key[]) => void;
+ treeDefaultExpandAll: boolean;
+ treeIcon: IconType;
+ switcherIcon: IconType;
+ treeLine: boolean;
+ treeNodeFilterProp: string;
+ treeLoadedKeys: Key[];
+ loadData: (treeNode: LegacyDataNode) => Promise;
+ onTreeLoad: (loadedKeys: Key[]) => void;
+}
+
+export const SelectContext = React.createContext(null);
diff --git a/src/OptionList.tsx b/src/OptionList.tsx
new file mode 100644
index 00000000..d77256d3
--- /dev/null
+++ b/src/OptionList.tsx
@@ -0,0 +1,261 @@
+import React from 'react';
+import KeyCode from 'rc-util/lib/KeyCode';
+import { RefOptionListProps } from 'rc-select/lib/OptionList';
+import Tree, { TreeProps } from 'rc-tree';
+import { EventDataNode } from 'rc-tree/lib/interface';
+import { FlattenDataNode, RawValueType, DataNode, TreeDataNode, Key } from './interface';
+import { SelectContext } from './Context';
+import useKeyValueMapping from './hooks/useKeyValueMapping';
+import useKeyValueMap from './hooks/useKeyValueMap';
+
+const HIDDEN_STYLE = {
+ width: 0,
+ height: 0,
+ display: 'flex',
+ overflow: 'hidden',
+ opacity: 0,
+ border: 0,
+ padding: 0,
+ margin: 0,
+};
+
+interface TreeEventInfo {
+ node: { key: Key };
+ selected?: boolean;
+ checked?: boolean;
+}
+
+export interface OptionListProps {
+ prefixCls: string;
+ id: string;
+ options: OptionsType;
+ flattenOptions: FlattenDataNode[];
+ height: number;
+ itemHeight: number;
+ values: Set;
+ multiple: boolean;
+ open: boolean;
+ defaultActiveFirstOption?: boolean;
+ notFoundContent?: React.ReactNode;
+ menuItemSelectedIcon?: any;
+ childrenAsData: boolean;
+ searchValue: string;
+
+ onSelect: (value: RawValueType, option: { selected: boolean }) => void;
+ onToggleOpen: (open?: boolean) => void;
+ /** Tell Select that some value is now active to make accessibility work */
+ onActiveValue: (value: RawValueType, index: number) => void;
+ onScroll: React.UIEventHandler;
+}
+
+const OptionList: React.RefForwardingComponent> = (
+ props,
+ ref,
+) => {
+ const {
+ prefixCls,
+ height,
+ itemHeight,
+ options,
+ flattenOptions,
+ multiple,
+ searchValue,
+ onSelect,
+ onToggleOpen,
+ open,
+ notFoundContent,
+ } = props;
+ const {
+ checkable,
+ checkedKeys,
+ halfCheckedKeys,
+ treeExpandedKeys,
+ treeDefaultExpandAll,
+ treeDefaultExpandedKeys,
+ onTreeExpand,
+ treeIcon,
+ switcherIcon,
+ treeLine,
+ treeNodeFilterProp,
+ loadData,
+ treeLoadedKeys,
+ onTreeLoad,
+ } = React.useContext(SelectContext);
+
+ const treeRef = React.useRef();
+
+ const [cacheKeyMap, cacheValueMap] = useKeyValueMap(flattenOptions);
+ const [getEntityByKey, getEntityByValue] = useKeyValueMapping(cacheKeyMap, cacheValueMap);
+
+ // ========================== Values ==========================
+ const valueKeys = React.useMemo(
+ () =>
+ checkedKeys.map(val => {
+ const entity = getEntityByValue(val);
+ return entity ? entity.key : null;
+ }),
+ [checkedKeys],
+ );
+
+ const mergedCheckedKeys = React.useMemo(() => {
+ if (!checkable) {
+ return null;
+ }
+
+ return {
+ checked: valueKeys,
+ halfChecked: halfCheckedKeys,
+ };
+ }, [valueKeys, halfCheckedKeys, checkable]);
+
+ // ========================== Scroll ==========================
+ React.useEffect(() => {
+ // Single mode should scroll to current key
+ if (open && !multiple && valueKeys.length) {
+ treeRef.current.scrollTo({ key: valueKeys[0] });
+ }
+ }, [open]);
+
+ // ========================== Search ==========================
+ const lowerSearchValue = String(searchValue).toLowerCase();
+ const filterTreeNode = (treeNode: EventDataNode) => {
+ if (!lowerSearchValue) {
+ return false;
+ }
+ return String(treeNode[treeNodeFilterProp])
+ .toLowerCase()
+ .includes(lowerSearchValue);
+ };
+
+ // =========================== Keys ===========================
+ const [expandedKeys, setExpandedKeys] = React.useState(treeDefaultExpandedKeys);
+ const [searchExpandedKeys, setSearchExpandedKeys] = React.useState(null);
+ const mergedExpandedKeys = treeExpandedKeys || (searchValue ? searchExpandedKeys : expandedKeys);
+
+ React.useEffect(() => {
+ if (searchValue) {
+ setSearchExpandedKeys(flattenOptions.map(o => o.key));
+ }
+ }, [searchValue]);
+
+ const onInternalExpand = (keys: Key[]) => {
+ setExpandedKeys(keys);
+ setSearchExpandedKeys(keys);
+
+ if (onTreeExpand) {
+ onTreeExpand(keys);
+ }
+ };
+
+ // ========================== Events ==========================
+ const onListMouseDown: React.MouseEventHandler = event => {
+ event.preventDefault();
+ };
+
+ const onInternalSelect = (_: Key[], { node: { key } }: TreeEventInfo) => {
+ const entity = getEntityByKey(key, checkable ? 'checkbox' : 'select');
+ if (entity !== null) {
+ onSelect(entity.data.value, { selected: !checkedKeys.includes(entity.data.value) });
+ }
+
+ if (!multiple) {
+ onToggleOpen(false);
+ }
+ };
+
+ // ========================= Keyboard =========================
+ const [activeKey, setActiveKey] = React.useState(null);
+ const activeEntity = getEntityByKey(activeKey);
+
+ React.useImperativeHandle(ref, () => ({
+ onKeyDown: event => {
+ const { which } = event;
+ switch (which) {
+ // >>> Arrow keys
+ case KeyCode.UP:
+ case KeyCode.DOWN:
+ case KeyCode.LEFT:
+ case KeyCode.RIGHT:
+ treeRef.current.onKeyDown(event as React.KeyboardEvent);
+ break;
+
+ // >>> Select item
+ case KeyCode.ENTER: {
+ if (activeEntity !== null) {
+ onInternalSelect(null, {
+ node: { key: activeKey },
+ selected: !checkedKeys.includes(activeEntity.data.value),
+ });
+ }
+ break;
+ }
+
+ // >>> Close
+ case KeyCode.ESC: {
+ onToggleOpen(false);
+ }
+ }
+ },
+ onKeyUp: () => {},
+ }));
+
+ // ========================== Render ==========================
+ if (options.length === 0) {
+ return (
+
+ {notFoundContent}
+
+ );
+ }
+
+ const treeProps: Partial = {};
+ if (treeLoadedKeys) {
+ treeProps.loadedKeys = treeLoadedKeys;
+ }
+ if (mergedExpandedKeys) {
+ treeProps.expandedKeys = mergedExpandedKeys;
+ }
+
+ return (
+
+ {activeEntity && open && (
+
+ {activeEntity.data.value}
+
+ )}
+
+
+
+ );
+};
+
+const RefOptionList = React.forwardRef>(OptionList);
+RefOptionList.displayName = 'OptionList';
+
+export default RefOptionList;
diff --git a/src/Popup/MultiplePopup.jsx b/src/Popup/MultiplePopup.jsx
deleted file mode 100644
index 09b09a27..00000000
--- a/src/Popup/MultiplePopup.jsx
+++ /dev/null
@@ -1,3 +0,0 @@
-import BasePopup from '../Base/BasePopup';
-
-export default BasePopup;
diff --git a/src/Popup/SinglePopup.jsx b/src/Popup/SinglePopup.jsx
deleted file mode 100644
index 80bfd202..00000000
--- a/src/Popup/SinglePopup.jsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import BasePopup from '../Base/BasePopup';
-import SearchInput from '../SearchInput';
-import { createRef } from '../util';
-
-class SinglePopup extends React.Component {
- static propTypes = {
- ...BasePopup.propTypes,
- searchValue: PropTypes.string,
- showSearch: PropTypes.bool,
- dropdownPrefixCls: PropTypes.string,
- disabled: PropTypes.bool,
- searchPlaceholder: PropTypes.string,
- };
-
- constructor() {
- super();
-
- this.inputRef = createRef();
- this.searchRef = createRef();
- this.popupRef = createRef();
- }
-
- onPlaceholderClick = () => {
- this.inputRef.current.focus();
- };
-
- getTree = () => {
- return this.popupRef.current && this.popupRef.current.getTree();
- };
-
- renderPlaceholder = () => {
- const { searchPlaceholder, searchValue, prefixCls } = this.props;
-
- if (!searchPlaceholder) {
- return null;
- }
-
- return (
-
- {searchPlaceholder}
-
- );
- };
-
- renderSearch = () => {
- const { showSearch, dropdownPrefixCls } = this.props;
-
- if (!showSearch) {
- return null;
- }
-
- return (
-
-
-
- );
- };
-
- render() {
- return ;
- }
-}
-
-export default SinglePopup;
diff --git a/src/SearchInput.jsx b/src/SearchInput.jsx
deleted file mode 100644
index 90954c38..00000000
--- a/src/SearchInput.jsx
+++ /dev/null
@@ -1,131 +0,0 @@
-/**
- * Since search box is in different position with different mode.
- * - Single: in the popup box
- * - multiple: in the selector
- * Move the code as a SearchInput for easy management.
- */
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import { polyfill } from 'react-lifecycles-compat';
-import { createRef } from './util';
-
-export const searchContextTypes = {
- onSearchInputChange: PropTypes.func.isRequired,
-};
-
-class SearchInput extends React.Component {
- static propTypes = {
- open: PropTypes.bool,
- searchValue: PropTypes.string,
- prefixCls: PropTypes.string,
- disabled: PropTypes.bool,
- renderPlaceholder: PropTypes.func,
- needAlign: PropTypes.bool,
- ariaId: PropTypes.string,
- };
-
- static contextTypes = {
- rcTreeSelect: PropTypes.shape({
- ...searchContextTypes,
- }),
- };
-
- constructor() {
- super();
-
- this.inputRef = createRef();
- this.mirrorInputRef = createRef();
- }
-
- componentDidMount() {
- const { open, needAlign } = this.props;
- if (needAlign) {
- this.alignInputWidth();
- }
-
- if (open) {
- this.focus(true);
- }
- }
-
- componentDidUpdate(prevProps) {
- const { open, searchValue, needAlign } = this.props;
-
- if (open && prevProps.open !== open) {
- this.focus();
- }
-
-
- if (needAlign && searchValue !== prevProps.searchValue) {
- this.alignInputWidth();
- }
- }
-
- /**
- * `scrollWidth` is not correct in IE, do the workaround.
- * ref: https://github.com/react-component/tree-select/issues/65
- */
- alignInputWidth = () => {
- this.inputRef.current.style.width =
- `${this.mirrorInputRef.current.clientWidth}px`;
- };
-
- /**
- * Need additional timeout for focus cause parent dom is not ready when didMount trigger
- */
- focus = (isDidMount) => {
- if (this.inputRef.current) {
- this.inputRef.current.focus();
- if (isDidMount) {
- setTimeout(() => {
- this.inputRef.current.focus();
- }, 0);
- }
- }
- };
-
- blur = () => {
- if (this.inputRef.current) {
- this.inputRef.current.blur();
- }
- };
-
- render() {
- const { searchValue, prefixCls, disabled, renderPlaceholder, open, ariaId } = this.props;
- const { rcTreeSelect: {
- onSearchInputChange, onSearchInputKeyDown,
- } } = this.context;
-
- return (
-
-
-
- {searchValue}
-
-
- {renderPlaceholder ? renderPlaceholder() : null}
-
- );
- }
-}
-
-polyfill(SearchInput);
-
-export default SearchInput;
diff --git a/src/Select.jsx b/src/Select.jsx
deleted file mode 100644
index cc03590c..00000000
--- a/src/Select.jsx
+++ /dev/null
@@ -1,1068 +0,0 @@
-/**
- * ARIA: https://www.w3.org/TR/wai-aria/#combobox
- * Sample 1: https://www.w3.org/TR/2017/NOTE-wai-aria-practices-1.1-20171214/examples/combobox/aria1.1pattern/listbox-combo.html
- * Sample 2: https://www.w3.org/blog/wai-components-gallery/widget/combobox-with-aria-autocompleteinline/
- *
- * Tab logic:
- * Popup is close
- * 1. Focus input (mark component as focused)
- * 2. Press enter to show the popup
- * 3. If popup has input, focus it
- *
- * Popup is open
- * 1. press tab to close the popup
- * 2. Focus back to the selection input box
- * 3. Let the native tab going on
- *
- * TreeSelect use 2 design type.
- * In single mode, we should focus on the `span`
- * In multiple mode, we should focus on the `input`
- */
-
-import React from 'react';
-import { findDOMNode } from 'react-dom';
-import PropTypes from 'prop-types';
-import { polyfill } from 'react-lifecycles-compat';
-import KeyCode from 'rc-util/lib/KeyCode';
-import shallowEqual from 'shallowequal';
-import raf from 'raf';
-import scrollIntoView from 'dom-scroll-into-view';
-
-import SelectTrigger from './SelectTrigger';
-import { selectorContextTypes } from './Base/BaseSelector';
-import { popupContextTypes } from './Base/BasePopup';
-import SingleSelector from './Selector/SingleSelector';
-import MultipleSelector, { multipleSelectorContextTypes } from './Selector/MultipleSelector';
-import SinglePopup from './Popup/SinglePopup';
-import MultiplePopup from './Popup/MultiplePopup';
-
-import { SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './strategies';
-
-import {
- createRef,
- generateAriaId,
- formatInternalValue,
- formatSelectorValue,
- parseSimpleTreeData,
- convertDataToTree,
- convertTreeToEntities,
- conductCheck,
- getHalfCheckedKeys,
- flatToHierarchy,
- isPosRelated,
- isLabelInValue,
- getFilterTree,
- cleanEntity,
- findPopupContainer,
-} from './util';
-import { valueProp } from './propTypes';
-import SelectNode from './SelectNode';
-
-class Select extends React.Component {
- static propTypes = {
- prefixCls: PropTypes.string,
- prefixAria: PropTypes.string,
- multiple: PropTypes.bool,
- showArrow: PropTypes.bool,
- open: PropTypes.bool,
- value: valueProp,
- autoFocus: PropTypes.bool,
-
- defaultOpen: PropTypes.bool,
- defaultValue: valueProp,
-
- showSearch: PropTypes.bool,
- placeholder: PropTypes.node,
- inputValue: PropTypes.string, // [Legacy] Deprecated. Use `searchValue` instead.
- searchValue: PropTypes.string,
- autoClearSearchValue: PropTypes.bool,
- searchPlaceholder: PropTypes.node, // [Legacy] Confuse with placeholder
- disabled: PropTypes.bool,
- children: PropTypes.node,
- labelInValue: PropTypes.bool,
- maxTagCount: PropTypes.number,
- maxTagPlaceholder: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
- maxTagTextLength: PropTypes.number,
- showCheckedStrategy: PropTypes.oneOf([SHOW_ALL, SHOW_PARENT, SHOW_CHILD]),
-
- dropdownMatchSelectWidth: PropTypes.bool,
- treeData: PropTypes.array,
- treeDataSimpleMode: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
- treeNodeFilterProp: PropTypes.string,
- treeNodeLabelProp: PropTypes.string,
- treeCheckable: PropTypes.oneOfType([PropTypes.bool, PropTypes.node]),
- treeCheckStrictly: PropTypes.bool,
- treeIcon: PropTypes.bool,
- treeLine: PropTypes.bool,
- treeDefaultExpandAll: PropTypes.bool,
- treeDefaultExpandedKeys: PropTypes.array,
- treeExpandedKeys: PropTypes.array,
- loadData: PropTypes.func,
- filterTreeNode: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
-
- notFoundContent: PropTypes.node,
-
- onSearch: PropTypes.func,
- onSelect: PropTypes.func,
- onDeselect: PropTypes.func,
- onChange: PropTypes.func,
- onDropdownVisibleChange: PropTypes.func,
-
- onTreeExpand: PropTypes.func,
-
- inputIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
- clearIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
- removeIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
- switcherIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
- };
-
- static childContextTypes = {
- rcTreeSelect: PropTypes.shape({
- ...selectorContextTypes,
- ...multipleSelectorContextTypes,
- ...popupContextTypes,
-
- onSearchInputChange: PropTypes.func,
- onSearchInputKeyDown: PropTypes.func,
- }),
- };
-
- static defaultProps = {
- prefixCls: 'rc-tree-select',
- prefixAria: 'rc-tree-select',
- showArrow: true,
- showSearch: true,
- autoClearSearchValue: true,
- showCheckedStrategy: SHOW_CHILD,
-
- // dropdownMatchSelectWidth change the origin design, set to false now
- // ref: https://github.com/react-component/select/blob/4cad95e098a341a09de239ad6981067188842020/src/Select.jsx#L344
- // ref: https://github.com/react-component/select/pull/71
- treeNodeFilterProp: 'value',
- treeNodeLabelProp: 'title',
- treeIcon: false,
- notFoundContent: 'Not Found',
- };
-
- constructor(props) {
- super(props);
-
- const { prefixAria, defaultOpen, open } = props;
-
- this.state = {
- open: open || defaultOpen,
- valueList: [],
- searchHalfCheckedKeys: [],
- missValueList: [], // Contains the value not in the tree
- selectorValueList: [], // Used for multiple selector
- valueEntities: {},
- keyEntities: {},
- searchValue: '',
-
- init: true,
- };
-
- this.selectorRef = createRef();
- this.selectTriggerRef = createRef();
-
- // ARIA need `aria-controls` props mapping
- // Since this need user input. Let's generate ourselves
- this.ariaId = generateAriaId(`${prefixAria}-list`);
- }
-
- getChildContext() {
- return {
- rcTreeSelect: {
- onSelectorFocus: this.onSelectorFocus,
- onSelectorBlur: this.onSelectorBlur,
- onSelectorKeyDown: this.onComponentKeyDown,
- onSelectorClear: this.onSelectorClear,
- onMultipleSelectorRemove: this.onMultipleSelectorRemove,
-
- onTreeNodeSelect: this.onTreeNodeSelect,
- onTreeNodeCheck: this.onTreeNodeCheck,
- onPopupKeyDown: this.onComponentKeyDown,
-
- onSearchInputChange: this.onSearchInputChange,
- onSearchInputKeyDown: this.onSearchInputKeyDown,
- },
- };
- }
-
- static getDerivedStateFromProps(nextProps, prevState) {
- const { prevProps = {} } = prevState;
- const {
- treeCheckable,
- treeCheckStrictly,
- filterTreeNode,
- treeNodeFilterProp,
- treeDataSimpleMode,
- } = nextProps;
- const newState = {
- prevProps: nextProps,
- init: false,
- };
-
- // Process the state when props updated
- function processState(propName, updater) {
- if (prevProps[propName] !== nextProps[propName]) {
- updater(nextProps[propName], prevProps[propName]);
- return true;
- }
- return false;
- }
-
- let valueRefresh = false;
-
- // Open
- processState('open', propValue => {
- newState.open = propValue;
- });
-
- // Tree Nodes
- let treeNodes;
- let treeDataChanged = false;
- let treeDataModeChanged = false;
- processState('treeData', propValue => {
- treeNodes = convertDataToTree(propValue);
- treeDataChanged = true;
- });
-
- processState('treeDataSimpleMode', (propValue, prevValue) => {
- if (!propValue) return;
-
- const prev = !prevValue || prevValue === true ? {} : prevValue;
-
- // Shallow equal to avoid dynamic prop object
- if (!shallowEqual(propValue, prev)) {
- treeDataModeChanged = true;
- }
- });
-
- // Parse by `treeDataSimpleMode`
- if (treeDataSimpleMode && (treeDataChanged || treeDataModeChanged)) {
- const simpleMapper = {
- id: 'id',
- pId: 'pId',
- rootPId: null,
- ...(treeDataSimpleMode !== true ? treeDataSimpleMode : {}),
- };
- treeNodes = convertDataToTree(parseSimpleTreeData(nextProps.treeData, simpleMapper));
- }
-
- // If `treeData` not provide, use children TreeNodes
- if (!nextProps.treeData) {
- processState('children', propValue => {
- treeNodes = Array.isArray(propValue) ? propValue : [propValue];
- });
- }
-
- // Convert `treeData` to entities
- if (treeNodes) {
- const entitiesMap = convertTreeToEntities(treeNodes);
- newState.treeNodes = treeNodes;
- newState.posEntities = entitiesMap.posEntities;
- newState.valueEntities = entitiesMap.valueEntities;
- newState.keyEntities = entitiesMap.keyEntities;
-
- valueRefresh = true;
- }
-
- // Value List
- if (prevState.init) {
- processState('defaultValue', propValue => {
- newState.valueList = formatInternalValue(propValue, nextProps);
- valueRefresh = true;
- });
- }
-
- processState('value', propValue => {
- newState.valueList = formatInternalValue(propValue, nextProps);
- valueRefresh = true;
- });
-
- // Selector Value List
- if (valueRefresh) {
- // Find out that value not exist in the tree
- const missValueList = [];
- const filteredValueList = [];
- const keyList = [];
-
- // Get latest value list
- let latestValueList = newState.valueList;
- if (!latestValueList) {
- // Also need add prev missValueList to avoid new treeNodes contains the value
- latestValueList = [...prevState.valueList, ...prevState.missValueList];
- }
-
- // Get key by value
- latestValueList.forEach(wrapperValue => {
- const { value } = wrapperValue;
- const entity = (newState.valueEntities || prevState.valueEntities)[value];
-
- if (entity) {
- keyList.push(entity.key);
- filteredValueList.push(wrapperValue);
- return;
- }
-
- // If not match, it may caused by ajax load. We need keep this
- missValueList.push(wrapperValue);
- });
-
- // We need calculate the value when tree is checked tree
- if (treeCheckable && !treeCheckStrictly) {
- // Calculate the keys need to be checked
- const { checkedKeys } = conductCheck(
- keyList,
- true,
- newState.keyEntities || prevState.keyEntities,
- );
-
- // Format value list again for internal usage
- newState.valueList = checkedKeys.map(key => ({
- value: (newState.keyEntities || prevState.keyEntities)[key].value,
- }));
- } else {
- newState.valueList = filteredValueList;
- }
-
- // Fill the missValueList, we still need display in the selector
- newState.missValueList = missValueList;
-
- // Calculate the value list for `Selector` usage
- newState.selectorValueList = formatSelectorValue(
- newState.valueList,
- nextProps,
- newState.valueEntities || prevState.valueEntities,
- );
- }
-
- // [Legacy] To align with `Select` component,
- // We use `searchValue` instead of `inputValue` but still keep the api
- // `inputValue` support `null` to work as `autoClearSearchValue`
- processState('inputValue', propValue => {
- if (propValue !== null) {
- newState.searchValue = propValue;
- }
- });
-
- // Search value
- processState('searchValue', propValue => {
- newState.searchValue = propValue;
- });
-
- // Do the search logic
- if (newState.searchValue !== undefined || (prevState.searchValue && treeNodes)) {
- const searchValue =
- newState.searchValue !== undefined ? newState.searchValue : prevState.searchValue;
- const upperSearchValue = String(searchValue).toUpperCase();
-
- let filterTreeNodeFn = filterTreeNode;
- if (filterTreeNode === false) {
- // Don't filter if is false
- filterTreeNodeFn = () => true;
- } else if (typeof filterTreeNodeFn !== 'function') {
- // When is not function (true or undefined), use inner filter
- filterTreeNodeFn = (_, node) => {
- const nodeValue = String(node.props[treeNodeFilterProp]).toUpperCase();
- return nodeValue.indexOf(upperSearchValue) !== -1;
- };
- }
-
- newState.filteredTreeNodes = getFilterTree(
- newState.treeNodes || prevState.treeNodes,
- searchValue,
- filterTreeNodeFn,
- newState.valueEntities || prevState.valueEntities,
- SelectNode,
- );
- }
-
- // We should re-calculate the halfCheckedKeys when in search mode
- if (
- valueRefresh &&
- treeCheckable &&
- !treeCheckStrictly &&
- (newState.searchValue || prevState.searchValue)
- ) {
- newState.searchHalfCheckedKeys = getHalfCheckedKeys(
- newState.valueList,
- newState.valueEntities || prevState.valueEntities,
- );
- }
-
- // Checked Strategy
- processState('showCheckedStrategy', () => {
- newState.selectorValueList =
- newState.selectorValueList ||
- formatSelectorValue(
- newState.valueList || prevState.valueList,
- nextProps,
- newState.valueEntities || prevState.valueEntities,
- );
- });
-
- return newState;
- }
-
- componentDidMount() {
- const { autoFocus, disabled } = this.props;
-
- if (autoFocus && !disabled) {
- this.focus();
- }
- }
-
- componentDidUpdate(_, prevState) {
- const { prefixCls } = this.props;
- const { valueList, open, selectorValueList, valueEntities } = this.state;
- const isMultiple = this.isMultiple();
-
- if (prevState.valueList !== valueList) {
- this.forcePopupAlign();
- }
-
- // Scroll to value position, only need sync on single mode
- if (!isMultiple && selectorValueList.length && !prevState.open && open && this.popup) {
- const { value } = selectorValueList[0];
- const { domTreeNodes } = this.popup.getTree();
- const { key } = valueEntities[value] || {};
- const treeNode = domTreeNodes[key];
-
- if (treeNode) {
- const domNode = findDOMNode(treeNode);
- raf(() => {
- const popupNode = findDOMNode(this.popup);
- const triggerContainer = findPopupContainer(popupNode, `${prefixCls}-dropdown`);
- const searchNode = this.popup.searchRef.current;
-
- if (domNode && triggerContainer && searchNode) {
- scrollIntoView(domNode, triggerContainer, {
- onlyScrollIfNeeded: true,
- offsetTop: searchNode.offsetHeight,
- });
- }
- });
- }
- }
- }
-
- // ==================== Selector ====================
- onSelectorFocus = () => {
- this.setState({ focused: true });
- };
-
- onSelectorBlur = () => {
- this.setState({ focused: false });
-
- // TODO: Close when Popup is also not focused
- // this.setState({ open: false });
- };
-
- // Handle key board event in both Selector and Popup
- onComponentKeyDown = event => {
- const { open } = this.state;
- const { keyCode } = event;
-
- if (!open) {
- if ([KeyCode.ENTER, KeyCode.DOWN].indexOf(keyCode) !== -1) {
- this.setOpenState(true);
- }
- } else if (KeyCode.ESC === keyCode) {
- this.setOpenState(false);
- } else if ([KeyCode.UP, KeyCode.DOWN, KeyCode.LEFT, KeyCode.RIGHT].indexOf(keyCode) !== -1) {
- // TODO: Handle `open` state
- event.stopPropagation();
- }
- };
-
- onDeselect = (wrappedValue, node, nodeEventInfo) => {
- const { onDeselect } = this.props;
- if (!onDeselect) return;
-
- onDeselect(wrappedValue, node, nodeEventInfo);
- };
-
- onSelectorClear = event => {
- const { disabled } = this.props;
- if (disabled) return;
-
- this.triggerChange([], []);
-
- if (!this.isSearchValueControlled()) {
- this.setUncontrolledState({
- searchValue: '',
- filteredTreeNodes: null,
- });
- }
-
- event.stopPropagation();
- };
-
- onMultipleSelectorRemove = (event, removeValue) => {
- event.stopPropagation();
-
- const { valueList, missValueList, valueEntities } = this.state;
-
- const { treeCheckable, treeCheckStrictly, treeNodeLabelProp, disabled } = this.props;
- if (disabled) return;
-
- // Find trigger entity
- const triggerEntity = valueEntities[removeValue];
-
- // Clean up value
- let newValueList = valueList;
- if (triggerEntity) {
- // If value is in tree
- if (treeCheckable && !treeCheckStrictly) {
- newValueList = valueList.filter(({ value }) => {
- const entity = valueEntities[value];
- return !isPosRelated(entity.pos, triggerEntity.pos);
- });
- } else {
- newValueList = valueList.filter(({ value }) => value !== removeValue);
- }
- }
-
- const triggerNode = triggerEntity ? triggerEntity.node : null;
-
- const extraInfo = {
- triggerValue: removeValue,
- triggerNode,
- };
- const deselectInfo = {
- node: triggerNode,
- };
-
- // [Legacy] Little hack on this to make same action as `onCheck` event.
- if (treeCheckable) {
- const filteredEntityList = newValueList.map(({ value }) => valueEntities[value]);
-
- deselectInfo.event = 'check';
- deselectInfo.checked = false;
- deselectInfo.checkedNodes = filteredEntityList.map(({ node }) => node);
- deselectInfo.checkedNodesPositions = filteredEntityList.map(({ node, pos }) => ({
- node,
- pos,
- }));
-
- if (treeCheckStrictly) {
- extraInfo.allCheckedNodes = deselectInfo.checkedNodes;
- } else {
- // TODO: It's too expansive to get `halfCheckedKeys` in onDeselect. Not pass this.
- extraInfo.allCheckedNodes = flatToHierarchy(filteredEntityList).map(({ node }) => node);
- }
- } else {
- deselectInfo.event = 'select';
- deselectInfo.selected = false;
- deselectInfo.selectedNodes = newValueList.map(
- ({ value }) => (valueEntities[value] || {}).node,
- );
- }
-
- // Some value user pass prop is not in the tree, we also need clean it
- const newMissValueList = missValueList.filter(({ value }) => value !== removeValue);
-
- let wrappedValue;
- if (this.isLabelInValue()) {
- wrappedValue = {
- label: triggerNode ? triggerNode.props[treeNodeLabelProp] : null,
- value: removeValue,
- };
- } else {
- wrappedValue = removeValue;
- }
-
- this.onDeselect(wrappedValue, triggerNode, deselectInfo);
-
- this.triggerChange(newMissValueList, newValueList, extraInfo);
- };
-
- // ===================== Popup ======================
- onValueTrigger = (isAdd, nodeList, nodeEventInfo, nodeExtraInfo) => {
- const { node } = nodeEventInfo;
- const { value } = node.props;
- const { missValueList, valueEntities, keyEntities, searchValue } = this.state;
- const {
- disabled,
- inputValue,
- treeNodeLabelProp,
- onSelect,
- onSearch,
- multiple,
- treeCheckable,
- treeCheckStrictly,
- autoClearSearchValue,
- } = this.props;
- const label = node.props[treeNodeLabelProp];
-
- if (disabled) return;
-
- // Wrap the return value for user
- let wrappedValue;
- if (this.isLabelInValue()) {
- wrappedValue = {
- value,
- label,
- };
- } else {
- wrappedValue = value;
- }
-
- // [Legacy] Origin code not trigger `onDeselect` every time. Let's align the behaviour.
- if (isAdd) {
- if (onSelect) {
- onSelect(wrappedValue, node, nodeEventInfo);
- }
- } else {
- this.onDeselect(wrappedValue, node, nodeEventInfo);
- }
-
- // Get wrapped value list.
- // This is a bit hack cause we use key to match the value.
- let newValueList = nodeList.map(({ props }) => ({
- value: props.value,
- label: props[treeNodeLabelProp],
- }));
-
- // When is `treeCheckable` and with `searchValue`, `valueList` is not full filled.
- // We need calculate the missing nodes.
- if (treeCheckable && !treeCheckStrictly) {
- let keyList = newValueList.map(({ value: val }) => valueEntities[val].key);
- if (isAdd) {
- keyList = conductCheck(keyList, true, keyEntities).checkedKeys;
- } else {
- keyList = conductCheck([valueEntities[value].key], false, keyEntities, {
- checkedKeys: keyList,
- }).checkedKeys;
- }
- newValueList = keyList.map(key => {
- const {
- node: { props },
- } = keyEntities[key];
- return {
- value: props.value,
- label: props[treeNodeLabelProp],
- };
- });
- }
-
- // Clean up `searchValue` when this prop is set
- if (autoClearSearchValue || inputValue === null) {
- // Clean state `searchValue` if uncontrolled
- if (!this.isSearchValueControlled() && (multiple || treeCheckable)) {
- this.setUncontrolledState({
- searchValue: '',
- filteredTreeNodes: null,
- });
- }
-
- // Trigger onSearch if `searchValue` to be empty.
- // We should also trigger onSearch with empty string here
- // since if user use `treeExpandedKeys`, it need user have the ability to reset it.
- if (onSearch && searchValue && searchValue.length) {
- onSearch('');
- }
- }
-
- // [Legacy] Provide extra info
- const extraInfo = {
- ...nodeExtraInfo,
- triggerValue: value,
- triggerNode: node,
- };
-
- this.triggerChange(missValueList, newValueList, extraInfo);
- };
-
- onTreeNodeSelect = (_, nodeEventInfo) => {
- const { valueList, valueEntities } = this.state;
- const { treeCheckable, multiple } = this.props;
- if (treeCheckable) return;
-
- if (!multiple) {
- this.setOpenState(false);
- }
-
- const isAdd = nodeEventInfo.selected;
- const {
- props: { value: selectedValue },
- } = nodeEventInfo.node;
-
- let newValueList;
-
- if (!multiple) {
- newValueList = [{ value: selectedValue }];
- } else {
- newValueList = valueList.filter(({ value }) => value !== selectedValue);
- if (isAdd) {
- newValueList.push({ value: selectedValue });
- }
- }
-
- const selectedNodes = newValueList
- .map(({ value }) => valueEntities[value])
- .filter(entity => entity)
- .map(({ node }) => node);
-
- this.onValueTrigger(isAdd, selectedNodes, nodeEventInfo, { selected: isAdd });
- };
-
- onTreeNodeCheck = (_, nodeEventInfo) => {
- const { searchValue, keyEntities, valueEntities, valueList } = this.state;
- const { treeCheckStrictly } = this.props;
-
- const { checkedNodes, checkedNodesPositions } = nodeEventInfo;
- const isAdd = nodeEventInfo.checked;
-
- const extraInfo = {
- checked: isAdd,
- };
-
- let checkedNodeList = checkedNodes;
-
- // [Legacy] Check event provide `allCheckedNodes`.
- // When `treeCheckStrictly` or internal `searchValue` is set, TreeNode will be unrelated:
- // - Related: Show the top checked nodes and has children prop.
- // - Unrelated: Show all the checked nodes.
- if (searchValue) {
- const oriKeyList = valueList
- .map(({ value }) => valueEntities[value])
- .filter(entity => entity)
- .map(({ key }) => key);
-
- let keyList;
- if (isAdd) {
- keyList = Array.from(
- new Set([
- ...oriKeyList,
- ...checkedNodeList.map(({ props: { value } }) => valueEntities[value].key),
- ]),
- );
- } else {
- keyList = conductCheck([nodeEventInfo.node.props.eventKey], false, keyEntities, {
- checkedKeys: oriKeyList,
- }).checkedKeys;
- }
-
- checkedNodeList = keyList.map(key => keyEntities[key].node);
-
- // Let's follow as not `treeCheckStrictly` format
- extraInfo.allCheckedNodes = keyList.map(key => cleanEntity(keyEntities[key]));
- } else if (treeCheckStrictly) {
- extraInfo.allCheckedNodes = nodeEventInfo.checkedNodes;
- } else {
- extraInfo.allCheckedNodes = flatToHierarchy(checkedNodesPositions);
- }
-
- this.onValueTrigger(isAdd, checkedNodeList, nodeEventInfo, extraInfo);
- };
-
- // ==================== Trigger =====================
-
- onDropdownVisibleChange = open => {
- const { multiple, treeCheckable } = this.props;
- const { searchValue } = this.state;
-
- // When set open success and single mode,
- // we will reset the input content.
- if (open && !multiple && !treeCheckable && searchValue) {
- this.setUncontrolledState({
- searchValue: '',
- filteredTreeNodes: null,
- });
- }
-
- this.setOpenState(open, true);
- };
-
- onSearchInputChange = ({ target: { value } }) => {
- const { treeNodes, valueEntities } = this.state;
- const { onSearch, filterTreeNode, treeNodeFilterProp } = this.props;
-
- if (onSearch) {
- onSearch(value);
- }
-
- let isSet = false;
-
- if (!this.isSearchValueControlled()) {
- isSet = this.setUncontrolledState({
- searchValue: value,
- });
- this.setOpenState(true);
- }
-
- if (isSet) {
- // Do the search logic
- const upperSearchValue = String(value).toUpperCase();
-
- let filterTreeNodeFn = filterTreeNode;
- if (filterTreeNode === false) {
- filterTreeNodeFn = () => true;
- } else if (!filterTreeNodeFn) {
- filterTreeNodeFn = (_, node) => {
- const nodeValue = String(node.props[treeNodeFilterProp]).toUpperCase();
- return nodeValue.indexOf(upperSearchValue) !== -1;
- };
- }
-
- this.setState({
- filteredTreeNodes: getFilterTree(
- treeNodes,
- value,
- filterTreeNodeFn,
- valueEntities,
- SelectNode,
- ),
- });
- }
- };
-
- onSearchInputKeyDown = event => {
- const { searchValue, valueList } = this.state;
-
- const { keyCode } = event;
-
- if (KeyCode.BACKSPACE === keyCode && this.isMultiple() && !searchValue && valueList.length) {
- const lastValue = valueList[valueList.length - 1].value;
- this.onMultipleSelectorRemove(event, lastValue);
- }
- };
-
- onChoiceAnimationLeave = () => {
- raf(() => {
- this.forcePopupAlign();
- });
- };
-
- setPopupRef = popup => {
- this.popup = popup;
- };
-
- /**
- * Only update the value which is not in props
- */
- setUncontrolledState = state => {
- let needSync = false;
- const newState = {};
-
- Object.keys(state).forEach(name => {
- if (name in this.props) return;
-
- needSync = true;
- newState[name] = state[name];
- });
-
- if (needSync) {
- this.setState(newState);
- }
-
- return needSync;
- };
-
- // [Legacy] Origin provide `documentClickClose` which triggered by `Trigger`
- // Currently `TreeSelect` align the hide popup logic as `Select` which blur to hide.
- // `documentClickClose` is not accurate anymore. Let's just keep the key word.
- setOpenState = (open, byTrigger = false) => {
- const { onDropdownVisibleChange } = this.props;
-
- if (
- onDropdownVisibleChange &&
- onDropdownVisibleChange(open, { documentClickClose: !open && byTrigger }) === false
- ) {
- return;
- }
-
- this.setUncontrolledState({ open });
- };
-
- // Tree checkable is also a multiple case
- isMultiple = () => {
- const { multiple, treeCheckable } = this.props;
- return !!(multiple || treeCheckable);
- };
-
- isLabelInValue = () => {
- return isLabelInValue(this.props);
- };
-
- // [Legacy] To align with `Select` component,
- // We use `searchValue` instead of `inputValue`
- // but currently still need support that.
- // Add this method the check if is controlled
- isSearchValueControlled = () => {
- const { inputValue } = this.props;
- if ('searchValue' in this.props) return true;
- return 'inputValue' in this.props && inputValue !== null;
- };
-
- forcePopupAlign = () => {
- const $trigger = this.selectTriggerRef.current;
-
- if ($trigger) {
- $trigger.forcePopupAlign();
- }
- };
-
- delayForcePopupAlign = () => {
- // Wait 2 frame to avoid dom update & dom algin in the same time
- // https://github.com/ant-design/ant-design/issues/12031
- raf(() => {
- raf(this.forcePopupAlign);
- });
- };
-
- /**
- * 1. Update state valueList.
- * 2. Fire `onChange` event to user.
- */
- triggerChange = (missValueList, valueList, extraInfo = {}) => {
- const { valueEntities, searchValue, selectorValueList: prevSelectorValueList } = this.state;
- const { onChange, disabled, treeCheckable, treeCheckStrictly } = this.props;
-
- if (disabled) return;
-
- // Trigger
- const extra = {
- // [Legacy] Always return as array contains label & value
- preValue: prevSelectorValueList.map(({ label, value }) => ({ label, value })),
- ...extraInfo,
- };
-
- // Format value by `treeCheckStrictly`
- const selectorValueList = formatSelectorValue(valueList, this.props, valueEntities);
-
- if (!('value' in this.props)) {
- const newState = {
- missValueList,
- valueList,
- selectorValueList,
- };
-
- if (searchValue && treeCheckable && !treeCheckStrictly) {
- newState.searchHalfCheckedKeys = getHalfCheckedKeys(valueList, valueEntities);
- }
-
- this.setState(newState);
- }
-
- // Only do the logic when `onChange` function provided
- if (onChange) {
- let connectValueList;
-
- // Get value by mode
- if (this.isMultiple()) {
- connectValueList = [...missValueList, ...selectorValueList];
- } else {
- connectValueList = selectorValueList.slice(0, 1);
- }
-
- let labelList = null;
- let returnValue;
-
- if (this.isLabelInValue()) {
- returnValue = connectValueList.map(({ label, value }) => ({ label, value }));
- } else {
- labelList = [];
- returnValue = connectValueList.map(({ label, value }) => {
- labelList.push(label);
- return value;
- });
- }
-
- if (!this.isMultiple()) {
- returnValue = returnValue[0];
- }
-
- onChange(returnValue, labelList, extra);
- }
- };
-
- focus() {
- this.selectorRef.current.focus();
- }
-
- blur() {
- this.selectorRef.current.blur();
- }
-
- // ===================== Render =====================
-
- render() {
- const {
- valueList,
- missValueList,
- selectorValueList,
- searchHalfCheckedKeys,
- valueEntities,
- keyEntities,
- searchValue,
- open,
- focused,
- treeNodes,
- filteredTreeNodes,
- } = this.state;
- const { prefixCls, treeExpandedKeys, onTreeExpand } = this.props;
- const isMultiple = this.isMultiple();
-
- const passProps = {
- ...this.props,
- isMultiple,
- valueList,
- searchHalfCheckedKeys,
- selectorValueList: [...missValueList, ...selectorValueList],
- valueEntities,
- keyEntities,
- searchValue,
- upperSearchValue: (searchValue || '').toUpperCase(), // Perf save
- open,
- focused,
- onChoiceAnimationLeave: this.onChoiceAnimationLeave,
- dropdownPrefixCls: `${prefixCls}-dropdown`,
- ariaId: this.ariaId,
- };
-
- const Popup = isMultiple ? MultiplePopup : SinglePopup;
- const $popup = (
-
- );
-
- const Selector = isMultiple ? MultipleSelector : SingleSelector;
- const $selector = ;
-
- return (
-
- {$selector}
-
- );
- }
-}
-
-Select.TreeNode = SelectNode;
-Select.SHOW_ALL = SHOW_ALL;
-Select.SHOW_PARENT = SHOW_PARENT;
-Select.SHOW_CHILD = SHOW_CHILD;
-
-// Let warning show correct component name
-Select.displayName = 'TreeSelect';
-
-polyfill(Select);
-
-export default Select;
diff --git a/src/SelectNode.jsx b/src/SelectNode.jsx
deleted file mode 100644
index edbefa83..00000000
--- a/src/SelectNode.jsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-import { TreeNode } from 'rc-tree';
-import { valueProp } from './propTypes';
-
-/**
- * SelectNode wrapped the tree node.
- * Let's use SelectNode instead of TreeNode
- * since TreeNode is so confuse here.
- */
-const SelectNode = (props) => (
-
-);
-
-SelectNode.propTypes = {
- ...TreeNode.propTypes,
- value: valueProp,
-};
-
-// Let Tree trade as TreeNode to reuse this for performance saving.
-SelectNode.isTreeNode = 1;
-
-export default SelectNode;
diff --git a/src/SelectTrigger.jsx b/src/SelectTrigger.jsx
deleted file mode 100644
index 0eef6305..00000000
--- a/src/SelectTrigger.jsx
+++ /dev/null
@@ -1,125 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { polyfill } from 'react-lifecycles-compat';
-import Trigger from 'rc-trigger';
-import classNames from 'classnames';
-
-import { createRef } from './util';
-
-const BUILT_IN_PLACEMENTS = {
- bottomLeft: {
- points: ['tl', 'bl'],
- offset: [0, 4],
- overflow: {
- adjustX: 0,
- adjustY: 1,
- },
- ignoreShake: true,
- },
- topLeft: {
- points: ['bl', 'tl'],
- offset: [0, -4],
- overflow: {
- adjustX: 0,
- adjustY: 1,
- },
- ignoreShake: true,
- },
-};
-
-class SelectTrigger extends React.Component {
- static propTypes = {
- // Pass by outside user props
- disabled: PropTypes.bool,
- showSearch: PropTypes.bool,
- prefixCls: PropTypes.string,
- dropdownPopupAlign: PropTypes.object,
- dropdownClassName: PropTypes.string,
- dropdownStyle: PropTypes.object,
- transitionName: PropTypes.string,
- animation: PropTypes.string,
- getPopupContainer: PropTypes.func,
- children: PropTypes.node,
-
- dropdownMatchSelectWidth: PropTypes.bool,
-
- // Pass by Select
- isMultiple: PropTypes.bool,
- dropdownPrefixCls: PropTypes.string,
- onDropdownVisibleChange: PropTypes.func,
- popupElement: PropTypes.node,
- open: PropTypes.bool,
- };
-
- constructor() {
- super();
-
- this.triggerRef = createRef();
- }
-
- getDropdownTransitionName = () => {
- const { transitionName, animation, dropdownPrefixCls } = this.props;
- if (!transitionName && animation) {
- return `${dropdownPrefixCls}-${animation}`;
- }
- return transitionName;
- };
-
- forcePopupAlign = () => {
- const $trigger = this.triggerRef.current;
-
- if ($trigger) {
- $trigger.forcePopupAlign();
- }
- };
-
- render() {
- const {
- disabled, isMultiple,
- dropdownPopupAlign, dropdownMatchSelectWidth, dropdownClassName,
- dropdownStyle, onDropdownVisibleChange, getPopupContainer,
- dropdownPrefixCls, popupElement, open,
- children,
- } = this.props;
-
- // TODO: [Legacy] Use new action when trigger fixed: https://github.com/react-component/trigger/pull/86
-
- // When false do nothing with the width
- // ref: https://github.com/ant-design/ant-design/issues/10927
- let stretch;
- if (dropdownMatchSelectWidth !== false) {
- stretch = dropdownMatchSelectWidth ? 'width' : 'minWidth';
- }
-
- return (
-
- {children}
-
- );
- }
-}
-
-polyfill(SelectTrigger);
-
-export default SelectTrigger;
diff --git a/src/Selector/MultipleSelector/Selection.jsx b/src/Selector/MultipleSelector/Selection.jsx
deleted file mode 100644
index 64670966..00000000
--- a/src/Selector/MultipleSelector/Selection.jsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import classNames from 'classnames';
-import { toTitle, UNSELECTABLE_ATTRIBUTE, UNSELECTABLE_STYLE } from '../../util';
-
-class Selection extends React.Component {
- static propTypes = {
- prefixCls: PropTypes.string,
- maxTagTextLength: PropTypes.number,
- onRemove: PropTypes.func,
- className: PropTypes.string,
- style: PropTypes.object,
-
- label: PropTypes.node,
- value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
- removeIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
- };
-
- onRemove = event => {
- const { onRemove, value } = this.props;
- onRemove(event, value);
-
- event.stopPropagation();
- };
-
- render() {
- const {
- prefixCls,
- maxTagTextLength,
- className,
- style,
- label,
- value,
- onRemove,
- removeIcon,
- } = this.props;
-
- let content = label || value;
- if (maxTagTextLength && typeof content === 'string' && content.length > maxTagTextLength) {
- content = `${content.slice(0, maxTagTextLength)}...`;
- }
-
- return (
-
- {onRemove && (
-
- {typeof removeIcon === 'function'
- ? React.createElement(removeIcon, { ...this.props })
- : removeIcon}
-
- )}
- {content}
-
- );
- }
-}
-
-export default Selection;
diff --git a/src/Selector/MultipleSelector/SelectorList.jsx b/src/Selector/MultipleSelector/SelectorList.jsx
deleted file mode 100644
index 283365bb..00000000
--- a/src/Selector/MultipleSelector/SelectorList.jsx
+++ /dev/null
@@ -1,121 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import CSSMotionList from 'rc-animate/lib/CSSMotionList';
-import Selection from './Selection';
-import SearchInput from '../../SearchInput';
-
-const NODE_SELECTOR = 'selector';
-const NODE_SEARCH = 'search';
-const TREE_SELECT_EMPTY_VALUE_KEY = 'RC_TREE_SELECT_EMPTY_VALUE_KEY';
-
-const SelectorList = props => {
- const {
- selectorValueList,
- choiceTransitionName,
- prefixCls,
- onChoiceAnimationLeave,
- labelInValue,
- maxTagCount,
- maxTagPlaceholder,
- showSearch,
- valueEntities,
- inputRef,
- onMultipleSelectorRemove,
- } = props;
- const nodeKeys = [];
-
- // Check if `maxTagCount` is set
- let myValueList = selectorValueList;
- if (maxTagCount >= 0) {
- myValueList = selectorValueList.slice(0, maxTagCount);
- }
-
- // Basic selectors
- myValueList.forEach(({ label, value }) => {
- const { props: { disabled } = {} } = (valueEntities[value] || {}).node || {};
- nodeKeys.push({
- key: value,
- type: NODE_SELECTOR,
- label,
- value,
- disabled,
- });
- });
-
- // Rest node count
- if (maxTagCount >= 0 && maxTagCount < selectorValueList.length) {
- let content = `+ ${selectorValueList.length - maxTagCount} ...`;
- if (typeof maxTagPlaceholder === 'string') {
- content = maxTagPlaceholder;
- } else if (typeof maxTagPlaceholder === 'function') {
- const restValueList = selectorValueList.slice(maxTagCount);
- content = maxTagPlaceholder(
- labelInValue ? restValueList : restValueList.map(({ value }) => value),
- );
- }
-
- nodeKeys.push({
- key: 'rc-tree-select-internal-max-tag-counter',
- type: NODE_SELECTOR,
- label: content,
- value: null,
- disabled: true,
- });
- }
-
- // Search node
- if (showSearch !== false) {
- nodeKeys.push({
- key: '__input',
- type: NODE_SEARCH,
- });
- }
-
- return (
-
- {({ type, label, value, disabled, className, style }) => {
- if (type === NODE_SELECTOR) {
- return (
-
- );
- }
- return (
-
-
-
- );
- }}
-
- );
-};
-
-SelectorList.propTypes = {
- selectorValueList: PropTypes.array,
- choiceTransitionName: PropTypes.string,
- prefixCls: PropTypes.string,
- onChoiceAnimationLeave: PropTypes.func,
- labelInValue: PropTypes.bool,
- showSearch: PropTypes.bool,
- maxTagCount: PropTypes.number,
- maxTagPlaceholder: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
- valueEntities: PropTypes.object,
- inputRef: PropTypes.func,
- onMultipleSelectorRemove: PropTypes.func,
-};
-
-export default SelectorList;
diff --git a/src/Selector/MultipleSelector/index.jsx b/src/Selector/MultipleSelector/index.jsx
deleted file mode 100644
index 47d6ad07..00000000
--- a/src/Selector/MultipleSelector/index.jsx
+++ /dev/null
@@ -1,108 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import generateSelector, { selectorPropTypes } from '../../Base/BaseSelector';
-import { createRef } from '../../util';
-import SelectorList from './SelectorList';
-
-const Selector = generateSelector('multiple');
-
-export const multipleSelectorContextTypes = {
- onMultipleSelectorRemove: PropTypes.func.isRequired,
-};
-
-class MultipleSelector extends React.Component {
- static propTypes = {
- ...selectorPropTypes,
- selectorValueList: PropTypes.array,
- disabled: PropTypes.bool,
- searchValue: PropTypes.string,
- labelInValue: PropTypes.bool,
- maxTagCount: PropTypes.number,
- maxTagPlaceholder: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
-
- onChoiceAnimationLeave: PropTypes.func,
- };
-
- static contextTypes = {
- rcTreeSelect: PropTypes.shape({
- ...multipleSelectorContextTypes,
-
- onSearchInputChange: PropTypes.func,
- }),
- };
-
- constructor() {
- super();
- this.inputRef = createRef();
- }
-
- onPlaceholderClick = () => {
- this.inputRef.current.focus();
- };
-
- focus = () => {
- this.inputRef.current.focus();
- };
-
- blur = () => {
- this.inputRef.current.blur();
- };
-
- renderPlaceholder = () => {
- const {
- prefixCls,
- placeholder,
- searchPlaceholder,
- searchValue,
- selectorValueList,
- } = this.props;
-
- const currentPlaceholder = placeholder || searchPlaceholder;
-
- if (!currentPlaceholder) return null;
-
- const hidden = searchValue || selectorValueList.length;
-
- // [Legacy] Not remove the placeholder
- return (
-
- {currentPlaceholder}
-
- );
- };
-
- renderSelection = () => {
- const {
- rcTreeSelect: { onMultipleSelectorRemove },
- } = this.context;
-
- return (
-
- );
- };
-
- render() {
- return (
-
- );
- }
-}
-
-export default MultipleSelector;
diff --git a/src/Selector/SingleSelector.jsx b/src/Selector/SingleSelector.jsx
deleted file mode 100644
index 37c79e0d..00000000
--- a/src/Selector/SingleSelector.jsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import React from 'react';
-import generateSelector, { selectorPropTypes } from '../Base/BaseSelector';
-import { toTitle, createRef } from '../util';
-
-const Selector = generateSelector('single');
-
-class SingleSelector extends React.Component {
- static propTypes = {
- ...selectorPropTypes,
- };
-
- constructor() {
- super();
- this.selectorRef = createRef();
- }
-
- focus = () => {
- this.selectorRef.current.focus();
- };
-
- blur = () => {
- this.selectorRef.current.blur();
- };
-
- renderSelection = () => {
- const { selectorValueList, placeholder, prefixCls } = this.props;
-
- let innerNode;
-
- if (selectorValueList.length) {
- const { label, value } = selectorValueList[0];
- innerNode = (
-
- {label || value}
-
- );
- } else {
- innerNode = (
-
- {placeholder}
-
- );
- }
-
- return {innerNode};
- };
-
- render() {
- return (
-
- );
- }
-}
-
-export default SingleSelector;
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
new file mode 100644
index 00000000..424d5b44
--- /dev/null
+++ b/src/TreeNode.tsx
@@ -0,0 +1,13 @@
+/* istanbul ignore file */
+import React from 'react';
+import { DataNode, Key } from './interface';
+
+export interface TreeNodeProps extends Omit {
+ value: Key;
+ children?: React.ReactNode;
+}
+
+/** This is a placeholder, not real render in dom */
+const TreeNode: React.FC = () => null;
+
+export default TreeNode;
diff --git a/src/TreeSelect.tsx b/src/TreeSelect.tsx
new file mode 100644
index 00000000..616d9151
--- /dev/null
+++ b/src/TreeSelect.tsx
@@ -0,0 +1,546 @@
+import React from 'react';
+import generateSelector, { SelectProps, RefSelectProps } from 'rc-select/lib/generate';
+import { getLabeledValue } from 'rc-select/lib/utils/valueUtil';
+import { convertDataToEntities } from 'rc-tree/lib/utils/treeUtil';
+import { conductCheck } from 'rc-tree/lib/utils/conductUtil';
+import { IconType } from 'rc-tree/lib/interface';
+import { FilterFunc, INTERNAL_PROPS_MARK } from 'rc-select/lib/interface/generator';
+import warning from 'rc-util/lib/warning';
+import OptionList from './OptionList';
+import TreeNode from './TreeNode';
+import {
+ Key,
+ DefaultValueType,
+ DataNode,
+ LabelValueType,
+ SimpleModeConfig,
+ RawValueType,
+ ChangeEventExtra,
+ LegacyDataNode,
+ SelectSource,
+} from './interface';
+import {
+ flattenOptions,
+ filterOptions,
+ isValueDisabled,
+ findValueOption,
+ addValue,
+ removeValue,
+ getRawValueLabeled,
+ toArray,
+} from './utils/valueUtil';
+import warningProps from './utils/warningPropsUtil';
+import { SelectContext } from './Context';
+import useTreeData from './hooks/useTreeData';
+import useKeyValueMap from './hooks/useKeyValueMap';
+import useKeyValueMapping from './hooks/useKeyValueMapping';
+import {
+ CheckedStrategy,
+ formatStrategyKeys,
+ SHOW_ALL,
+ SHOW_PARENT,
+ SHOW_CHILD,
+} from './utils/strategyUtil';
+import { fillAdditionalInfo } from './utils/legacyUtil';
+import useSelectValues from './hooks/useSelectValues';
+
+const OMIT_PROPS = [
+ 'expandedKeys',
+ 'treeData',
+ 'treeCheckable',
+ 'showCheckedStrategy',
+ 'searchPlaceholder',
+ 'treeLine',
+ 'treeNodeFilterProp',
+ 'filterTreeNode',
+ 'dropdownPopupAlign',
+ 'treeDefaultExpandAll',
+ 'treeCheckStrictly',
+ 'treeExpandedKeys',
+ 'treeLoadedKeys',
+ 'onTreeExpand',
+ 'onTreeLoad',
+ 'loadData',
+ 'treeDataSimpleMode',
+ 'treeNodeLabelProp',
+ 'treeDefaultExpandedKeys',
+];
+
+const RefSelect = generateSelector({
+ prefixCls: 'rc-tree-select',
+ components: {
+ optionList: OptionList,
+ },
+ // Not use generate since we will handle ourself
+ convertChildrenToData: () => null,
+ flattenOptions,
+ // Handle `optionLabelProp` in TreeSelect component
+ getLabeledValue: getLabeledValue as any,
+ filterOptions,
+ isValueDisabled,
+ findValueOption,
+ omitDOMProps: (props: object) => {
+ const cloneProps = { ...props };
+ OMIT_PROPS.forEach(prop => {
+ delete cloneProps[prop];
+ });
+ return cloneProps;
+ },
+});
+
+RefSelect.displayName = 'Select';
+
+export interface TreeSelectProps
+ extends Omit<
+ SelectProps,
+ | 'onChange'
+ | 'mode'
+ | 'menuItemSelectedIcon'
+ | 'dropdownRender'
+ | 'dropdownAlign'
+ | 'backfill'
+ | 'getInputElement'
+ | 'optionLabelProp'
+ | 'tokenSeparators'
+ | 'filterOption'
+ > {
+ multiple?: boolean;
+ showArrow?: boolean;
+ showSearch?: boolean;
+ open?: boolean;
+ defaultOpen?: boolean;
+ value?: ValueType;
+ defaultValue?: ValueType;
+ disabled?: boolean;
+
+ placeholder?: React.ReactNode;
+ /** @deprecated Use `searchValue` instead */
+ inputValue?: string;
+ searchValue?: string;
+ autoClearSearchValue?: boolean;
+
+ maxTagTextLength?: number;
+ maxTagCount?: number;
+ maxTagPlaceholder?: (omittedValues: LabelValueType[]) => React.ReactNode;
+
+ loadData?: (dataNode: LegacyDataNode) => Promise;
+ treeNodeFilterProp?: string;
+ treeNodeLabelProp?: string;
+ treeDataSimpleMode?: boolean | SimpleModeConfig;
+ treeExpandedKeys?: Key[];
+ treeDefaultExpandedKeys?: Key[];
+ treeLoadedKeys?: Key[];
+ treeCheckable?: boolean | React.ReactNode;
+ treeCheckStrictly?: boolean;
+ showCheckedStrategy?: CheckedStrategy;
+ treeDefaultExpandAll?: boolean;
+ treeData?: DataNode[];
+ treeLine?: boolean;
+ treeIcon?: IconType;
+ switcherIcon?: IconType;
+ children?: React.ReactNode;
+
+ filterTreeNode?: boolean | FilterFunc;
+ dropdownPopupAlign?: any;
+
+ // Event
+ onSearch?: (value: string) => void;
+ onChange?: (value: ValueType, labelList: React.ReactNode[], extra: ChangeEventExtra) => void;
+ onTreeExpand?: (expandedKeys: Key[]) => void;
+ onTreeLoad?: (loadedKeys: Key[]) => void;
+
+ // Legacy
+ /** `searchPlaceholder` has been removed since search box has been merged into input box */
+ searchPlaceholder?: React.ReactNode;
+}
+
+const RefTreeSelect = React.forwardRef((props, ref) => {
+ const {
+ multiple,
+ treeCheckable,
+ treeCheckStrictly,
+ showCheckedStrategy = 'SHOW_CHILD',
+ labelInValue,
+ loadData,
+ treeLoadedKeys,
+ treeNodeFilterProp = 'value',
+ treeNodeLabelProp,
+ treeDataSimpleMode,
+ treeData,
+ treeExpandedKeys,
+ treeDefaultExpandedKeys,
+ treeDefaultExpandAll,
+ children,
+ treeIcon,
+ switcherIcon,
+ treeLine,
+ filterTreeNode,
+ dropdownPopupAlign,
+ onChange,
+ onTreeExpand,
+ onTreeLoad,
+ onDropdownVisibleChange,
+ onSelect,
+ onDeselect,
+ } = props;
+ const mergedCheckable = !!(treeCheckable || treeCheckStrictly);
+ const mergedMultiple = multiple || mergedCheckable;
+ const treeConduction = treeCheckable && !treeCheckStrictly;
+ const mergedLabelInValue = treeCheckStrictly || labelInValue;
+
+ // ========================== Ref ==========================
+ const selectRef = React.useRef(null);
+
+ React.useImperativeHandle(ref, () => ({
+ focus: selectRef.current.focus,
+ blur: selectRef.current.blur,
+ }));
+
+ // ======================= Tree Data =======================
+ // Legacy both support `label` or `title` if not set.
+ // We have to fallback to function to handle this
+ const getTreeNodeLabelProp = (node: DataNode): React.ReactNode => {
+ if (treeNodeLabelProp) {
+ return node[treeNodeLabelProp];
+ }
+
+ if (!treeData) {
+ return node.title;
+ }
+ return node.label || node.title;
+ };
+
+ const mergedTreeData = useTreeData(treeData, children, {
+ getLabelProp: getTreeNodeLabelProp,
+ simpleMode: treeDataSimpleMode,
+ });
+
+ const flattedOptions = React.useMemo(() => flattenOptions(mergedTreeData), [mergedTreeData]);
+ const [cacheKeyMap, cacheValueMap] = useKeyValueMap(flattedOptions);
+ const [getEntityByKey, getEntityByValue] = useKeyValueMapping(cacheKeyMap, cacheValueMap);
+
+ // Only generate keyEntities for check conduction when is `treeCheckable`
+ const { keyEntities: conductKeyEntities } = React.useMemo(() => {
+ if (treeConduction) {
+ return convertDataToEntities(mergedTreeData as any);
+ }
+ return { keyEntities: null };
+ }, [mergedTreeData, treeCheckable, treeCheckStrictly]);
+
+ // ========================= Value =========================
+ const [value, setValue] = React.useState(props.defaultValue);
+ const mergedValue = 'value' in props ? props.value : value;
+
+ /** Get `missingRawValues` which not exist in the tree yet */
+ const splitRawValues = (newRawValues: RawValueType[]) => {
+ const missingRawValues = [];
+ const existRawValues = [];
+
+ // Keep missing value in the cache
+ newRawValues.forEach(val => {
+ if (getEntityByValue(val)) {
+ existRawValues.push(val);
+ } else {
+ missingRawValues.push(val);
+ }
+ });
+
+ return { missingRawValues, existRawValues };
+ };
+
+ const [rawValues, rawHalfCheckedKeys]: [RawValueType[], RawValueType[]] = React.useMemo(() => {
+ const valueHalfCheckedKeys: RawValueType[] = [];
+ const newRawValues: RawValueType[] = [];
+
+ toArray(mergedValue).forEach(item => {
+ if (item && typeof item === 'object' && 'value' in item) {
+ if (item.halfChecked && treeCheckStrictly) {
+ const entity = getEntityByValue(item.value);
+ valueHalfCheckedKeys.push(entity ? entity.key : item.value);
+ } else {
+ newRawValues.push(item.value);
+ }
+ } else {
+ newRawValues.push(item as RawValueType);
+ }
+ });
+
+ // We need do conduction of values
+ if (treeConduction) {
+ const { missingRawValues, existRawValues } = splitRawValues(newRawValues);
+ const keyList = existRawValues.map(val => getEntityByValue(val).key);
+
+ const { checkedKeys, halfCheckedKeys } = conductCheck(keyList, true, conductKeyEntities);
+ return [
+ [...missingRawValues, ...checkedKeys.map(key => getEntityByKey(key).data.value)],
+ halfCheckedKeys,
+ ];
+ }
+ return [newRawValues, valueHalfCheckedKeys];
+ }, [mergedValue, mergedMultiple, mergedLabelInValue, treeCheckable, treeCheckStrictly]);
+ const selectValues = useSelectValues(rawValues, {
+ treeConduction,
+ value: mergedValue,
+ showCheckedStrategy,
+ conductKeyEntities,
+ getEntityByValue,
+ getEntityByKey,
+ getLabelProp: getTreeNodeLabelProp,
+ });
+
+ const triggerChange = (
+ newRawValues: RawValueType[],
+ extra: { triggerValue: RawValueType; selected: boolean },
+ source: SelectSource,
+ ) => {
+ setValue(mergedMultiple ? newRawValues : newRawValues[0]);
+ if (onChange) {
+ let eventValues: RawValueType[] = newRawValues;
+ if (treeConduction && showCheckedStrategy !== 'SHOW_ALL') {
+ const keyList = newRawValues.map(val => {
+ const entity = getEntityByValue(val);
+ return entity ? entity.key : val;
+ });
+ const formattedKeyList = formatStrategyKeys(
+ keyList,
+ showCheckedStrategy,
+ conductKeyEntities,
+ );
+
+ eventValues = formattedKeyList.map(key => {
+ const entity = getEntityByKey(key);
+ return entity ? entity.data.value : key;
+ });
+ }
+
+ const { triggerValue, selected } = extra || { triggerValue: undefined, selected: undefined };
+
+ let returnValues = mergedLabelInValue
+ ? getRawValueLabeled(eventValues, mergedValue, getEntityByValue, getTreeNodeLabelProp)
+ : eventValues;
+
+ // We need fill half check back
+ if (treeCheckStrictly) {
+ const halfValues = rawHalfCheckedKeys
+ .map(key => {
+ const entity = getEntityByKey(key);
+ return entity ? entity.data.value : key;
+ })
+ .filter(val => !eventValues.includes(val));
+
+ returnValues = [
+ ...(returnValues as LabelValueType[]),
+ ...getRawValueLabeled(halfValues, mergedValue, getEntityByValue, getTreeNodeLabelProp),
+ ];
+ }
+
+ const additionalInfo = {
+ // [Legacy] Always return as array contains label & value
+ preValue: selectValues,
+ triggerValue,
+ } as ChangeEventExtra;
+
+ // [Legacy] Fill legacy data if user query.
+ // This is expansive that we only fill when user query
+ // https://github.com/react-component/tree-select/blob/fe33eb7c27830c9ac70cd1fdb1ebbe7bc679c16a/src/Select.jsx
+ let showPosition = true;
+ if (treeCheckStrictly || (source === 'selection' && !selected)) {
+ showPosition = false;
+ }
+
+ fillAdditionalInfo(additionalInfo, triggerValue, newRawValues, mergedTreeData, showPosition);
+
+ if (mergedCheckable) {
+ additionalInfo.checked = selected;
+ } else {
+ additionalInfo.selected = selected;
+ }
+
+ onChange(
+ mergedMultiple ? returnValues : returnValues[0],
+ mergedLabelInValue
+ ? null
+ : eventValues.map(val => {
+ const entity = getEntityByValue(val);
+ return entity ? getTreeNodeLabelProp(entity.data) : null;
+ }),
+ additionalInfo,
+ );
+ }
+ };
+
+ const onInternalSelect = (selectValue: RawValueType, option: DataNode, source: SelectSource) => {
+ const eventValue = mergedLabelInValue ? selectValue : selectValue;
+
+ if (!mergedMultiple) {
+ // Single mode always set value
+ triggerChange([selectValue], { selected: true, triggerValue: selectValue }, source);
+ } else {
+ let newRawValues = addValue(rawValues, selectValue);
+
+ // Add keys if tree conduction
+ if (treeConduction) {
+ // Should keep missing values
+ const { missingRawValues, existRawValues } = splitRawValues(newRawValues);
+ const keyList = existRawValues.map(val => getEntityByValue(val).key);
+ const { checkedKeys } = conductCheck(keyList, true, conductKeyEntities);
+ newRawValues = [
+ ...missingRawValues,
+ ...checkedKeys.map(key => getEntityByKey(key).data.value),
+ ];
+ }
+
+ triggerChange(newRawValues, { selected: true, triggerValue: selectValue }, source);
+ }
+
+ if (onSelect) {
+ onSelect(eventValue, option);
+ }
+ };
+
+ const onInternalDeselect = (
+ selectValue: RawValueType,
+ option: DataNode,
+ source: SelectSource,
+ ) => {
+ const eventValue = mergedLabelInValue ? selectValue : selectValue;
+
+ let newRawValues = removeValue(rawValues, selectValue);
+
+ // Remove keys if tree conduction
+ if (treeConduction) {
+ const { missingRawValues, existRawValues } = splitRawValues(newRawValues);
+ const keyList = existRawValues.map(val => getEntityByValue(val).key);
+ const { checkedKeys } = conductCheck(
+ keyList,
+ { checked: false, halfCheckedKeys: rawHalfCheckedKeys },
+ conductKeyEntities,
+ );
+ newRawValues = [
+ ...missingRawValues,
+ ...checkedKeys.map(key => getEntityByKey(key).data.value),
+ ];
+ }
+
+ triggerChange(newRawValues, { selected: false, triggerValue: selectValue }, source);
+
+ if (onDeselect) {
+ onDeselect(eventValue, option);
+ }
+ };
+
+ const onInternalClear = () => {
+ triggerChange([], null, 'clear');
+ };
+
+ // ========================= Open ==========================
+ const onInternalDropdownVisibleChange = React.useCallback(
+ (open: boolean) => {
+ if (onDropdownVisibleChange) {
+ const legacyParam = {};
+
+ Object.defineProperty(legacyParam, 'documentClickClose', {
+ get() {
+ warning(false, 'Second param of `onDropdownVisibleChange` has been removed.');
+ return false;
+ },
+ });
+
+ (onDropdownVisibleChange as any)(open, legacyParam);
+ }
+ },
+ [onDropdownVisibleChange],
+ );
+
+ // ======================== Warning ========================
+ if (process.env.NODE_ENV !== 'production') {
+ warningProps(props);
+ }
+
+ // ======================== Render =========================
+ // We pass some props into select props style
+ const selectProps: Partial> = {
+ optionLabelProp: null,
+ optionFilterProp: treeNodeFilterProp,
+ dropdownAlign: dropdownPopupAlign,
+ internalProps: {
+ mark: INTERNAL_PROPS_MARK,
+ onClear: onInternalClear,
+ skipTriggerChange: true,
+ skipTriggerSelect: true,
+ onRawSelect: onInternalSelect,
+ onRawDeselect: onInternalDeselect,
+ },
+ };
+
+ if ('filterTreeNode' in props) {
+ selectProps.filterOption = filterTreeNode;
+ }
+
+ return (
+
+
+
+ );
+});
+
+// Use class component since typescript not support generic
+// by `forwardRef` with function component yet.
+class TreeSelect extends React.Component<
+ TreeSelectProps,
+ {}
+> {
+ static TreeNode = TreeNode;
+
+ static SHOW_ALL: typeof SHOW_ALL = SHOW_ALL;
+
+ static SHOW_PARENT: typeof SHOW_PARENT = SHOW_PARENT;
+
+ static SHOW_CHILD: typeof SHOW_CHILD = SHOW_CHILD;
+
+ selectRef = React.createRef();
+
+ focus = () => {
+ this.selectRef.current.focus();
+ };
+
+ blur = () => {
+ this.selectRef.current.blur();
+ };
+
+ render() {
+ return ;
+ }
+}
+
+export default TreeSelect;
diff --git a/src/hooks/useKeyValueMap.ts b/src/hooks/useKeyValueMap.ts
new file mode 100644
index 00000000..34f6a4e0
--- /dev/null
+++ b/src/hooks/useKeyValueMap.ts
@@ -0,0 +1,21 @@
+import React from 'react';
+import { FlattenDataNode, Key, RawValueType } from '../interface';
+
+/**
+ * Return cached Key Value map with DataNode.
+ * Only re-calculate when `flattenOptions` changed.
+ */
+export default function useKeyValueMap(flattenOptions: FlattenDataNode[]) {
+ return React.useMemo(() => {
+ const cacheKeyMap: Map = new Map();
+ const cacheValueMap: Map = new Map();
+
+ // Cache options by key
+ flattenOptions.forEach((dataNode: FlattenDataNode) => {
+ cacheKeyMap.set(dataNode.key, dataNode);
+ cacheValueMap.set(dataNode.data.value, dataNode);
+ });
+
+ return [cacheKeyMap, cacheValueMap];
+ }, [flattenOptions]);
+}
diff --git a/src/hooks/useKeyValueMapping.ts b/src/hooks/useKeyValueMapping.ts
new file mode 100644
index 00000000..0d1ad942
--- /dev/null
+++ b/src/hooks/useKeyValueMapping.ts
@@ -0,0 +1,57 @@
+import React from 'react';
+import { FlattenDataNode, Key, RawValueType } from '../interface';
+
+export type SkipType = null | 'select' | 'checkbox';
+
+export function isDisabled(dataNode: FlattenDataNode, skipType: SkipType): boolean {
+ if (!dataNode) {
+ return true;
+ }
+
+ const { disabled, disableCheckbox } = dataNode.data;
+
+ switch (skipType) {
+ case 'select':
+ return disabled;
+ case 'checkbox':
+ return disabled || disableCheckbox;
+ }
+
+ return false;
+}
+
+export default function useKeyValueMapping(
+ cacheKeyMap: Map,
+ cacheValueMap: Map,
+): [
+ (key: Key, skipType?: SkipType) => FlattenDataNode,
+ (value: RawValueType, skipType?: SkipType) => FlattenDataNode,
+] {
+ const getEntityByKey = React.useCallback(
+ (key: Key, skipType: SkipType = 'select') => {
+ const dataNode = cacheKeyMap.get(key);
+
+ if (isDisabled(dataNode, skipType)) {
+ return null;
+ }
+
+ return dataNode;
+ },
+ [cacheKeyMap],
+ );
+
+ const getEntityByValue = React.useCallback(
+ (value: RawValueType, skipType: SkipType = 'select') => {
+ const dataNode = cacheValueMap.get(value);
+
+ if (isDisabled(dataNode, skipType)) {
+ return null;
+ }
+
+ return dataNode;
+ },
+ [cacheValueMap],
+ );
+
+ return [getEntityByKey, getEntityByValue];
+}
diff --git a/src/hooks/useSelectValues.ts b/src/hooks/useSelectValues.ts
new file mode 100644
index 00000000..7fff530e
--- /dev/null
+++ b/src/hooks/useSelectValues.ts
@@ -0,0 +1,54 @@
+import React from 'react';
+import { DefaultValueType } from 'rc-select/lib/interface/generator';
+import { DataEntity } from 'rc-tree/lib/interface';
+import { RawValueType, FlattenDataNode, Key, LabelValueType, DataNode } from '../interface';
+import { SkipType } from './useKeyValueMapping';
+import { getRawValueLabeled } from '../utils/valueUtil';
+import { formatStrategyKeys, CheckedStrategy } from '../utils/strategyUtil';
+
+interface Config {
+ treeConduction: boolean;
+ /** Current `value` of TreeSelect */
+ value: DefaultValueType;
+ showCheckedStrategy: CheckedStrategy;
+ conductKeyEntities: Record;
+ getEntityByKey: (key: Key, skipType?: SkipType) => FlattenDataNode;
+ getEntityByValue: (value: RawValueType, skipType?: SkipType) => FlattenDataNode;
+ getLabelProp: (node: DataNode) => React.ReactNode;
+}
+
+/** Return */
+export default function useSelectValues(
+ rawValues: RawValueType[],
+ {
+ value,
+ getEntityByValue,
+ getEntityByKey,
+ treeConduction,
+ showCheckedStrategy,
+ conductKeyEntities,
+ getLabelProp,
+ }: Config,
+): LabelValueType[] {
+ return React.useMemo(() => {
+ let mergedRawValues = rawValues;
+
+ if (treeConduction) {
+ const rawKeys = formatStrategyKeys(
+ rawValues.map(val => {
+ const entity = getEntityByValue(val);
+ return entity ? entity.key : val;
+ }),
+ showCheckedStrategy,
+ conductKeyEntities,
+ );
+
+ mergedRawValues = rawKeys.map(key => {
+ const entity = getEntityByKey(key);
+ return entity ? entity.data.value : key;
+ });
+ }
+
+ return getRawValueLabeled(mergedRawValues, value, getEntityByValue, getLabelProp);
+ }, [rawValues, value, treeConduction, showCheckedStrategy]);
+}
diff --git a/src/hooks/useTreeData.ts b/src/hooks/useTreeData.ts
new file mode 100644
index 00000000..e3ed29fc
--- /dev/null
+++ b/src/hooks/useTreeData.ts
@@ -0,0 +1,144 @@
+import React from 'react';
+import warning from 'rc-util/lib/warning';
+import { DataNode, InnerDataNode, SimpleModeConfig, RawValueType } from '../interface';
+import { convertChildrenToData } from '../utils/legacyUtil';
+
+const MAX_WARNING_TIMES = 10;
+
+function parseSimpleTreeData(
+ treeData: DataNode[],
+ { id, pId, rootPId }: SimpleModeConfig,
+): DataNode[] {
+ const keyNodes = {};
+ const rootNodeList = [];
+
+ // Fill in the map
+ const nodeList = treeData.map(node => {
+ const clone = { ...node };
+ const key = clone[id];
+ keyNodes[key] = clone;
+ clone.key = clone.key || key;
+ return clone;
+ });
+
+ // Connect tree
+ nodeList.forEach(node => {
+ const parentKey = node[pId];
+ const parent = keyNodes[parentKey];
+
+ // Fill parent
+ if (parent) {
+ parent.children = parent.children || [];
+ parent.children.push(node);
+ }
+
+ // Fill root tree node
+ if (parentKey === rootPId || (!parent && rootPId === null)) {
+ rootNodeList.push(node);
+ }
+ });
+
+ return rootNodeList;
+}
+
+/**
+ * Format `treeData` with `value` & `key` which is used for calculation
+ */
+function formatTreeData(
+ treeData: DataNode[],
+ getLabelProp: (node: DataNode) => React.ReactNode,
+): InnerDataNode[] {
+ let warningTimes = 0;
+ const valueSet = new Set();
+
+ function dig(dataNodes: DataNode[]) {
+ return dataNodes.map(node => {
+ const { key, value, children, ...rest } = node;
+
+ const mergedValue = 'value' in node ? value : key;
+
+ const dataNode: InnerDataNode = {
+ ...rest,
+ key: key !== null && key !== undefined ? key : mergedValue,
+ value: mergedValue,
+ title: getLabelProp(node),
+ };
+
+ // Check `key` & `value` and warning user
+ if (process.env.NODE_ENV !== 'production') {
+ if (
+ key !== null &&
+ key !== undefined &&
+ value !== undefined &&
+ String(key) !== String(value) &&
+ warningTimes < MAX_WARNING_TIMES
+ ) {
+ warningTimes += 1;
+ warning(
+ false,
+ `\`key\` or \`value\` with TreeNode must be the same or you can remove one of them. key: ${key}, value: ${value}.`,
+ );
+ }
+
+ warning(!valueSet.has(value), `Same \`value\` exist in the tree: ${value}`);
+ valueSet.add(value);
+ }
+
+ if ('children' in node) {
+ dataNode.children = dig(children);
+ }
+
+ return dataNode;
+ });
+ }
+
+ return dig(treeData);
+}
+
+/**
+ * Convert `treeData` or `children` into formatted `treeData`.
+ * Will not re-calculate if `treeData` or `children` not change.
+ */
+export default function useTreeData(
+ treeData: DataNode[],
+ children: React.ReactNode,
+ {
+ getLabelProp,
+ simpleMode,
+ }: {
+ getLabelProp: (node: DataNode) => React.ReactNode;
+ simpleMode: boolean | SimpleModeConfig;
+ },
+): InnerDataNode[] {
+ const cacheRef = React.useRef<{
+ treeData?: DataNode[];
+ children?: React.ReactNode;
+ formatTreeData?: InnerDataNode[];
+ }>({});
+
+ if (treeData) {
+ cacheRef.current.formatTreeData =
+ cacheRef.current.treeData === treeData
+ ? cacheRef.current.formatTreeData
+ : formatTreeData(
+ simpleMode
+ ? parseSimpleTreeData(treeData, {
+ id: 'id',
+ pId: 'pId',
+ rootPId: null,
+ ...(simpleMode !== true ? simpleMode : {}),
+ })
+ : treeData,
+ getLabelProp,
+ );
+
+ cacheRef.current.treeData = treeData;
+ } else {
+ cacheRef.current.formatTreeData =
+ cacheRef.current.children === children
+ ? cacheRef.current.formatTreeData
+ : formatTreeData(convertChildrenToData(children), getLabelProp);
+ }
+
+ return cacheRef.current.formatTreeData;
+}
diff --git a/src/index.js b/src/index.js
deleted file mode 100644
index 49f26730..00000000
--- a/src/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import Select from './Select';
-import SelectNode from './SelectNode';
-
-export { SHOW_ALL, SHOW_CHILD, SHOW_PARENT } from './strategies';
-export const TreeNode = SelectNode;
-
-export default Select;
diff --git a/src/index.tsx b/src/index.tsx
new file mode 100644
index 00000000..e62f1757
--- /dev/null
+++ b/src/index.tsx
@@ -0,0 +1,7 @@
+import TreeSelect from './TreeSelect';
+import TreeNode from './TreeNode';
+import { SHOW_ALL, SHOW_CHILD, SHOW_PARENT } from './utils/strategyUtil';
+
+export { TreeNode, SHOW_ALL, SHOW_CHILD, SHOW_PARENT };
+
+export default TreeSelect;
diff --git a/src/interface.ts b/src/interface.ts
new file mode 100644
index 00000000..7ed7c0e3
--- /dev/null
+++ b/src/interface.ts
@@ -0,0 +1,82 @@
+import React from 'react';
+
+export type SelectSource = 'option' | 'selection' | 'input' | 'clear';
+
+export type Key = string | number;
+
+export type RawValueType = string | number;
+
+export interface LabelValueType {
+ key?: Key;
+ value?: RawValueType;
+ label?: React.ReactNode;
+ /** Only works on `treeCheckStrictly` */
+ halfChecked?: boolean;
+}
+
+export type DefaultValueType = RawValueType | RawValueType[] | LabelValueType | LabelValueType[];
+
+export interface DataNode {
+ value?: RawValueType;
+ title?: React.ReactNode;
+ label?: React.ReactNode;
+ key?: Key;
+ disabled?: boolean;
+ disableCheckbox?: boolean;
+ checkable?: boolean;
+ children?: DataNode[];
+
+ /** Customize data info */
+ [prop: string]: any;
+}
+
+export interface InnerDataNode extends DataNode {
+ key: Key;
+ value: RawValueType;
+ label?: React.ReactNode;
+ children?: InnerDataNode[];
+}
+
+export interface LegacyDataNode extends DataNode {
+ props: any;
+}
+
+export interface TreeDataNode extends DataNode {
+ key: Key;
+ children?: TreeDataNode[];
+}
+
+export interface FlattenDataNode {
+ data: DataNode;
+ key: Key;
+ level: number;
+}
+
+export interface SimpleModeConfig {
+ id?: Key;
+ pId?: Key;
+ rootPId?: Key;
+}
+
+/** @deprecated This is only used for legacy compatible. Not works on new code. */
+export interface LegacyCheckedNode {
+ pos: string;
+ node: React.ReactElement;
+ children?: LegacyCheckedNode[];
+}
+
+export interface ChangeEventExtra {
+ /** @deprecated Please save prev value by control logic instead */
+ preValue: LabelValueType[];
+ triggerValue: RawValueType;
+ /** @deprecated Use `onSelect` or `onDeselect` instead. */
+ selected?: boolean;
+ /** @deprecated Use `onSelect` or `onDeselect` instead. */
+ checked?: boolean;
+
+ // Not sure if exist user still use this. We have to keep but not recommend user to use
+ /** @deprecated This prop not work as react node anymore. */
+ triggerNode: React.ReactElement;
+ /** @deprecated This prop not work as react node anymore. */
+ allCheckedNodes: LegacyCheckedNode[];
+}
diff --git a/src/propTypes.js b/src/propTypes.js
deleted file mode 100644
index 08c66f47..00000000
--- a/src/propTypes.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import PropTypes from 'prop-types';
-import { isLabelInValue } from './util';
-
-const internalValProp = PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.number,
-]);
-
-export function genArrProps(propType) {
- return PropTypes.oneOfType([
- propType,
- PropTypes.arrayOf(propType),
- ]);
-}
-
-/**
- * Origin code check `multiple` is true when `treeCheckStrictly` & `labelInValue`.
- * But in process logic is already cover to array.
- * Check array is not necessary. Let's simplify this check logic.
- */
-export function valueProp(...args) {
- const [props, propName, Component] = args;
-
- if (isLabelInValue(props)) {
- const err = genArrProps(PropTypes.shape({
- label: PropTypes.node,
- value: internalValProp,
- }))(...args);
- if (err) {
- return new Error(
- `Invalid prop \`${propName}\` supplied to \`${Component}\`. ` +
- `You should use { label: string, value: string | number } or [{ label: string, value: string | number }] instead.`
- );
- }
- return null;
- }
-
- const err = genArrProps(internalValProp)(...args);
- if (err) {
- return new Error(
- `Invalid prop \`${propName}\` supplied to \`${Component}\`. ` +
- `You should use string or [string] instead.`
- );
- }
- return null;
-}
diff --git a/src/strategies.js b/src/strategies.js
deleted file mode 100644
index b5cf1944..00000000
--- a/src/strategies.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export const SHOW_ALL = 'SHOW_ALL';
-export const SHOW_PARENT = 'SHOW_PARENT';
-export const SHOW_CHILD = 'SHOW_CHILD';
diff --git a/src/util.js b/src/util.js
deleted file mode 100644
index 099763ab..00000000
--- a/src/util.js
+++ /dev/null
@@ -1,439 +0,0 @@
-import React from 'react';
-import warning from 'warning';
-import {
- convertDataToTree as rcConvertDataToTree,
- convertTreeToEntities as rcConvertTreeToEntities,
- conductCheck as rcConductCheck,
-} from 'rc-tree/lib/util';
-import toNodeArray from 'rc-util/lib/Children/toArray';
-import { hasClass } from 'rc-util/lib/Dom/class';
-import { SHOW_CHILD, SHOW_PARENT } from './strategies';
-
-let warnDeprecatedLabel = false;
-
-// =================== DOM =====================
-export function findPopupContainer(node, prefixClass) {
- let current = node;
- while (current) {
- if (hasClass(current, prefixClass)) {
- return current;
- }
- current = current.parentNode;
- }
-
- return null;
-}
-
-// =================== MISC ====================
-export function toTitle(title) {
- if (typeof title === 'string') {
- return title;
- }
- return null;
-}
-
-export function toArray(data) {
- if (data === undefined || data === null) return [];
-
- return Array.isArray(data) ? data : [data];
-}
-
-// Shallow copy of React 16.3 createRef api
-export function createRef() {
- const func = function setRef(node) {
- func.current = node;
- };
- return func;
-}
-
-// =============== Legacy ===============
-export const UNSELECTABLE_STYLE = {
- userSelect: 'none',
- WebkitUserSelect: 'none',
-};
-
-export const UNSELECTABLE_ATTRIBUTE = {
- unselectable: 'unselectable',
-};
-
-/**
- * Convert position list to hierarchy structure.
- * This is little hack since use '-' to split the position.
- */
-export function flatToHierarchy(positionList) {
- if (!positionList.length) {
- return [];
- }
-
- const entrances = {};
-
- // Prepare the position map
- const posMap = {};
- const parsedList = positionList.slice().map(entity => {
- const clone = {
- ...entity,
- fields: entity.pos.split('-'),
- };
- delete clone.children;
- return clone;
- });
-
- parsedList.forEach(entity => {
- posMap[entity.pos] = entity;
- });
-
- parsedList.sort((a, b) => {
- return a.fields.length - b.fields.length;
- });
-
- // Create the hierarchy
- parsedList.forEach(entity => {
- const parentPos = entity.fields.slice(0, -1).join('-');
- const parentEntity = posMap[parentPos];
-
- if (!parentEntity) {
- entrances[entity.pos] = entity;
- } else {
- parentEntity.children = parentEntity.children || [];
- parentEntity.children.push(entity);
- }
-
- // Some time position list provide `key`, we don't need it
- delete entity.key;
- delete entity.fields;
- });
-
- return Object.keys(entrances).map(key => entrances[key]);
-}
-
-// =============== Accessibility ===============
-let ariaId = 0;
-
-export function resetAriaId() {
- ariaId = 0;
-}
-
-export function generateAriaId(prefix) {
- ariaId += 1;
- return `${prefix}_${ariaId}`;
-}
-
-export function isLabelInValue(props) {
- const { treeCheckable, treeCheckStrictly, labelInValue } = props;
- if (treeCheckable && treeCheckStrictly) {
- return true;
- }
- return labelInValue || false;
-}
-
-// =================== Tree ====================
-export function parseSimpleTreeData(treeData, { id, pId, rootPId }) {
- const keyNodes = {};
- const rootNodeList = [];
-
- // Fill in the map
- const nodeList = treeData.map(node => {
- const clone = { ...node };
- const key = clone[id];
- keyNodes[key] = clone;
- clone.key = clone.key || key;
- return clone;
- });
-
- // Connect tree
- nodeList.forEach(node => {
- const parentKey = node[pId];
- const parent = keyNodes[parentKey];
-
- // Fill parent
- if (parent) {
- parent.children = parent.children || [];
- parent.children.push(node);
- }
-
- // Fill root tree node
- if (parentKey === rootPId || (!parent && rootPId === null)) {
- rootNodeList.push(node);
- }
- });
-
- return rootNodeList;
-}
-
-/**
- * Detect if position has relation.
- * e.g. 1-2 related with 1-2-3
- * e.g. 1-3-2 related with 1
- * e.g. 1-2 not related with 1-21
- */
-export function isPosRelated(pos1, pos2) {
- const fields1 = pos1.split('-');
- const fields2 = pos2.split('-');
-
- const minLen = Math.min(fields1.length, fields2.length);
- for (let i = 0; i < minLen; i += 1) {
- if (fields1[i] !== fields2[i]) {
- return false;
- }
- }
- return true;
-}
-
-/**
- * This function is only used on treeNode check (none treeCheckStrictly but has searchInput).
- * We convert entity to { node, pos, children } format.
- * This is legacy bug but we still need to do with it.
- * @param entity
- */
-export function cleanEntity({ node, pos, children }) {
- const instance = {
- node,
- pos,
- };
-
- if (children) {
- instance.children = children.map(cleanEntity);
- }
-
- return instance;
-}
-
-/**
- * Get a filtered TreeNode list by provided treeNodes.
- * [Legacy] Since `Tree` use `key` as map but `key` will changed by React,
- * we have to convert `treeNodes > data > treeNodes` to keep the key.
- * Such performance hungry!
- *
- * We pass `Component` as argument is to fix eslint issue.
- */
-export function getFilterTree(treeNodes, searchValue, filterFunc, valueEntities, Component) {
- if (!searchValue) {
- return null;
- }
-
- function mapFilteredNodeToData(node) {
- if (!node) return null;
-
- let match = false;
- if (filterFunc(searchValue, node)) {
- match = true;
- }
-
- const children = toNodeArray(node.props.children)
- .map(mapFilteredNodeToData)
- .filter(n => n);
-
- if (children.length || match) {
- return (
-
- {children}
-
- );
- }
-
- return null;
- }
-
- return treeNodes.map(mapFilteredNodeToData).filter(node => node);
-}
-
-// =================== Value ===================
-/**
- * Convert value to array format to make logic simplify.
- */
-export function formatInternalValue(value, props) {
- const valueList = toArray(value);
-
- // Parse label in value
- if (isLabelInValue(props)) {
- return valueList.map(val => {
- if (typeof val !== 'object' || !val) {
- return {
- value: '',
- label: '',
- };
- }
-
- return val;
- });
- }
-
- return valueList.map(val => ({
- value: val,
- }));
-}
-
-export function getLabel(wrappedValue, entity, treeNodeLabelProp) {
- if (wrappedValue.label) {
- return wrappedValue.label;
- }
-
- if (entity && entity.node.props) {
- return entity.node.props[treeNodeLabelProp];
- }
-
- // Since value without entity will be in missValueList.
- // This code will never reached, but we still need this in case.
- return wrappedValue.value;
-}
-
-/**
- * Convert internal state `valueList` to user needed value list.
- * This will return an array list. You need check if is not multiple when return.
- *
- * `allCheckedNodes` is used for `treeCheckStrictly`
- */
-export function formatSelectorValue(valueList, props, valueEntities) {
- const { treeNodeLabelProp, treeCheckable, treeCheckStrictly, showCheckedStrategy } = props;
-
- // Will hide some value if `showCheckedStrategy` is set
- if (treeCheckable && !treeCheckStrictly) {
- const values = {};
- valueList.forEach(wrappedValue => {
- values[wrappedValue.value] = wrappedValue;
- });
- const hierarchyList = flatToHierarchy(valueList.map(({ value }) => valueEntities[value]));
-
- if (showCheckedStrategy === SHOW_PARENT) {
- // Only get the parent checked value
- return hierarchyList.map(({ node: { props: { value } } }) => ({
- label: getLabel(values[value], valueEntities[value], treeNodeLabelProp),
- value,
- }));
- }
-
- if (showCheckedStrategy === SHOW_CHILD) {
- // Only get the children checked value
- const targetValueList = [];
-
- // Find the leaf children
- const traverse = ({
- node: {
- props: { value },
- },
- children,
- }) => {
- if (!children || children.length === 0) {
- targetValueList.push({
- label: getLabel(values[value], valueEntities[value], treeNodeLabelProp),
- value,
- });
- return;
- }
-
- children.forEach(entity => {
- traverse(entity);
- });
- };
-
- hierarchyList.forEach(entity => {
- traverse(entity);
- });
-
- return targetValueList;
- }
- }
-
- return valueList.map(wrappedValue => ({
- label: getLabel(wrappedValue, valueEntities[wrappedValue.value], treeNodeLabelProp),
- value: wrappedValue.value,
- }));
-}
-
-/**
- * Use `rc-tree` convertDataToTree to convert treeData to TreeNodes.
- * This will change the label to title value
- */
-function processProps(props) {
- const { title, label, key, value } = props;
- const cloneProps = { ...props };
-
- // Warning user not to use deprecated label prop.
- if (label && !title) {
- if (!warnDeprecatedLabel) {
- warning(false, "'label' in treeData is deprecated. Please use 'title' instead.");
- warnDeprecatedLabel = true;
- }
-
- cloneProps.title = label;
- }
-
- if (!key) {
- cloneProps.key = value;
- }
-
- return cloneProps;
-}
-
-export function convertDataToTree(treeData) {
- return rcConvertDataToTree(treeData, { processProps });
-}
-
-/**
- * Use `rc-tree` convertTreeToEntities for entities calculation.
- * We have additional entities of `valueEntities`
- */
-function initWrapper(wrapper) {
- return {
- ...wrapper,
- valueEntities: {},
- };
-}
-
-function processEntity(entity, wrapper) {
- const value = entity.node.props.value;
- entity.value = value;
-
- // This should be empty, or will get error message.
- const currentEntity = wrapper.valueEntities[value];
- if (currentEntity) {
- warning(
- false,
- `Conflict! value of node '${entity.key}' (${value}) has already used by node '${
- currentEntity.key
- }'.`,
- );
- }
- wrapper.valueEntities[value] = entity;
-}
-
-export function convertTreeToEntities(treeNodes) {
- return rcConvertTreeToEntities(treeNodes, {
- initWrapper,
- processEntity,
- });
-}
-
-/**
- * https://github.com/ant-design/ant-design/issues/13328
- * We need calculate the half check key when searchValue is set.
- */
-// TODO: This logic may better move to rc-tree
-export function getHalfCheckedKeys(valueList, valueEntities) {
- const values = {};
-
- // Fill checked keys
- valueList.forEach(({ value }) => {
- values[value] = false;
- });
-
- // Fill half checked keys
- valueList.forEach(({ value }) => {
- let current = valueEntities[value];
-
- while (current && current.parent) {
- const parentValue = current.parent.value;
- if (parentValue in values) break;
- values[parentValue] = true;
-
- current = current.parent;
- }
- });
-
- // Get half keys
- return Object.keys(values)
- .filter(value => values[value])
- .map(value => valueEntities[value].key);
-}
-
-export const conductCheck = rcConductCheck;
diff --git a/src/utils/legacyUtil.tsx b/src/utils/legacyUtil.tsx
new file mode 100644
index 00000000..db1f625f
--- /dev/null
+++ b/src/utils/legacyUtil.tsx
@@ -0,0 +1,154 @@
+import React from 'react';
+import toArray from 'rc-util/lib/Children/toArray';
+import warning from 'rc-util/lib/warning';
+import {
+ DataNode,
+ LegacyDataNode,
+ ChangeEventExtra,
+ InnerDataNode,
+ RawValueType,
+ LegacyCheckedNode,
+} from '../interface';
+import TreeNode from '../TreeNode';
+
+export function convertChildrenToData(nodes: React.ReactNode): DataNode[] {
+ return toArray(nodes)
+ .map((node: React.ReactElement) => {
+ if (!React.isValidElement(node) || !node.type) {
+ return null;
+ }
+
+ const {
+ key,
+ props: { children, value, ...restProps },
+ } = node as React.ReactElement;
+
+ const data = {
+ key,
+ value,
+ ...restProps,
+ };
+
+ const childData = convertChildrenToData(children);
+ if (childData.length) {
+ data.children = childData;
+ }
+
+ return data;
+ })
+ .filter(data => data);
+}
+
+export function fillLegacyProps(dataNode: DataNode): LegacyDataNode {
+ // Skip if not dataNode exist
+ if (!dataNode) {
+ return dataNode as LegacyDataNode;
+ }
+
+ const cloneNode = { ...dataNode };
+
+ if (!('props' in cloneNode)) {
+ Object.defineProperty(cloneNode, 'props', {
+ get() {
+ warning(
+ false,
+ 'New `rc-tree-select` not support return node instance as argument anymore. Please consider to remove `props` access.',
+ );
+ return cloneNode;
+ },
+ });
+ }
+
+ return cloneNode as LegacyDataNode;
+}
+
+export function fillAdditionalInfo(
+ extra: ChangeEventExtra,
+ triggerValue: RawValueType,
+ checkedValues: RawValueType[],
+ treeData: InnerDataNode[],
+ showPosition: boolean,
+) {
+ let triggerNode: React.ReactNode = null;
+ let nodeList: LegacyCheckedNode[] = null;
+
+ function generateMap() {
+ function dig(list: InnerDataNode[], level = '0', parentIncluded = false) {
+ return list
+ .map((dataNode, index) => {
+ const pos = `${level}-${index}`;
+ const included = checkedValues.includes(dataNode.value);
+ const children = dig(dataNode.children || [], pos, included);
+ const node = {children.map(child => child.node)};
+
+ // Link with trigger node
+ if (triggerValue === dataNode.value) {
+ triggerNode = node;
+ }
+
+ if (included) {
+ const checkedNode: LegacyCheckedNode = {
+ pos,
+ node,
+ children,
+ };
+
+ if (!parentIncluded) {
+ nodeList.push(checkedNode);
+ }
+
+ return checkedNode;
+ }
+ return null;
+ })
+ .filter(node => node);
+ }
+
+ if (!nodeList) {
+ nodeList = [];
+
+ dig(treeData);
+
+ // Sort to keep the checked node length
+ nodeList.sort(
+ (
+ {
+ node: {
+ props: { value: val1 },
+ },
+ },
+ {
+ node: {
+ props: { value: val2 },
+ },
+ },
+ ) => {
+ const index1 = checkedValues.indexOf(val1);
+ const index2 = checkedValues.indexOf(val2);
+ return index1 - index2;
+ },
+ );
+ }
+ }
+
+ Object.defineProperty(extra, 'triggerNode', {
+ get() {
+ warning(false, '`triggerNode` is deprecated. Please consider decoupling data with node.');
+ generateMap();
+
+ return triggerNode;
+ },
+ });
+ Object.defineProperty(extra, 'allCheckedNodes', {
+ get() {
+ warning(false, '`allCheckedNodes` is deprecated. Please consider decoupling data with node.');
+ generateMap();
+
+ if (showPosition) {
+ return nodeList;
+ }
+
+ return nodeList.map(({ node }) => node);
+ },
+ });
+}
diff --git a/src/utils/strategyUtil.ts b/src/utils/strategyUtil.ts
new file mode 100644
index 00000000..4c5a64a1
--- /dev/null
+++ b/src/utils/strategyUtil.ts
@@ -0,0 +1,46 @@
+import { DataEntity } from 'rc-tree/lib/interface';
+import { RawValueType, Key, DataNode } from '../interface';
+import { isCheckDisabled } from './valueUtil';
+
+export const SHOW_ALL = 'SHOW_ALL';
+export const SHOW_PARENT = 'SHOW_PARENT';
+export const SHOW_CHILD = 'SHOW_CHILD';
+
+export type CheckedStrategy = typeof SHOW_ALL | typeof SHOW_PARENT | typeof SHOW_CHILD;
+
+export function formatStrategyKeys(
+ keys: Key[],
+ strategy: CheckedStrategy,
+ keyEntities: Record,
+): RawValueType[] {
+ const keySet = new Set(keys);
+
+ if (strategy === SHOW_CHILD) {
+ return keys.filter(key => {
+ const entity = keyEntities[key];
+
+ if (
+ entity &&
+ entity.children &&
+ entity.children.every(
+ ({ node }) => isCheckDisabled(node) || keySet.has((node as DataNode).key),
+ )
+ ) {
+ return false;
+ }
+ return true;
+ });
+ }
+ if (strategy === SHOW_PARENT) {
+ return keys.filter(key => {
+ const entity = keyEntities[key];
+ const parent = entity ? entity.parent : null;
+
+ if (parent && !isCheckDisabled(parent.node) && keySet.has((parent.node as DataNode).key)) {
+ return false;
+ }
+ return true;
+ });
+ }
+ return keys;
+}
diff --git a/src/utils/valueUtil.ts b/src/utils/valueUtil.ts
new file mode 100644
index 00000000..015aa111
--- /dev/null
+++ b/src/utils/valueUtil.ts
@@ -0,0 +1,188 @@
+import { flattenTreeData } from 'rc-tree/lib/utils/treeUtil';
+import { FlattenNode } from 'rc-tree/lib/interface';
+import { FilterFunc } from 'rc-select/lib/interface/generator';
+import {
+ FlattenDataNode,
+ Key,
+ RawValueType,
+ DataNode,
+ DefaultValueType,
+ LabelValueType,
+ LegacyDataNode,
+} from '../interface';
+import { fillLegacyProps } from './legacyUtil';
+import { SkipType } from '../hooks/useKeyValueMapping';
+
+export function toArray(value: T | T[]): T[] {
+ if (Array.isArray(value)) {
+ return value;
+ }
+ return value !== undefined ? [value] : [];
+}
+
+export function findValueOption(values: RawValueType[], options: FlattenDataNode[]): DataNode[] {
+ const optionMap: Map = new Map();
+
+ options.forEach(flattenItem => {
+ const { data } = flattenItem;
+ optionMap.set(data.value, data);
+ });
+
+ return values.map(val => fillLegacyProps(optionMap.get(val)));
+}
+
+export function isValueDisabled(value: RawValueType, options: FlattenDataNode[]): boolean {
+ const option = findValueOption([value], options)[0];
+ if (option) {
+ return option.disabled;
+ }
+
+ return false;
+}
+
+export function isCheckDisabled(node: DataNode) {
+ return node.disabled || node.disableCheckbox || node.checkable === false;
+}
+
+interface TreeDataNode {
+ key: Key;
+}
+
+function getLevel({ parent }: FlattenNode): number {
+ let level = 0;
+ let current = parent;
+
+ while (current) {
+ current = current.parent;
+ level += 1;
+ }
+
+ return level;
+}
+
+/**
+ * Before reuse `rc-tree` logic, we need to add key since TreeSelect use `value` instead of `key`.
+ */
+export function flattenOptions(options: DataNode[]): FlattenDataNode[] {
+ // Add missing key
+ function fillKey(list: DataNode[]): TreeDataNode[] {
+ return (list || []).map(node => {
+ const { value, key, children } = node;
+
+ const clone = {
+ ...node,
+ key: 'key' in node ? key : value,
+ };
+
+ if (children) {
+ clone.children = fillKey(children);
+ }
+
+ return clone;
+ });
+ }
+
+ const flattenList = flattenTreeData(fillKey(options), true);
+
+ return flattenList.map(node => ({
+ key: node.data.key,
+ data: node.data,
+ level: getLevel(node),
+ }));
+}
+
+function getDefaultFilterOption(optionFilterProp: string) {
+ return (searchValue: string, dataNode: LegacyDataNode) => {
+ const value = dataNode[optionFilterProp];
+
+ return String(value)
+ .toLowerCase()
+ .includes(String(searchValue).toLowerCase());
+ };
+}
+
+/** Filter options and return a new options by the search text */
+export function filterOptions(
+ searchValue: string,
+ options: DataNode[],
+ {
+ optionFilterProp,
+ filterOption,
+ }: { optionFilterProp: string; filterOption: boolean | FilterFunc },
+): DataNode[] {
+ if (filterOption === false) {
+ return options;
+ }
+
+ let filterOptionFunc: FilterFunc;
+ if (typeof filterOption === 'function') {
+ filterOptionFunc = filterOption;
+ } else {
+ filterOptionFunc = getDefaultFilterOption(optionFilterProp);
+ }
+
+ function dig(list: DataNode[], keepAll: boolean = false) {
+ return list
+ .map(dataNode => {
+ const { children } = dataNode;
+
+ const match = keepAll || filterOptionFunc(searchValue, fillLegacyProps(dataNode));
+ const childList = dig(children || [], match);
+
+ if (match || childList.length) {
+ return {
+ ...dataNode,
+ children: childList,
+ };
+ }
+ return null;
+ })
+ .filter(node => node);
+ }
+
+ return dig(options);
+}
+
+export function getRawValueLabeled(
+ values: RawValueType[],
+ prevValue: DefaultValueType,
+ getEntityByValue: (value: RawValueType, skipType?: SkipType) => FlattenDataNode,
+ getLabelProp: (node: DataNode) => React.ReactNode,
+): LabelValueType[] {
+ const valueMap = new Map();
+
+ toArray(prevValue).forEach(item => {
+ if (item && typeof item === 'object' && 'value' in item) {
+ valueMap.set(item.value, item);
+ }
+ });
+
+ return values.map(val => {
+ const item: LabelValueType = { value: val };
+ const entity = getEntityByValue(val);
+ const label = entity ? getLabelProp(entity.data) : val;
+
+ if (valueMap.has(val)) {
+ const labeledValue = valueMap.get(val);
+ item.label = 'label' in labeledValue ? labeledValue.label : label;
+ if ('halfChecked' in labeledValue) {
+ item.halfChecked = labeledValue.halfChecked;
+ }
+ } else {
+ item.label = label;
+ }
+
+ return item;
+ });
+}
+
+export function addValue(rawValues: RawValueType[], value: RawValueType) {
+ const values = new Set(rawValues);
+ values.add(value);
+ return Array.from(values);
+}
+export function removeValue(rawValues: RawValueType[], value: RawValueType) {
+ const values = new Set(rawValues);
+ values.delete(value);
+ return Array.from(values);
+}
diff --git a/src/utils/warningPropsUtil.ts b/src/utils/warningPropsUtil.ts
new file mode 100644
index 00000000..a172ab56
--- /dev/null
+++ b/src/utils/warningPropsUtil.ts
@@ -0,0 +1,38 @@
+import warning from 'rc-util/lib/warning';
+import { TreeSelectProps } from '../TreeSelect';
+import { toArray } from './valueUtil';
+
+function warningProps(props: TreeSelectProps) {
+ const {
+ searchPlaceholder,
+ treeCheckStrictly,
+ treeCheckable,
+ labelInValue,
+ value,
+ multiple,
+ } = props;
+
+ warning(!searchPlaceholder, '`searchPlaceholder` has been removed.');
+
+ if (treeCheckStrictly && labelInValue === false) {
+ warning(false, '`treeCheckStrictly` will force set `labelInValue` to `true`.');
+ }
+
+ if (labelInValue || treeCheckStrictly) {
+ warning(
+ toArray(value).every(val => val && typeof val === 'object' && 'value' in val),
+ 'Invalid prop `value` supplied to `TreeSelect`. You should use { label: string, value: string | number } or [{ label: string, value: string | number }] instead.',
+ );
+ }
+
+ if (treeCheckStrictly || multiple || treeCheckable) {
+ warning(
+ !value || Array.isArray(value),
+ '`value` should be an array when `TreeSelect` is checkable or multiple.',
+ );
+ } else {
+ warning(!Array.isArray(value), '`value` should not be array when `TreeSelect` is single mode.');
+ }
+}
+
+export default warningProps;
diff --git a/tests/Select.SearchInput.spec.js b/tests/Select.SearchInput.spec.js
index 3615f4d0..89e698c4 100644
--- a/tests/Select.SearchInput.spec.js
+++ b/tests/Select.SearchInput.spec.js
@@ -2,78 +2,28 @@
import React from 'react';
import { mount } from 'enzyme';
import TreeSelect, { TreeNode } from '../src';
-import { resetAriaId } from '../src/util';
describe('TreeSelect.SearchInput', () => {
- beforeEach(() => {
- resetAriaId();
- });
-
- const createSelect = (props) => {
- return mount(
-
-
-
-
-
-
- );
- }
-
- describe('click placeholder to get focus', () => {
- it('single', (done) => {
- const wrapper = createSelect({ showSearch: true });
-
- setTimeout(() => {
- // Focus outside
- wrapper.find('.pre-focus').instance().focus();
-
- // Click placeholder
- wrapper.find('.rc-tree-select-search__field__placeholder').simulate('click');
-
- const $input = wrapper.find('input.rc-tree-select-search__field').instance();
- expect($input).toBe(document.activeElement);
-
- done();
- }, 10);
- });
-
- it('multiple', (done) => {
- const wrapper = createSelect({ multiple: true });
-
- setTimeout(() => {
- // Focus outside
- wrapper.find('.pre-focus').instance().focus();
-
- // Click placeholder
- wrapper.find('.rc-tree-select-search__field__placeholder').simulate('click');
-
- const $input = wrapper.find('input.rc-tree-select-search__field').instance();
- expect($input).toBe(document.activeElement);
-
- done();
- }, 10);
- });
- });
-
it('select item will clean searchInput', () => {
const onSearch = jest.fn();
const wrapper = mount(
-
+ ,
);
- wrapper.find('.rc-tree-select-search__field').simulate('change', { target: { value: 'test' } });
- expect(onSearch).toBeCalledWith('test');
+ wrapper.search('test');
+ expect(onSearch).toHaveBeenCalledWith('test');
onSearch.mockReset();
- wrapper.find('.rc-tree-select-tree-node-content-wrapper').simulate('click');
- expect(onSearch).toBeCalledWith('');
+ wrapper.selectNode();
+ expect(onSearch).not.toHaveBeenCalled();
+ expect(
+ wrapper
+ .find('input')
+ .first()
+ .props().value,
+ ).toBeFalsy();
});
});
diff --git a/tests/Select.checkable.spec.js b/tests/Select.checkable.spec.js
index 2ccf4c1c..afcb2139 100644
--- a/tests/Select.checkable.spec.js
+++ b/tests/Select.checkable.spec.js
@@ -2,21 +2,8 @@
import React from 'react';
import { mount } from 'enzyme';
import TreeSelect, { SHOW_PARENT, SHOW_ALL, TreeNode } from '../src';
-import { resetAriaId } from '../src/util';
describe('TreeSelect.checkable', () => {
- beforeEach(() => {
- resetAriaId();
- });
-
- beforeAll(() => {
- jest.useFakeTimers();
- });
-
- afterAll(() => {
- jest.useRealTimers();
- });
-
it('allow clear when controlled', () => {
const treeData = [
{
@@ -32,7 +19,6 @@ describe('TreeSelect.checkable', () => {
],
},
];
-
class App extends React.Component {
state = {
value: [],
@@ -57,13 +43,10 @@ describe('TreeSelect.checkable', () => {
}
}
const wrapper = mount();
- // open
wrapper.openSelect();
- // select
- wrapper.find('.rc-tree-select-tree-checkbox').simulate('click');
- // clear
- wrapper.find('.rc-tree-select-selection__clear').simulate('click');
- expect(wrapper.find('.rc-tree-select-selection__choice')).toHaveLength(0);
+ wrapper.selectNode();
+ wrapper.clearSelection();
+ expect(wrapper.getSelection()).toHaveLength(0);
});
// https://github.com/ant-design/ant-design/issues/6731
@@ -141,21 +124,19 @@ describe('TreeSelect.checkable', () => {
}
}
const wrapper = mount();
- expect(wrapper.find('.rc-tree-select-selection__choice')).toHaveLength(1);
- // open
+ expect(wrapper.getSelection()).toHaveLength(1);
+
wrapper.openSelect();
- // select
- wrapper
- .find('.rc-tree-select-tree-checkbox')
- .at(2)
- .simulate('click');
- expect(wrapper.find('.rc-tree-select-selection__choice')).toHaveLength(2);
- // clear
- wrapper.find('.rc-tree-select-selection__clear').simulate('click');
- expect(wrapper.find('.rc-tree-select-selection__choice')).toHaveLength(0);
- // disabled
+ wrapper.selectNode(2);
+ expect(wrapper.getSelection()).toHaveLength(2);
+
+ // Clear all
+ wrapper.clearAll();
+ expect(wrapper.getSelection()).toHaveLength(0);
+
+ // disabled - legacy, just keep it though it's meaningless anymore
wrapper.find('#checkbox').simulate('change', { target: { checked: true } });
- expect(wrapper.find('.rc-tree-select-selection__choice')).toHaveLength(0);
+ expect(wrapper.getSelection()).toHaveLength(0);
});
// Fix https://github.com/ant-design/ant-design/issues/7312#issuecomment-324865971
@@ -183,23 +164,12 @@ describe('TreeSelect.checkable', () => {
onChange={handleChange}
/>,
);
- // open
- wrapper.find('.rc-tree-select').simulate('click');
- jest.runAllTimers();
- wrapper.update();
- // select
- wrapper
- .find('.rc-tree-select-tree-checkbox')
- .at(0)
- .simulate('click');
- expect(handleChange).toBeCalled();
- expect(wrapper.find('.rc-tree-select-selection__choice__content').length).toBe(1);
- expect(
- wrapper
- .find('.rc-tree-select-selection__choice__content')
- .at(0)
- .text(),
- ).toBe('1-1');
+
+ wrapper.openSelect();
+ wrapper.selectNode();
+ expect(handleChange).toHaveBeenCalled();
+ expect(wrapper.getSelection()).toHaveLength(1);
+ expect(wrapper.getSelection(0).text()).toEqual('1-1');
});
// Fix https://github.com/ant-design/ant-design/issues/8581
@@ -227,28 +197,15 @@ describe('TreeSelect.checkable', () => {
onChange={handleChange}
/>,
);
- // open
- wrapper.find('.rc-tree-select').simulate('click');
- jest.runAllTimers();
- // select
- wrapper
- .find('.rc-tree-select-tree-node-content-wrapper')
- .at(0)
- .simulate('click');
- expect(handleChange).toBeCalled();
- expect(wrapper.find('.rc-tree-select-selection__choice__content').length).toBe(1);
- expect(
- wrapper
- .find('.rc-tree-select-selection__choice__content')
- .at(0)
- .text(),
- ).toBe('1-1');
- // clear
- wrapper
- .find('.rc-tree-select-tree-node-content-wrapper')
- .at(0)
- .simulate('click');
- expect(wrapper.find('.rc-tree-select-selection__choice__content').length).toBe(0);
+
+ wrapper.openSelect();
+ wrapper.selectNode();
+ expect(handleChange).toHaveBeenCalled();
+ expect(wrapper.getSelection()).toHaveLength(1);
+ expect(wrapper.getSelection(0).text()).toBe('1-1');
+
+ wrapper.selectNode(0);
+ expect(wrapper.getSelection()).toHaveLength(0);
});
it('clear selected value and input value', () => {
@@ -270,14 +227,16 @@ describe('TreeSelect.checkable', () => {
/>,
);
wrapper.openSelect();
- wrapper
- .find('.rc-tree-select-tree-checkbox')
- .at(0)
- .simulate('click');
- wrapper.find('input').simulate('change', { target: { value: 'foo' } });
- wrapper.find('.rc-tree-select-selection__clear').simulate('click');
- expect(wrapper.state().valueList).toEqual([]);
- expect(wrapper.state().searchValue).toBe('');
+ wrapper.selectNode(0);
+ wrapper.search('foo');
+ wrapper.clearAll();
+ expect(wrapper.getSelection()).toHaveLength(0);
+ expect(
+ wrapper
+ .find('input')
+ .first()
+ .props().value,
+ ).toBe('');
});
describe('uncheck', () => {
@@ -300,16 +259,11 @@ describe('TreeSelect.checkable', () => {
,
);
-
describe('remove by selector', () => {
it('not treeCheckStrictly', () => {
const wrapper = createSelect();
expect(wrapper.render()).toMatchSnapshot();
-
- wrapper
- .find('.rc-tree-select-selection__choice__remove')
- .at(1)
- .simulate('click');
+ wrapper.clearSelection(1);
expect(wrapper.render()).toMatchSnapshot();
});
@@ -321,16 +275,12 @@ describe('TreeSelect.checkable', () => {
defaultValue: [val('0'), val('0-0'), val('0-0-0')],
onChange,
});
- wrapper
- .find('.rc-tree-select-selection__choice__remove')
- .at(1)
- .simulate('click');
-
- expect(onChange.mock.calls[0][0]).toEqual([
- { label: '0', value: '0' },
- { label: '0-0-0', value: '0-0-0' },
- ]);
- expect(onChange.mock.calls[0][1]).toEqual(null);
+ wrapper.clearSelection(1);
+ expect(onChange).toHaveBeenCalledWith(
+ [{ label: '0', value: '0' }, { label: '0-0-0', value: '0-0-0' }],
+ null,
+ expect.anything(),
+ );
const getProps = index => {
const node = onChange.mock.calls[0][2].allCheckedNodes[index];
@@ -348,10 +298,7 @@ describe('TreeSelect.checkable', () => {
const wrapper = createSelect({ searchValue: '0' });
expect(wrapper.render()).toMatchSnapshot();
- wrapper
- .find('.rc-tree-select-tree-checkbox')
- .at(1)
- .simulate('click');
+ wrapper.selectNode(1);
expect(wrapper.render()).toMatchSnapshot();
});
@@ -384,24 +331,14 @@ describe('TreeSelect.checkable', () => {
],
},
];
-
const wrapper = mount();
+ wrapper.search('58');
+ wrapper.selectNode(2);
+ expect(wrapper.getSelection()).toHaveLength(1);
- wrapper.find('.rc-tree-select-search__field').simulate('change', { target: { value: '58' } });
- wrapper
- .find(TreeNode)
- .at(2)
- .find('.rc-tree-select-tree-checkbox')
- .simulate('click');
- expect(wrapper.state().valueList.length).toBe(3);
-
- wrapper.find('.rc-tree-select-search__field').simulate('change', { target: { value: '59' } });
- wrapper
- .find(TreeNode)
- .at(2)
- .find('.rc-tree-select-tree-checkbox')
- .simulate('click');
- expect(wrapper.state().valueList.length).toBe(6);
+ wrapper.search('59');
+ wrapper.selectNode(2);
+ expect(wrapper.getSelection()).toHaveLength(2);
});
});
@@ -457,14 +394,9 @@ describe('TreeSelect.checkable', () => {
/>,
);
- wrapper.find('.rc-tree-select-search__field').simulate('change', { target: { value: '0-0' } });
- wrapper
- .find('.rc-tree-select-tree-checkbox')
- .at(0)
- .simulate('click');
- const keyList = onChange.mock.calls[0][0];
-
- expect(keyList.sort()).toEqual(['0-1-0', '0-1-2'].sort());
+ wrapper.search('0-0');
+ wrapper.selectNode(0);
+ expect(onChange).toHaveBeenCalledWith(['0-1-0', '0-1-2'], expect.anything(), expect.anything());
});
// https://github.com/ant-design/ant-design/issues/13328
@@ -499,21 +431,16 @@ describe('TreeSelect.checkable', () => {
/>,
);
- wrapper
- .find('.rc-tree-select-search__field')
- .simulate('change', { target: { value: '0-0-1' } });
- wrapper
- .find('.rc-tree-select-tree-checkbox')
- .at(1)
- .simulate('click');
+ wrapper.search('0-0-1');
+ wrapper.selectNode(1);
+ expect(onChange).toHaveBeenCalledWith(['0-0-1'], expect.anything(), expect.anything());
- expect(onChange.mock.calls[0][0]).toEqual(['0-0-1']);
expect(
wrapper
.find('.rc-tree-select-tree-checkbox')
.at(0)
.hasClass('rc-tree-select-tree-checkbox-indeterminate'),
- ).toBe(true);
+ ).toBeTruthy();
});
it('controlled', () => {
@@ -547,15 +474,9 @@ describe('TreeSelect.checkable', () => {
const wrapper = mount();
- wrapper
- .find('.rc-tree-select-search__field')
- .simulate('change', { target: { value: '0-0-1' } });
- wrapper
- .find('.rc-tree-select-tree-checkbox')
- .at(1)
- .simulate('click');
-
- expect(onChange).toBeCalled();
+ wrapper.search('0-0-1');
+ wrapper.selectNode(1);
+ expect(onChange).toHaveBeenCalled();
expect(
wrapper
@@ -575,12 +496,7 @@ describe('TreeSelect.checkable', () => {
,
);
- expect(wrapper.state().selectorValueList).toEqual([
- {
- label: '0-0',
- value: '0-0',
- },
- ]);
+ expect(wrapper.getSelection(0).text()).toEqual('0-0');
});
it('extra.allCheckedNodes', () => {
@@ -593,19 +509,19 @@ describe('TreeSelect.checkable', () => {
);
// Just click
- wrapper.find('.rc-tree-select-tree-checkbox').simulate('click');
+ wrapper.selectNode();
expect(onChange.mock.calls[0][2].allCheckedNodes).toEqual([
expect.objectContaining({
pos: '0-0',
}),
]);
- wrapper.find('.rc-tree-select-selection__choice__remove').simulate('click');
+ wrapper.clearSelection(0);
onChange.mockReset();
// By search
- wrapper.find('input').simulate('change', { target: { value: '0' } });
- wrapper.find('.rc-tree-select-tree-checkbox').simulate('click');
+ wrapper.search('0');
+ wrapper.selectNode();
expect(onChange.mock.calls[0][2].allCheckedNodes).toEqual([
expect.objectContaining({
pos: '0-0',
@@ -666,14 +582,8 @@ describe('TreeSelect.checkable', () => {
/>,
);
- wrapper
- .find('.rc-tree-select-search__field')
- .simulate('change', { target: { value: '0-0-0' } });
- wrapper
- .find(TreeNode)
- .at(1)
- .find('.rc-tree-select-tree-checkbox')
- .simulate('click');
+ wrapper.search('0-0-0');
+ wrapper.selectNode(1);
expect(onChange.mock.calls[0][0]).toEqual([
{ label: 'Node2', value: '0-1' },
@@ -693,8 +603,49 @@ describe('TreeSelect.checkable', () => {
const wrapper = mount();
- const choiceNode = wrapper.find('.rc-tree-select-selection__choice');
- expect(choiceNode.length).toBeTruthy();
- expect(choiceNode.find('.rc-tree-select-selection__choice__remove').length).toBeFalsy();
+ expect(wrapper.getSelection().length).toBeTruthy();
+ expect(wrapper.find('.rc-tree-select-selection-item-remove').length).toBeFalsy();
+ });
+
+ it('treeCheckStrictly can set halfChecked', () => {
+ const onChange = jest.fn();
+ const wrapper = mount(
+ ,
+ );
+
+ function getTreeNode(index) {
+ return wrapper.find('.rc-tree-select-tree-treenode').at(index);
+ }
+
+ expect(
+ getTreeNode(0).hasClass('rc-tree-select-tree-treenode-checkbox-indeterminate'),
+ ).toBeTruthy();
+ expect(
+ getTreeNode(1).hasClass('rc-tree-select-tree-treenode-checkbox-indeterminate'),
+ ).toBeFalsy();
+
+ wrapper.selectNode(1);
+ expect(onChange).toHaveBeenCalledWith(
+ [
+ {
+ label: 'Full Check',
+ value: 'full',
+ },
+ {
+ value: 'half',
+ label: 'Half Check',
+ halfChecked: true,
+ },
+ ],
+ null,
+ expect.anything(),
+ );
});
});
diff --git a/tests/Select.multiple.spec.js b/tests/Select.multiple.spec.js
index 58e1c105..70b2ed3e 100644
--- a/tests/Select.multiple.spec.js
+++ b/tests/Select.multiple.spec.js
@@ -1,10 +1,8 @@
/* eslint-disable no-undef */
import React from 'react';
-import { mount, render } from 'enzyme';
+import { mount } from 'enzyme';
import KeyCode from 'rc-util/lib/KeyCode';
import TreeSelect, { TreeNode } from '../src';
-import Selection from '../src/Selector/MultipleSelector/Selection';
-import { resetAriaId } from '../src/util';
import focusTest from './shared/focusTest';
describe('TreeSelect.multiple', () => {
@@ -15,45 +13,30 @@ describe('TreeSelect.multiple', () => {
{ key: '1', value: '1', title: 'label1' },
];
const createSelect = props => ;
- const select = (wrapper, index = 0) => {
- wrapper
- .find('.rc-tree-select-tree-node-content-wrapper')
- .at(index)
- .simulate('click');
- };
-
- beforeEach(() => {
- resetAriaId();
- });
it('select multiple nodes', () => {
const wrapper = mount(createSelect({ open: true }));
- select(wrapper, 0);
- select(wrapper, 1);
- const result = wrapper.find('.rc-tree-select-selection__rendered');
- const choices = result.find('.rc-tree-select-selection__choice__content');
- expect(result.last().is('ul')).toBe(true);
- expect(choices.at(0).prop('children')).toBe('label0');
- expect(choices.at(1).prop('children')).toBe('label1');
+ wrapper.selectNode(0);
+ wrapper.selectNode(1);
+ expect(wrapper.getSelection(0).text()).toBe('label0');
+ expect(wrapper.getSelection(1).text()).toBe('label1');
});
it('remove selected node', () => {
const wrapper = mount(createSelect({ defaultValue: ['0', '1'] }));
- wrapper
- .find('.rc-tree-select-selection__choice__remove')
- .first()
- .simulate('click');
- const choice = wrapper.find('ul .rc-tree-select-selection__choice__content');
- expect(choice).toHaveLength(1);
- expect(choice.prop('children')).toBe('label1');
+ wrapper.clearSelection();
+ expect(wrapper.getSelection()).toHaveLength(1);
+ expect(wrapper.getSelection(0).text()).toBe('label1');
});
it('remove by backspace key', () => {
const wrapper = mount(createSelect({ defaultValue: ['0', '1'] }));
- wrapper.find('input').simulate('keyDown', { keyCode: KeyCode.BACKSPACE });
- const choice = wrapper.find('ul .rc-tree-select-selection__choice__content');
- expect(choice).toHaveLength(1);
- expect(choice.prop('children')).toBe('label0');
+ wrapper
+ .find('input')
+ .first()
+ .simulate('keyDown', { which: KeyCode.BACKSPACE });
+ expect(wrapper.getSelection()).toHaveLength(1);
+ expect(wrapper.getSelection(0).text()).toBe('label0');
});
// https://github.com/react-component/tree-select/issues/47
@@ -78,15 +61,17 @@ describe('TreeSelect.multiple', () => {
}
}
const wrapper = mount();
- wrapper.find('input').simulate('keyDown', { keyCode: KeyCode.BACKSPACE });
wrapper
- .find('.rc-tree-select-tree-checkbox')
- .at(1)
- .simulate('click');
- wrapper.find('input').simulate('keyDown', { keyCode: KeyCode.BACKSPACE });
- const choice = wrapper.find('ul .rc-tree-select-selection__choice__content');
- expect(choice).toHaveLength(1);
- expect(choice.prop('children')).toBe('label0');
+ .find('input')
+ .first()
+ .simulate('keyDown', { which: KeyCode.BACKSPACE });
+ wrapper.selectNode(1);
+ wrapper
+ .find('input')
+ .first()
+ .simulate('keyDown', { which: KeyCode.BACKSPACE });
+ expect(wrapper.getSelection()).toHaveLength(1);
+ expect(wrapper.getSelection(0).text()).toBe('label0');
});
// TODO: Check preVal, it's not correct
@@ -107,15 +92,9 @@ describe('TreeSelect.multiple', () => {
}),
);
- const $remove = wrapper
- .find('.rc-tree-select-selection__rendered')
- .find('.rc-tree-select-selection__choice')
- .find('.rc-tree-select-selection__choice__remove')
- .at(1);
-
- $remove.simulate('click');
+ wrapper.clearSelection(1);
- expect(handleChange).toBeCalledWith(
+ expect(handleChange).toHaveBeenCalledWith(
['0'],
['label0'],
expect.objectContaining({
@@ -129,94 +108,102 @@ describe('TreeSelect.multiple', () => {
});
it('renders clear button', () => {
- const wrapper = render(createSelect({ allowClear: true }));
+ const wrapper = mount(createSelect({ allowClear: true, value: ['0'] }));
- expect(wrapper.find('.rc-tree-select-selection__clear')).toMatchSnapshot();
+ expect(wrapper.find('.rc-tree-select-clear').length).toBeTruthy();
});
it('should focus and clear search input after select and unselect item', () => {
const wrapper = mount(createSelect());
- wrapper.find('input').simulate('change', { target: { value: '0' } });
- expect(wrapper.find('input').getDOMNode().value).toBe('0');
- select(wrapper, 0);
- expect(wrapper.find('input').getDOMNode().value).toBe('');
- wrapper.find('input').simulate('change', { target: { value: '0' } });
- expect(wrapper.find('input').getDOMNode().value).toBe('0');
- select(wrapper, 0); // unselect
- expect(wrapper.find('input').getDOMNode().value).toBe('');
+
+ wrapper.search('0');
+ wrapper.selectNode(0);
+ expect(
+ wrapper
+ .find('input')
+ .first()
+ .props().value,
+ ).toBe('');
+
+ wrapper.search('0');
+ wrapper.selectNode(0);
+ expect(
+ wrapper
+ .find('input')
+ .first()
+ .props().value,
+ ).toBe('');
});
it('do not open tree when close button click', () => {
const wrapper = mount(createSelect());
- wrapper.find('.rc-tree-select-selection').simulate('click');
- select(wrapper, 0);
- select(wrapper, 1);
- wrapper.setState({ open: false });
- wrapper
- .find('.rc-tree-select-selection__choice__remove')
- .at(0)
- .simulate('click');
- expect(wrapper.state('open')).toBe(false);
- expect(wrapper.state('valueList')).toEqual([{ label: 'label1', value: '1' }]);
+ wrapper.openSelect();
+ wrapper.selectNode(0);
+ wrapper.selectNode(1);
+ wrapper.openSelect();
+ wrapper.clearSelection(0);
+ expect(wrapper.isOpen()).toBeFalsy();
+ expect(wrapper.getSelection()).toHaveLength(1);
});
describe('maxTagCount', () => {
it('legal', () => {
- const wrapper = render(
+ const wrapper = mount(
createSelect({
maxTagCount: 1,
value: ['0', '1'],
}),
);
- expect(wrapper.find('.rc-tree-select-selection')).toMatchSnapshot();
+ expect(wrapper.getSelection()).toHaveLength(2);
+ expect(wrapper.getSelection(1).text()).toBe('+ 1 ...');
});
it('illegal', () => {
- const wrapper = render(
+ const wrapper = mount(
createSelect({
maxTagCount: 1,
value: ['0', 'not exist'],
}),
);
- expect(wrapper.find('.rc-tree-select-selection')).toMatchSnapshot();
+ expect(wrapper.getSelection()).toHaveLength(2);
+ expect(wrapper.getSelection(1).text()).toBe('+ 1 ...');
});
it('zero', () => {
- const wrapper = render(
+ const wrapper = mount(
createSelect({
maxTagCount: 0,
value: ['0', '1'],
}),
);
- expect(wrapper.find('.rc-tree-select-selection')).toMatchSnapshot();
+ expect(wrapper.getSelection()).toHaveLength(1);
+ expect(wrapper.getSelection(0).text()).toBe('+ 2 ...');
});
describe('maxTagPlaceholder', () => {
it('string', () => {
- const wrapper = render(
+ const wrapper = mount(
createSelect({
maxTagCount: 1,
value: ['0', '1'],
maxTagPlaceholder: 'bamboo',
}),
);
-
- expect(wrapper.find('.rc-tree-select-selection')).toMatchSnapshot();
+ expect(wrapper.getSelection(1).text()).toBe('bamboo');
});
it('function', () => {
- const wrapper = render(
+ const wrapper = mount(
createSelect({
maxTagCount: 1,
value: ['0', '1'],
maxTagPlaceholder: list => `${list.length} bamboo...`,
}),
);
-
- expect(wrapper.find('.rc-tree-select-selection')).toMatchSnapshot();
+ expect(wrapper.getSelection(1).text()).toBe('1 bamboo...');
});
});
});
@@ -245,17 +232,17 @@ describe('TreeSelect.multiple', () => {
}),
);
- select(wrapper, 0);
- select(wrapper, 1);
+ wrapper.selectNode(0);
+ expect(onChange).toHaveBeenCalledWith([4, 0], expect.anything(), expect.anything());
+ onChange.mockReset();
- expect(onChange.mock.calls[0][0]).toEqual([0, 4]);
- expect(onChange.mock.calls[1][0]).toEqual([0, 2, 3, 4]);
+ wrapper.selectNode(1);
+ expect(onChange).toHaveBeenCalledWith([4, 0, 2, 3], expect.anything(), expect.anything());
});
// https://github.com/ant-design/ant-design/issues/12315
it('select searched node', () => {
const onChange = jest.fn();
-
const wrapper = mount(
@@ -269,13 +256,9 @@ describe('TreeSelect.multiple', () => {
,
);
- wrapper.find('.rc-tree-select-search__field').simulate('change', { target: { value: 'sss' } });
- wrapper
- .find('.rc-tree-select-tree-node-content-wrapper')
- .at(2)
- .simulate('click');
-
- expect(onChange.mock.calls[0][0]).toEqual(['leaf1', 'sss']);
+ wrapper.search('sss');
+ wrapper.selectNode(2);
+ expect(onChange).toHaveBeenCalledWith(['leaf1', 'sss'], expect.anything(), expect.anything());
});
it('do not crash when value has empty string', () => {
@@ -285,12 +268,12 @@ describe('TreeSelect.multiple', () => {
,
);
- expect(wrapper.find(Selection).length).toEqual(1);
+ expect(wrapper.getSelection()).toHaveLength(1);
});
it('can hide search box by showSearch = false', () => {
- const wrapper = render();
+ const wrapper = mount();
- expect(wrapper).toMatchSnapshot();
+ expect(wrapper.render()).toMatchSnapshot();
});
});
diff --git a/tests/Select.props.spec.js b/tests/Select.props.spec.js
index 24e83a39..99926493 100644
--- a/tests/Select.props.spec.js
+++ b/tests/Select.props.spec.js
@@ -1,12 +1,8 @@
/* eslint-disable no-undef, react/no-multi-comp, no-console */
import React from 'react';
-import { mount, render } from 'enzyme';
-import { renderToJson } from 'enzyme-to-json';
+import { mount } from 'enzyme';
import Tree, { TreeNode } from 'rc-tree';
-import Trigger from 'rc-trigger';
import TreeSelect, { SHOW_ALL, SHOW_CHILD, SHOW_PARENT, TreeNode as SelectNode } from '../src';
-import { resetAriaId } from '../src/util';
-import { setMock } from './__mocks__/rc-animate/lib/CSSMotionList';
// Promisify timeout to let jest catch works
function timeoutPromise(delay = 0) {
@@ -16,15 +12,6 @@ function timeoutPromise(delay = 0) {
}
describe('TreeSelect.props', () => {
- beforeEach(() => {
- jest.useFakeTimers();
- resetAriaId();
- });
-
- afterEach(() => {
- jest.useRealTimers();
- });
-
// Must wrap with `div` since enzyme will only return first child of fragment
const createSelect = (props = {}) => (
@@ -37,83 +24,17 @@ describe('TreeSelect.props', () => {
);
-
const createOpenSelect = (props = {}) =>
createSelect({ open: true, treeDefaultExpandAll: true, ...props });
- it('basic', () => {
- const wrapper = mount(createSelect());
- expect(wrapper.render()).toMatchSnapshot();
-
- wrapper.find('.rc-tree-select').simulate('click');
- expect(wrapper.render()).toMatchSnapshot();
- });
-
it('className', () => {
const wrapper = mount(createOpenSelect({ className: 'test-class' }));
- expect(wrapper.render()).toMatchSnapshot();
+ expect(wrapper.find('.rc-tree-select').hasClass('test-class')).toBeTruthy();
});
it('prefixCls', () => {
const wrapper = mount(createOpenSelect({ prefixCls: 'another-cls' }));
- expect(wrapper.render()).toMatchSnapshot();
- });
-
- it.skip('animation', () => {
- // setMock(true);
- const wrapper = mount(
- createSelect({
- animation: 'test-animation',
- }),
- );
- wrapper.find('.rc-tree-select').simulate('click');
- expect(wrapper.render()).toMatchSnapshot();
- // setMock(false);
- });
-
- it.skip('transitionName', () => {
- // setMock(true);
- const wrapper = mount(
- createSelect({
- transitionName: 'test-transitionName',
- }),
- );
- wrapper.find('.rc-tree-select').simulate('click');
- expect(wrapper.render()).toMatchSnapshot();
- // setMock(false);
- });
-
- it.only('choiceTransitionName', () => {
- setMock(true);
- class Wrapper extends React.Component {
- state = {
- value: [],
- };
-
- doValueUpdate = () => {
- this.setState({
- value: ['Value 0'],
- });
- };
-
- render() {
- const { value } = this.state;
- return (
-
-
-
-
-
- );
- }
- }
-
- const wrapper = mount();
- expect(wrapper.render()).toMatchSnapshot();
-
- wrapper.instance().doValueUpdate();
- expect(wrapper.render()).toMatchSnapshot();
- setMock(false);
+ expect(wrapper.find('.another-cls').length).toBeTruthy();
});
describe('filterTreeNode', () => {
@@ -122,29 +43,23 @@ describe('TreeSelect.props', () => {
return String(child.props.title).indexOf(input) !== -1;
}
const wrapper = mount(createOpenSelect({ filterTreeNode }));
- wrapper.find('input').simulate('change', { target: { value: 'Title 1' } });
- expect(wrapper.render()).toMatchSnapshot();
+ wrapper.search('Title 1');
+ expect(wrapper.find('List').props().data).toHaveLength(1);
- wrapper.find('input').simulate('change', { target: { value: '0-0' } });
- expect(wrapper.render()).toMatchSnapshot();
+ wrapper.search('0-0');
+ expect(wrapper.find('List').props().data).toHaveLength(2);
});
it('false', () => {
const wrapper = mount(createOpenSelect({ filterTreeNode: false }));
- wrapper.find('input').simulate('change', { target: { value: 'Title 1' } });
- expect(wrapper.render()).toMatchSnapshot();
+ wrapper.search('Title 1');
+ expect(wrapper.find('List').props().data).toHaveLength(4);
});
});
- it('showSearch', () => {
- const wrapper = mount(createOpenSelect({ showSearch: false }));
- expect(wrapper.render()).toMatchSnapshot();
- });
-
describe('allowClear', () => {
it('functional works', () => {
const handleChange = jest.fn();
-
const wrapper = mount(
createSelect({
allowClear: true,
@@ -153,29 +68,32 @@ describe('TreeSelect.props', () => {
open: true,
}),
);
- wrapper.find('.rc-tree-select').simulate('click');
- expect(wrapper.render()).toMatchSnapshot();
+
+ wrapper.openSelect();
// Click node 0-1
- const $node = wrapper.find(TreeNode).at(2);
- $node.find('.rc-tree-select-tree-node-content-wrapper').simulate('click');
-
- expect(wrapper.render()).toMatchSnapshot();
- expect(handleChange).toBeCalledWith('Value 0-1', ['Title 0-1'], {
- preValue: [],
- selected: true,
- triggerValue: 'Value 0-1',
- triggerNode: $node.instance(),
- });
+ wrapper.selectNode(2);
+ expect(handleChange).toHaveBeenCalledWith(
+ 'Value 0-1',
+ ['Title 0-1'],
+ expect.objectContaining({
+ preValue: [],
+ selected: true,
+ triggerValue: 'Value 0-1',
+ triggerNode: expect.anything(),
+ }),
+ );
handleChange.mockReset();
// Click to clear
- wrapper.find('.rc-tree-select-selection__clear').simulate('click');
-
- expect(wrapper.render()).toMatchSnapshot();
- expect(handleChange).toBeCalledWith(undefined, [], {
- preValue: [{ label: 'Title 0-1', value: 'Value 0-1' }],
- });
+ wrapper.clearAll();
+ expect(handleChange).toHaveBeenCalledWith(
+ undefined,
+ [],
+ expect.objectContaining({
+ preValue: [{ label: 'Title 0-1', value: 'Value 0-1' }],
+ }),
+ );
});
it('value not in tree should also display allow clear', () => {
@@ -185,26 +103,17 @@ describe('TreeSelect.props', () => {
value: 'not-exist-in-tree',
}),
);
- expect(wrapper.find('.rc-tree-select-selection__clear').length).toBeTruthy();
+ expect(wrapper.find('.rc-tree-select-clear').length).toBeTruthy();
});
});
it('placeholder', () => {
- const wrapper = render(
+ const wrapper = mount(
createSelect({
placeholder: 'RC Component',
}),
);
- expect(renderToJson(wrapper)).toMatchSnapshot();
- });
-
- it('searchPlaceholder', () => {
- const wrapper = mount(
- createOpenSelect({
- searchPlaceholder: 'RC Component',
- }),
- );
- expect(wrapper.render()).toMatchSnapshot();
+ expect(wrapper.find('.rc-tree-select-selection-placeholder').text()).toBe('RC Component');
});
// https://github.com/ant-design/ant-design/issues/11746
@@ -216,14 +125,11 @@ describe('TreeSelect.props', () => {
);
const wrapper = mount();
-
- expect(wrapper.render()).toMatchSnapshot();
-
+ expect(wrapper.find('List').props().data).toHaveLength(1);
wrapper.setProps({
treeData: [{ title: 'bbb', value: '222' }],
});
-
- expect(wrapper.render()).toMatchSnapshot();
+ expect(wrapper.find('List').length).toBeFalsy();
});
describe('labelInValue', () => {
@@ -235,17 +141,18 @@ describe('TreeSelect.props', () => {
onChange: handleChange,
}),
);
-
// Click node 0-1
- const $node = wrapper.find(TreeNode).at(2);
- $node.find('.rc-tree-select-tree-node-content-wrapper').simulate('click');
-
- expect(handleChange).toBeCalledWith({ label: 'Title 0-1', value: 'Value 0-1' }, null, {
- preValue: [],
- selected: true,
- triggerValue: 'Value 0-1',
- triggerNode: $node.instance(),
- });
+ wrapper.selectNode(2);
+ expect(handleChange).toHaveBeenCalledWith(
+ { label: 'Title 0-1', value: 'Value 0-1' },
+ null,
+ expect.objectContaining({
+ preValue: [],
+ selected: true,
+ triggerValue: 'Value 0-1',
+ triggerNode: expect.anything(),
+ }),
+ );
});
it('set illegal value', () => {
@@ -255,11 +162,7 @@ describe('TreeSelect.props', () => {
value: [null],
}),
);
-
- expect(wrapper.find(TreeSelect).instance().state.valueList).toEqual([]);
- expect(wrapper.find(TreeSelect).instance().state.missValueList).toEqual([
- { label: '', value: '' },
- ]);
+ expect(wrapper.getSelection(0).text()).toBe('');
});
});
@@ -271,10 +174,9 @@ describe('TreeSelect.props', () => {
onClick: handleClick,
}),
);
-
- // `onClick` depends on origin event trigger. Need't test args
+ // `onClick` depends on origin event trigger. Needn't test args
wrapper.find('.rc-tree-select').simulate('click');
- expect(handleClick).toBeCalled();
+ expect(handleClick).toHaveBeenCalled();
});
// onChange - is already test above
@@ -286,19 +188,16 @@ describe('TreeSelect.props', () => {
onSelect: handleSelect,
}),
);
-
- const $paren = wrapper.find(TreeNode).at(0);
- const $node = wrapper.find(TreeNode).at(2);
- $node.find('.rc-tree-select-tree-node-content-wrapper').simulate('click');
+ wrapper.selectNode(2);
// TreeNode use cloneElement so that the node is not the same
- expect(handleSelect).toBeCalledWith('Value 0-1', $node.instance(), {
- event: 'select',
- node: $node.instance(),
- selected: true,
- selectedNodes: [$paren.props().children[1]],
- nativeEvent: expect.objectContaining({}), // Native event object
- });
+ expect(handleSelect.mock.calls[0][0]).toEqual('Value 0-1');
+ expect(handleSelect.mock.calls[0][1].props).toEqual(
+ expect.objectContaining({
+ value: 'Value 0-1',
+ title: 'Title 0-1',
+ }),
+ );
});
// TODO: `onDeselect` is copy from `Select` component and not implement complete.
@@ -311,32 +210,13 @@ describe('TreeSelect.props', () => {
onSearch: handleSearch,
}),
);
-
- wrapper.find('input').simulate('change', { target: { value: 'Search changed' } });
- expect(handleSearch).toBeCalledWith('Search changed');
+ wrapper.search('Search changed');
+ expect(handleSearch).toHaveBeenCalledWith('Search changed');
});
it('showArrow', () => {
const wrapper = mount(createOpenSelect({ showArrow: false }));
- expect(wrapper.render()).toMatchSnapshot();
- });
-
- describe('dropdownMatchSelectWidth', () => {
- it('default', () => {
- const wrapper = mount(createOpenSelect());
- expect(wrapper.render()).toMatchSnapshot();
- });
-
- [true, false].forEach(dropdownMatchSelectWidth => {
- it(String(dropdownMatchSelectWidth), () => {
- const wrapper = mount(
- createOpenSelect({
- dropdownMatchSelectWidth,
- }),
- );
- expect(wrapper.render()).toMatchSnapshot();
- });
- });
+ expect(wrapper.find('.rc-tree-select-arrow').length).toBeFalsy();
});
it('dropdownClassName', () => {
@@ -345,85 +225,35 @@ describe('TreeSelect.props', () => {
dropdownClassName: 'test-dropdownClassName',
}),
);
- expect(wrapper.render()).toMatchSnapshot();
+ expect(wrapper.find('.test-dropdownClassName').length).toBeTruthy();
});
it('dropdownStyle', () => {
- const wrapper = mount(
- createOpenSelect({
- dropdownStyle: {
- background: 'red',
- },
- }),
- );
- expect(wrapper.render()).toMatchSnapshot();
- });
-
- it('dropdownPopupAlign', () => {
- const dropdownPopupAlign = {
- forPassPropTest: true,
+ const style = {
+ background: 'red',
};
-
- const wrapper = mount(createOpenSelect({ dropdownPopupAlign }));
-
- expect(wrapper.find(Trigger).props().popupAlign).toBe(dropdownPopupAlign);
- });
-
- it('onDropdownVisibleChange', () => {
- let canProcess = true;
-
- const handleDropdownVisibleChange = jest.fn();
const wrapper = mount(
- createSelect({
- onDropdownVisibleChange: (...args) => {
- handleDropdownVisibleChange(...args);
- return canProcess;
- },
+ createOpenSelect({
+ dropdownClassName: 'test-dropdownClassName',
+ dropdownStyle: style,
}),
);
-
- const $select = wrapper.find('.rc-tree-select');
-
- // Simulate when can process
- $select.simulate('click');
- expect(handleDropdownVisibleChange).toBeCalledWith(true, { documentClickClose: false });
- handleDropdownVisibleChange.mockReset();
-
- // https://github.com/ant-design/ant-design/issues/9857
- // Both use blur to hide. click not affect this.
- $select.simulate('click');
- expect(handleDropdownVisibleChange).toBeCalledWith(false, { documentClickClose: true });
- handleDropdownVisibleChange.mockReset();
-
- $select.simulate('blur');
- jest.runAllTimers();
- expect(handleDropdownVisibleChange).not.toBeCalled();
- handleDropdownVisibleChange.mockReset();
-
- // Simulate when can't process
- canProcess = false;
-
- $select.simulate('click');
- expect(handleDropdownVisibleChange).toBeCalledWith(true, { documentClickClose: false });
- handleDropdownVisibleChange.mockReset();
-
- $select.simulate('click');
- expect(handleDropdownVisibleChange).toBeCalledWith(true, { documentClickClose: false });
- handleDropdownVisibleChange.mockReset();
-
- $select.simulate('blur');
- jest.runAllTimers();
- expect(handleDropdownVisibleChange).not.toBeCalled();
+ expect(
+ wrapper
+ .find('.test-dropdownClassName')
+ .first()
+ .props().style,
+ ).toEqual(expect.objectContaining(style));
});
it('notFoundContent', () => {
const wrapper = mount(
createOpenSelect({
- notFoundContent: 'Noting Matched!',
+ notFoundContent: Noting Matched!
,
treeData: [],
}),
);
- expect(wrapper.render()).toMatchSnapshot();
+ expect(wrapper.find('.not-match').text()).toEqual('Noting Matched!');
});
describe('showCheckedStrategy', () => {
@@ -432,83 +262,60 @@ describe('TreeSelect.props', () => {
strategy: SHOW_ALL,
arg1: ['Value 0', 'Value 0-0', 'Value 0-1'],
arg2: ['Title 0', 'Title 0-0', 'Title 0-1'],
- arg3: ($node, $oriNode) => {
- const children = $node.props().children;
-
- return {
- allCheckedNodes: [
- {
- node: $oriNode,
- pos: '0-0',
- children: [
- { node: children[0], pos: '0-0-0' },
- { node: children[1], pos: '0-0-1' },
- ],
- },
- ],
- checked: true,
- preValue: [],
- triggerNode: $node.instance(),
- triggerValue: 'Value 0',
- };
+ arg3: {
+ allCheckedNodes: [
+ expect.objectContaining({
+ node: expect.anything(),
+ pos: '0-0',
+ children: expect.anything(),
+ }),
+ ],
+ checked: true,
+ preValue: [],
+ triggerNode: expect.anything(),
+ triggerValue: 'Value 0',
},
},
{
strategy: SHOW_CHILD,
arg1: ['Value 0-0', 'Value 0-1'],
arg2: ['Title 0-0', 'Title 0-1'],
- arg3: ($node, $oriNode) => {
- const children = $node.props().children;
-
- return {
- allCheckedNodes: [
- {
- node: $oriNode,
- pos: '0-0',
- children: [
- { node: children[0], pos: '0-0-0' },
- { node: children[1], pos: '0-0-1' },
- ],
- },
- ],
- checked: true,
- preValue: [],
- triggerNode: $node.instance(),
- triggerValue: 'Value 0',
- };
+ arg3: {
+ allCheckedNodes: [
+ expect.objectContaining({
+ node: expect.anything(),
+ pos: '0-0',
+ children: expect.anything(),
+ }),
+ ],
+ checked: true,
+ preValue: [],
+ triggerNode: expect.anything(),
+ triggerValue: 'Value 0',
},
},
{
strategy: SHOW_PARENT,
arg1: ['Value 0'],
arg2: ['Title 0'],
- arg3: ($node, $oriNode) => {
- const children = $node.props().children;
-
- return {
- allCheckedNodes: [
- {
- node: $oriNode,
- pos: '0-0',
- children: [
- { node: children[0], pos: '0-0-0' },
- { node: children[1], pos: '0-0-1' },
- ],
- },
- ],
- checked: true,
- preValue: [],
- triggerNode: $node.instance(),
- triggerValue: 'Value 0',
- };
+ arg3: {
+ allCheckedNodes: [
+ expect.objectContaining({
+ node: expect.anything(),
+ pos: '0-0',
+ children: expect.anything(),
+ }),
+ ],
+ checked: true,
+ preValue: [],
+ triggerNode: expect.anything(),
+ triggerValue: 'Value 0',
},
},
];
-
testList.forEach(({ strategy, arg1, arg2, arg3 }) => {
it(strategy, () => {
const handleChange = jest.fn();
-
const wrapper = mount(
createOpenSelect({
treeCheckable: true,
@@ -516,45 +323,20 @@ describe('TreeSelect.props', () => {
onChange: handleChange,
}),
);
-
// TreeSelect will convert SelectNode to TreeNode.
// Transitional node should get before click event
// Since after click will render new TreeNode
// [Legacy] FIXME: This is so hard to test
- const $tree = wrapper.find(Tree);
- const $oriNode = $tree.props().children[0];
-
- const $node = wrapper.find(TreeNode).at(0);
- $node
- .find('.rc-tree-select-tree-checkbox')
- .first()
- .simulate('click');
-
- expect(handleChange).toBeCalledWith(arg1, arg2, arg3($node, $oriNode));
+ wrapper.selectNode(0);
+ expect(handleChange).toHaveBeenCalledWith(arg1, arg2, expect.anything());
+ const { triggerNode, allCheckedNodes, ...rest } = handleChange.mock.calls[0][2];
+ expect({ ...rest, triggerNode, allCheckedNodes }).toEqual(arg3);
});
});
});
// treeCheckStrictly - already tested in Select.checkable.spec.js
- it('treeIcon', () => {
- const wrapper = mount(
- createOpenSelect({
- treeIcon: true,
- }),
- );
- expect(wrapper.render()).toMatchSnapshot();
- });
-
- it('treeLine', () => {
- const wrapper = mount(
- createOpenSelect({
- treeLine: true,
- }),
- );
- expect(wrapper.render()).toMatchSnapshot();
- });
-
// treeDataSimpleMode - already tested in Select.spec.js
it('treeDefaultExpandAll', () => {
@@ -563,14 +345,14 @@ describe('TreeSelect.props', () => {
treeDefaultExpandAll: true,
}),
);
- expect(expandWrapper.render()).toMatchSnapshot();
+ expect(expandWrapper.find('List').props().data).toHaveLength(4);
- const unexpandWrapper = mount(
+ const unExpandWrapper = mount(
createOpenSelect({
treeDefaultExpandAll: false,
}),
);
- expect(unexpandWrapper.render()).toMatchSnapshot();
+ expect(unExpandWrapper.find('List').props().data).toHaveLength(2);
});
// treeCheckable - already tested in Select.checkable.spec.js
@@ -586,7 +368,9 @@ describe('TreeSelect.props', () => {
value: ['Value 0-0', 'Value 1', 'Value 0-1'],
}),
);
- expect(wrapper.render()).toMatchSnapshot();
+ for (let i = 0; i < 3; i += 1) {
+ expect(wrapper.getSelection(0).text()).toBe('Ti...');
+ }
});
// disabled - already tested in Select.spec.js
@@ -598,7 +382,7 @@ describe('TreeSelect.props', () => {
defaultValue: 'Value 0-0',
}),
);
- expect(wrapper.render()).toMatchSnapshot();
+ expect(wrapper.getSelection(0).text()).toBe('Title 0-0');
});
// labelInValue - already tested in Select.spec.js
@@ -608,12 +392,8 @@ describe('TreeSelect.props', () => {
// treeData - already tested in Select.spec.js
it('loadData', () => {
- jest.useRealTimers();
-
let called = 0;
-
const handleLoadData = jest.fn();
-
class Demo extends React.Component {
state = {
loaded: false,
@@ -622,9 +402,7 @@ describe('TreeSelect.props', () => {
loadData = (...args) => {
called += 1;
handleLoadData(...args);
-
this.setState({ loaded: true });
-
return Promise.resolve();
};
@@ -639,33 +417,47 @@ describe('TreeSelect.props', () => {
);
}
}
-
const wrapper = mount();
+ expect(handleLoadData).not.toHaveBeenCalled();
- expect(handleLoadData).not.toBeCalled();
-
- const switcher = wrapper.find('.rc-tree-select-tree-switcher');
- const node = wrapper.find(TreeNode).instance();
- switcher.simulate('click');
+ wrapper.find('.rc-tree-select-tree-switcher').simulate('click');
return timeoutPromise().then(() => {
- expect(handleLoadData).toBeCalledWith(node);
+ expect(handleLoadData).toHaveBeenCalledWith(expect.objectContaining({ value: '0-0' }));
expect(called).toBe(1);
- expect(wrapper.render()).toMatchSnapshot();
+ expect(wrapper.find('List').props().data).toHaveLength(2);
});
});
+ it('treeLoadedKeys', () => {
+ const loadData = jest.fn(() => Promise.resolve());
+ mount(
+ ,
+ );
+
+ expect(loadData).toHaveBeenCalledTimes(1);
+ expect(loadData).toHaveBeenCalledWith(expect.objectContaining({ value: '0-1' }));
+ });
+
it('getPopupContainer', () => {
const getPopupContainer = trigger => trigger.parentNode;
-
const wrapper = mount(createOpenSelect({ getPopupContainer }));
-
- expect(wrapper.find(Trigger).props().getPopupContainer).toBe(getPopupContainer);
+ expect(
+ wrapper
+ .find('Trigger')
+ .first()
+ .props().getPopupContainer,
+ ).toBe(getPopupContainer);
});
it('set value not in the Tree', () => {
const onChange = jest.fn();
-
const wrapper = mount(
@@ -673,37 +465,23 @@ describe('TreeSelect.props', () => {
,
);
-
wrapper.find('.rc-tree-select-tree-checkbox').simulate('click');
-
const valueList = onChange.mock.calls[0][0];
expect(valueList).toEqual(['not exist', 'exist']);
});
- it('warning if use label', () => {
- const spy = jest.spyOn(global.console, 'error');
- console.log(">>> Follow Warning is for test purpose. Don't be scared :)");
- render();
- expect(spy).toHaveBeenCalledWith(
- "Warning: 'label' in treeData is deprecated. Please use 'title' instead.",
- );
- spy.mockRestore();
- });
-
describe('onDeselect trigger', () => {
const nodeProps1 = {
title: 'bamboo',
value: 'smart',
customize: 'beautiful',
};
-
const nodeProps2 = {
title: 'day',
value: 'light',
customize: 'well',
};
const propList = [nodeProps1, nodeProps2];
-
const createDeselectWrapper = props =>
mount(
@@ -711,7 +489,6 @@ describe('TreeSelect.props', () => {
,
);
-
const nodeMatcher = index =>
expect.objectContaining({
props: expect.objectContaining(propList[index]),
@@ -722,20 +499,9 @@ describe('TreeSelect.props', () => {
const onSelect = jest.fn();
const onDeselect = jest.fn();
const wrapper = createDeselectWrapper({ onSelect, onDeselect, defaultValue: 'smart' });
- wrapper
- .find('.rc-tree-select-tree-node-content-wrapper')
- .at(0)
- .simulate('click');
- expect(onSelect).not.toBeCalled();
- expect(onDeselect.mock.calls[0][0]).toEqual('smart');
- expect(onDeselect.mock.calls[0][1]).toEqual(nodeMatcher(0));
- expect(onDeselect.mock.calls[0][2]).toEqual({
- event: 'select',
- selected: false,
- nativeEvent: expect.objectContaining({}), // Not check native event
- node: nodeMatcher(0),
- selectedNodes: [],
- });
+ wrapper.selectNode(0);
+ expect(onDeselect).not.toHaveBeenCalled();
+ expect(onSelect).toHaveBeenCalledWith('smart', nodeMatcher(0));
});
});
@@ -766,18 +532,10 @@ describe('TreeSelect.props', () => {
defaultValue: ['smart', 'light'],
...props,
});
- wrapper
- .find('.rc-tree-select-tree-node-content-wrapper')
- .at(0)
- .simulate('click');
- expect(onSelect).not.toBeCalled();
- expect(onDeselect.mock.calls[0][0]).toEqual('smart');
- expect(onDeselect.mock.calls[0][1]).toEqual(nodeMatcher(0));
- expect(onDeselect.mock.calls[0][2]).toEqual({
- nativeEvent: expect.objectContaining({}), // Not check native event
- node: nodeMatcher(0),
- ...match,
- });
+
+ wrapper.selectNode(0);
+ expect(onSelect).not.toHaveBeenCalled();
+ expect(onDeselect).toHaveBeenCalledWith('smart', nodeMatcher(0));
});
it('click on selector', () => {
@@ -789,26 +547,10 @@ describe('TreeSelect.props', () => {
defaultValue: ['smart', 'light'],
...props,
});
- wrapper
- .find('.rc-tree-select-selection__choice__remove')
- .at(0)
- .simulate('click');
- expect(onSelect).not.toBeCalled();
- expect(onDeselect.mock.calls[0][0]).toEqual('smart');
- expect(onDeselect.mock.calls[0][1]).toEqual(nodeMatcher(0));
-
- const tgtArg3 = {
- nativeEvent: expect.objectContaining({}), // Not check native event
- node: nodeMatcher(0),
- ...match,
- };
-
- Object.keys(tgtArg3).forEach(key => {
- if (selectorSkip.includes(key)) return;
-
- const tgtVal = tgtArg3[key];
- expect(onDeselect.mock.calls[0][2][key]).toEqual(tgtVal);
- });
+
+ wrapper.clearSelection(0);
+ expect(onSelect).not.toHaveBeenCalled();
+ expect(onDeselect).toHaveBeenCalledWith('smart', nodeMatcher(0));
});
});
});
diff --git a/tests/Select.spec.js b/tests/Select.spec.js
index f3c50c5f..a22f7deb 100644
--- a/tests/Select.spec.js
+++ b/tests/Select.spec.js
@@ -1,15 +1,12 @@
/* eslint-disable no-undef react/no-multi-comp */
import React from 'react';
-import { render, mount } from 'enzyme';
+import { mount } from 'enzyme';
import KeyCode from 'rc-util/lib/KeyCode';
-import raf from 'raf';
import TreeSelect, { TreeNode } from '../src';
-import { resetAriaId, getLabel } from '../src/util';
import focusTest from './shared/focusTest';
describe('TreeSelect.basic', () => {
beforeEach(() => {
- resetAriaId();
jest.useFakeTimers();
});
@@ -24,7 +21,7 @@ describe('TreeSelect.basic', () => {
focusTest('single');
describe('render', () => {
- let treeData = [
+ const treeData = [
{ key: '0', value: '0', title: '0 label' },
{
key: '1',
@@ -38,7 +35,7 @@ describe('TreeSelect.basic', () => {
];
it('renders correctly', () => {
- const wrapper = render(
+ const wrapper = mount(
{
treeData={treeData}
/>,
);
- expect(wrapper).toMatchSnapshot();
+ expect(wrapper.render()).toMatchSnapshot();
});
it('renders tree correctly', () => {
- const wrapper = render(
+ const wrapper = mount(
{
treeData={treeData}
/>,
);
- expect(wrapper).toMatchSnapshot();
+ expect(wrapper.render()).toMatchSnapshot();
});
it('not crash if no children', () => {
- render();
+ mount();
});
it('renders disabled correctly', () => {
- const wrapper = render();
- expect(wrapper).toMatchSnapshot();
+ const wrapper = mount();
+ expect(wrapper.render()).toMatchSnapshot();
});
it('renders TreeNode correctly', () => {
- const wrapper = render(
-
+ const wrapper = mount(
+
@@ -85,12 +82,12 @@ describe('TreeSelect.basic', () => {
,
);
- expect(wrapper).toMatchSnapshot();
+ expect(wrapper.render()).toMatchSnapshot();
});
it('renders TreeNode correctly with falsy child', () => {
- const wrapper = render(
-
+ const wrapper = mount(
+
@@ -99,29 +96,32 @@ describe('TreeSelect.basic', () => {
,
);
- expect(wrapper).toMatchSnapshot();
+ expect(wrapper.render()).toMatchSnapshot();
});
it('renders treeDataSimpleMode correctly', () => {
- treeData = [
- { id: '0', value: '0', title: 'label0' },
- { id: '1', value: '1', title: 'label1', pId: '0' },
- ];
- const wrapper = render(
+ const wrapper = mount(
-
+
,
);
- expect(wrapper).toMatchSnapshot();
+ expect(wrapper.render()).toMatchSnapshot();
});
});
it('sets default value', () => {
- const treeData = [{ key: '0', value: '0', title: 'label0' }];
- const wrapper = mount();
- expect(wrapper.find('.rc-tree-select-selection__rendered > span').props().children).toBe(
- 'label0',
+ const wrapper = mount(
+ ,
);
+ expect(wrapper.getSelection(0).text()).toEqual('label0');
});
it('select value twice', () => {
@@ -133,17 +133,14 @@ describe('TreeSelect.basic', () => {
,
);
wrapper.openSelect();
- wrapper
- .find('.rc-tree-select-tree-title')
- .at(0)
- .simulate('click');
+ wrapper.selectNode();
expect(onChange.mock.calls[0][0]).toEqual('0');
+ expect(onChange).toHaveBeenCalledWith('0', expect.anything(), expect.anything());
+
+ onChange.mockReset();
wrapper.openSelect();
- wrapper
- .find('.rc-tree-select-tree-title')
- .at(1)
- .simulate('click');
- expect(onChange.mock.calls[1][0]).toEqual('1');
+ wrapper.selectNode(1);
+ expect(onChange).toHaveBeenCalledWith('1', expect.anything(), expect.anything());
});
it('can be controlled by value', () => {
@@ -152,11 +149,10 @@ describe('TreeSelect.basic', () => {
{ key: '1', value: '1', title: 'label1' },
];
const wrapper = mount();
- let choice = wrapper.find('.rc-tree-select-selection__rendered > span');
- expect(choice.prop('children')).toBe('label0');
+ expect(wrapper.getSelection(0).text()).toEqual('label0');
+
wrapper.setProps({ value: '1' });
- choice = wrapper.find('.rc-tree-select-selection__rendered > span');
- expect(choice.prop('children')).toBe('label1');
+ expect(wrapper.getSelection(0).text()).toEqual('label1');
});
describe('select', () => {
@@ -170,31 +166,32 @@ describe('TreeSelect.basic', () => {
const onChange = jest.fn();
const onSelect = jest.fn();
const wrapper = mount(createSelect({ onChange, onSelect }));
- wrapper.selectNode(0);
- const selectedNode = wrapper
- .find('TreeNode')
- .first()
- .instance();
-
- expect(onChange).toBeCalledWith('0', ['label0'], {
- preValue: [],
- selected: true,
- triggerNode: selectedNode,
- triggerValue: '0',
- });
+ wrapper.selectNode();
+ expect(onChange).toHaveBeenCalledWith(
+ '0',
+ ['label0'],
+ expect.objectContaining({
+ preValue: [],
+ selected: true,
+ triggerValue: '0',
+ triggerNode: expect.anything(),
+ }),
+ );
- const args = onSelect.mock.calls[0];
- expect(args[1]).toBe(selectedNode);
- expect(args[2]).toMatchObject({
- event: 'select',
- selected: true,
- });
+ expect(onSelect).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.objectContaining({
+ value: '0',
+ key: '0',
+ title: 'label0',
+ }),
+ );
});
it('render result by treeNodeLabelProp', () => {
const wrapper = mount(createSelect({ treeNodeLabelProp: 'value' }));
- wrapper.selectNode(0);
- expect(wrapper.find('.rc-tree-select-selection__rendered > span').prop('children')).toBe('0');
+ wrapper.selectNode();
+ expect(wrapper.getSelection(0).text()).toEqual('0');
});
});
@@ -206,15 +203,15 @@ describe('TreeSelect.basic', () => {
const createSelect = props => ;
it('renders search input', () => {
- const wrapper = render(createSelect());
- expect(wrapper).toMatchSnapshot();
+ const wrapper = mount(createSelect());
+ expect(wrapper.render()).toMatchSnapshot();
});
it('fires search event', () => {
const onSearch = jest.fn();
const wrapper = mount(createSelect({ onSearch }));
- wrapper.find('input').simulate('change', { target: { value: 'a' } });
- expect(onSearch).toBeCalledWith('a');
+ wrapper.search('a');
+ expect(onSearch).toHaveBeenCalledWith('a');
});
it('check tree changed by filter', () => {
@@ -228,14 +225,14 @@ describe('TreeSelect.basic', () => {
it('search nodes by filterTreeNode', () => {
const filter = (value, node) => node.props.value.toLowerCase() === value.toLowerCase();
const wrapper = mount(createSelect({ filterTreeNode: filter }));
- wrapper.find('input').simulate('change', { target: { value: 'A' } });
+ wrapper.search('A');
expect(wrapper.find('TreeNode')).toHaveLength(1);
expect(wrapper.find('TreeNode').prop('value')).toBe('a');
});
it('search nodes by treeNodeFilterProp', () => {
const wrapper = mount(createSelect({ treeNodeFilterProp: 'title' }));
- wrapper.find('input').simulate('change', { target: { value: 'labela' } });
+ wrapper.search('labela');
expect(wrapper.find('TreeNode')).toHaveLength(1);
expect(wrapper.find('TreeNode').prop('value')).toBe('a');
});
@@ -262,7 +259,7 @@ describe('TreeSelect.basic', () => {
,
);
wrapper.openSelect();
- expect(wrapper.state('open')).toBe(true);
+ expect(wrapper.isOpen()).toBeTruthy();
});
it('close tree when press ESC', () => {
@@ -271,17 +268,21 @@ describe('TreeSelect.basic', () => {
,
);
- wrapper.setState({ open: true });
- wrapper.find('.rc-tree-select-search__field').simulate('keyDown', { keyCode: KeyCode.ESC });
- expect(wrapper.state('open')).toBe(false);
+ wrapper.openSelect();
+ wrapper
+ .find('input')
+ .first()
+ .simulate('keyDown', { which: KeyCode.ESC });
+ expect(wrapper.isOpen()).toBeFalsy();
});
// https://github.com/ant-design/ant-design/issues/4084
it('checks node correctly after treeData updated', () => {
- const wrapper = mount();
+ const onChange = jest.fn();
+ const wrapper = mount();
wrapper.setProps({ treeData: [{ key: '0', value: '0', title: 'label0' }] });
wrapper.find('.rc-tree-select-tree-checkbox').simulate('click');
- expect(wrapper.state().valueList).toEqual([{ value: '0', label: 'label0' }]);
+ expect(onChange).toHaveBeenCalledWith(['0'], expect.anything(), expect.anything());
});
it('expands tree nodes by treeDefaultExpandedKeys', () => {
@@ -295,8 +296,12 @@ describe('TreeSelect.basic', () => {
,
);
- const node = wrapper.find('.rc-tree-select-tree-node-content-wrapper').at(1);
- expect(node.hasClass('rc-tree-select-tree-node-content-wrapper-open')).toBe(true);
+ expect(
+ wrapper
+ .find('.rc-tree-select-tree-treenode')
+ .at(1)
+ .hasClass('rc-tree-select-tree-treenode-switcher-open'),
+ ).toBeTruthy();
});
describe('allowClear', () => {
@@ -308,9 +313,9 @@ describe('TreeSelect.basic', () => {
);
wrapper.openSelect();
- wrapper.find('.rc-tree-select-tree-title').simulate('click');
- wrapper.find('.rc-tree-select-selection__clear').simulate('click');
- expect(wrapper.state().valueList).toEqual([]);
+ wrapper.selectNode();
+ wrapper.clearAll();
+ expect(wrapper.find('Select').props().value).toHaveLength(0);
});
it('has inputValue prop', () => {
@@ -336,21 +341,12 @@ describe('TreeSelect.basic', () => {
}
const wrapper = mount();
wrapper.openSelect();
- wrapper.selectNode(0);
- wrapper.find('.rc-tree-select-selection__clear').simulate('click');
- expect(wrapper.find(TreeSelect).instance().state.valueList).toEqual([]);
+ wrapper.selectNode();
+ wrapper.clearAll();
+ expect(wrapper.find('Select').props().value).toHaveLength(0);
});
});
- it('check title when label is a object', () => {
- const wrapper = render(
-
- Do not show} value="0" key="0" />
- ,
- );
- expect(wrapper).toMatchSnapshot();
- });
-
describe('keyCode', () => {
[KeyCode.ENTER, KeyCode.DOWN].forEach(code => {
it('open', () => {
@@ -362,52 +358,80 @@ describe('TreeSelect.basic', () => {
,
);
- wrapper.find('.rc-tree-select').simulate('keyDown', { keyCode: code });
- expect(wrapper.state('open')).toBe(true);
+ wrapper
+ .find('input')
+ .first()
+ .simulate('keyDown', { which: code });
+ expect(wrapper.isOpen()).toBeTruthy();
});
});
});
- describe('util', () => {
- it('getLabel never reach', () => {
- expect(getLabel({ value: 'newValue' })).toBe('newValue');
- });
- });
-
- describe('forceAlign', () => {
- it('onChoiceAnimationLeave trigger', done => {
+ describe('scroll to view', () => {
+ it('single mode should trigger scroll', () => {
const wrapper = mount(
-
+
,
);
- const instance = wrapper.instance();
- instance.forcePopupAlign = jest.fn();
+ wrapper.openSelect();
+ wrapper.openSelect();
+ expect(wrapper.isOpen()).toBeFalsy();
- instance.onChoiceAnimationLeave();
+ const scrollTo = jest.fn();
+ wrapper.find('List').instance().scrollTo = scrollTo;
- raf(() => {
- expect(instance.forcePopupAlign).toBeCalled();
- done();
- });
+ wrapper.openSelect();
+ expect(scrollTo).toHaveBeenCalled();
});
});
- describe('scroll to view', () => {
- it('single mode should trigger scroll', done => {
+ describe('accessibility', () => {
+ it('key operation', () => {
+ const onChange = jest.fn();
const wrapper = mount(
-
-
- ,
+ ,
);
+ function keyDown(code) {
+ wrapper
+ .find('input')
+ .first()
+ .simulate('keyDown', { which: code });
+ wrapper.update();
+ }
+
+ function matchValue(value) {
+ expect(onChange).toHaveBeenCalledWith(value, expect.anything(), expect.anything());
+ onChange.mockReset();
+ }
+
wrapper.openSelect();
- raf(() => {
- done();
- });
- jest.runAllTimers();
+ keyDown(KeyCode.DOWN);
+ keyDown(KeyCode.ENTER);
+ matchValue(['parent']);
+
+ keyDown(KeyCode.UP);
+ keyDown(KeyCode.ENTER);
+ matchValue(['parent', 'child']);
});
});
+
+ it('click in list should preventDefault', () => {
+ const wrapper = mount();
+
+ const preventDefault = jest.fn();
+ wrapper.find('.rc-tree-select-tree-node-content-wrapper').simulate('mouseDown', {
+ preventDefault,
+ });
+
+ expect(preventDefault).toHaveBeenCalled();
+ });
});
diff --git a/tests/Select.tree.spec.js b/tests/Select.tree.spec.js
index 44cdab3d..0c159074 100644
--- a/tests/Select.tree.spec.js
+++ b/tests/Select.tree.spec.js
@@ -1,22 +1,10 @@
/* eslint-disable no-undef, react/no-multi-comp, no-console */
import React from 'react';
-import { mount, render } from 'enzyme';
+import { mount } from 'enzyme';
+import { resetWarned } from 'rc-util/lib/warning';
import TreeSelect, { TreeNode as SelectNode } from '../src';
-import { resetAriaId } from '../src/util';
-// import { setMock } from './__mocks__/rc-animate';
describe('TreeSelect.tree', () => {
- beforeEach(() => {
- jest.useFakeTimers();
- resetAriaId();
- // setMock(true);
- });
-
- afterEach(() => {
- jest.useRealTimers();
- // setMock(false);
- });
-
const createSelect = props => (
@@ -69,33 +57,35 @@ describe('TreeSelect.tree', () => {
const wrapper = mount();
- wrapper.find('.rc-tree-select-tree-switcher').simulate('click');
- expect(wrapper.state().treeExpandedKeys).toEqual(['0-0']);
+ wrapper.switchNode();
+ expect(wrapper.find('Tree').props().expandedKeys).toEqual(['0-0']);
- wrapper
- .find('.rc-tree-select-tree-switcher')
- .at(2)
- .simulate('click');
- expect(wrapper.state().treeExpandedKeys).toEqual(['0-0', '0-0-1']);
+ wrapper.switchNode(2);
+ expect(wrapper.find('Tree').props().expandedKeys).toEqual(['0-0', '0-0-1']);
wrapper.find('button.reset').simulate('click');
- expect(wrapper.find('Tree').instance().state.expandedKeys).toEqual([]);
+ expect(wrapper.find('Tree').props().expandedKeys).toEqual([]);
+ });
+
+ it('warning if node key are not same as value', () => {
+ resetWarned();
+ const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ mount();
+ expect(spy).toHaveBeenCalledWith(
+ 'Warning: `key` or `value` with TreeNode must be the same or you can remove one of them. key: little, value: ttt.',
+ );
+ spy.mockRestore();
});
it('warning if node has same value', () => {
- const spy = jest.spyOn(global.console, 'error');
- console.log(">>> Follow Warning is for test purpose. Don't be scared :)");
- render(
+ resetWarned();
+ const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ mount(
,
);
- expect(spy).toHaveBeenCalledWith(
- "Warning: Conflict! value of node 'bamboo' (ttt) has already used by node 'little'.",
- );
+ expect(spy).toHaveBeenCalledWith('Warning: Same `value` exist in the tree: ttt');
spy.mockRestore();
});
@@ -107,6 +97,6 @@ describe('TreeSelect.tree', () => {
,
);
- expect(wrapper.render()).toMatchSnapshot();
+ expect(wrapper.getSelection(0).text()).toEqual('empty string');
});
});
diff --git a/tests/Select.warning.spec.js b/tests/Select.warning.spec.js
new file mode 100644
index 00000000..7bbde15e
--- /dev/null
+++ b/tests/Select.warning.spec.js
@@ -0,0 +1,74 @@
+import React from 'react';
+import { mount } from 'enzyme';
+import { resetWarned } from 'rc-util/lib/warning';
+import TreeSelect from '../src';
+
+describe('TreeSelect.warning', () => {
+ let spy = null;
+
+ beforeAll(() => {
+ spy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ });
+
+ beforeEach(() => {
+ resetWarned();
+ spy.mockReset();
+ });
+
+ afterAll(() => {
+ spy.mockRestore();
+ });
+
+ it('warns on invalid value when labelInValue', () => {
+ mount();
+
+ expect(spy).toHaveBeenCalledWith(
+ 'Warning: Invalid prop `value` supplied to `TreeSelect`. You should use { label: string, value: string | number } or [{ label: string, value: string | number }] instead.',
+ );
+ });
+
+ it('warns on invalid value when treeCheckable and treeCheckStrictly', () => {
+ mount();
+
+ expect(spy).toHaveBeenCalledWith(
+ 'Warning: Invalid prop `value` supplied to `TreeSelect`. You should use { label: string, value: string | number } or [{ label: string, value: string | number }] instead.',
+ );
+ });
+
+ it('warns on invalid value when single', () => {
+ mount();
+ expect(spy).toHaveBeenCalledWith(
+ 'Warning: `value` should not be array when `TreeSelect` is single mode.',
+ );
+ });
+
+ it('warns on invalid value when multiple', () => {
+ mount();
+ expect(spy).toHaveBeenCalledWith(
+ 'Warning: `value` should be an array when `TreeSelect` is checkable or multiple.',
+ );
+ });
+
+ it('treeCheckStrictly but not labelInValue', () => {
+ mount();
+ expect(spy).toHaveBeenCalledWith(
+ 'Warning: `treeCheckStrictly` will force set `labelInValue` to `true`.',
+ );
+ });
+
+ it('documentClickClose should removed', () => {
+ const wrapper = mount(
+ {
+ expect(documentClickClose).toBeFalsy();
+ }}
+ />,
+ );
+
+ wrapper.openSelect();
+
+ expect(spy).toHaveBeenCalledWith(
+ 'Warning: Second param of `onDropdownVisibleChange` has been removed.',
+ );
+ });
+});
diff --git a/tests/__mocks__/rc-virtual-list.js b/tests/__mocks__/rc-virtual-list.js
new file mode 100644
index 00000000..21b6e92a
--- /dev/null
+++ b/tests/__mocks__/rc-virtual-list.js
@@ -0,0 +1,3 @@
+import List from 'rc-virtual-list/lib/mock';
+
+export default List;
diff --git a/tests/__snapshots__/Select.checkable.spec.js.snap b/tests/__snapshots__/Select.checkable.spec.js.snap
index 3ce59a73..67ab8511 100644
--- a/tests/__snapshots__/Select.checkable.spec.js.snap
+++ b/tests/__snapshots__/Select.checkable.spec.js.snap
@@ -2,166 +2,194 @@
exports[`TreeSelect.checkable uncheck remove by selector not treeCheckStrictly 1`] = `
-
-
-
+
-
- 0
+ ×
-
-
+
+
+
+ 0-0
+
+
-
- 0-0
+ ×
-
-
+
+
+
+ 0-0-0
+
+
-
- 0-0-0
+ ×
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
- -
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ 0-0
+
+
+
+
+
+
+
+
@@ -172,18 +200,21 @@ exports[`TreeSelect.checkable uncheck remove by selector not treeCheckStrictly 1
class="rc-tree-select-tree-node-content-wrapper rc-tree-select-tree-node-content-wrapper-normal"
title="0-0-0"
>
+
0-0-0
-
-
-
-
-
-
+
+
+
+
+
+
@@ -192,118 +223,134 @@ exports[`TreeSelect.checkable uncheck remove by selector not treeCheckStrictly 1
exports[`TreeSelect.checkable uncheck remove by selector not treeCheckStrictly 2`] = `
-
-
-
-
-
-
-
+
+
+
+
+
+
-
- -
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ 0-0
+
+
+
+
+
+
+
+
@@ -314,18 +361,21 @@ exports[`TreeSelect.checkable uncheck remove by selector not treeCheckStrictly 2
class="rc-tree-select-tree-node-content-wrapper rc-tree-select-tree-node-content-wrapper-normal"
title="0-0-0"
>
+
0-0-0
-
-
-
-
-
-
+
+
+
+
+
+
@@ -334,166 +384,194 @@ exports[`TreeSelect.checkable uncheck remove by selector not treeCheckStrictly 2
exports[`TreeSelect.checkable uncheck remove by tree check 1`] = `
-
-
-
+
-
- 0
+ ×
-
-
+
+
+
+ 0-0
+
+
-
- 0-0
+ ×
-
-
+
+
+
+ 0-0-0
+
+
-
- 0-0-0
+ ×
-
-
+
+
+
+
-
-
-
- 0
-
-
-
-
-
-
-
-
+ 0
+
+
+
+
-
- -
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ 0-0
+
+
+
+
+
+
+
+
@@ -504,18 +582,21 @@ exports[`TreeSelect.checkable uncheck remove by tree check 1`] = `
class="rc-tree-select-tree-node-content-wrapper rc-tree-select-tree-node-content-wrapper-normal"
title="0-0-0"
>
+
0-0-0
-
-
-
-
-
-
+
+
+
+
+
+
@@ -524,118 +605,131 @@ exports[`TreeSelect.checkable uncheck remove by tree check 1`] = `
exports[`TreeSelect.checkable uncheck remove by tree check 2`] = `
-
-
-
-
-
-
-
+ 0
+
+
+
+
-
- -
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ 0-0
+
+
+
+
+
+
+
+
@@ -646,18 +740,21 @@ exports[`TreeSelect.checkable uncheck remove by tree check 2`] = `
class="rc-tree-select-tree-node-content-wrapper rc-tree-select-tree-node-content-wrapper-normal"
title="0-0-0"
>
+
0-0-0
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/tests/__snapshots__/Select.multiple.spec.js.snap b/tests/__snapshots__/Select.multiple.spec.js.snap
index 8050e784..d92bf666 100644
--- a/tests/__snapshots__/Select.multiple.spec.js.snap
+++ b/tests/__snapshots__/Select.multiple.spec.js.snap
@@ -1,314 +1,39 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TreeSelect.multiple can hide search box by showSearch = false 1`] = `
-
-
-
-
-
-`;
-
-exports[`TreeSelect.multiple maxTagCount illegal 1`] = `
-
-
-
-`;
-
-exports[`TreeSelect.multiple maxTagCount legal 1`] = `
-
-
-
-`;
-
-exports[`TreeSelect.multiple maxTagCount maxTagPlaceholder function 1`] = `
-
-
-
-`;
-
-exports[`TreeSelect.multiple maxTagCount maxTagPlaceholder string 1`] = `
-
-
-
-`;
-
-exports[`TreeSelect.multiple maxTagCount zero 1`] = `
-
-
-
+
+
+
+
`;
-
-exports[`TreeSelect.multiple renders clear button 1`] = `null`;
diff --git a/tests/__snapshots__/Select.props.spec.js.snap b/tests/__snapshots__/Select.props.spec.js.snap
deleted file mode 100644
index 2cfe384e..00000000
--- a/tests/__snapshots__/Select.props.spec.js.snap
+++ /dev/null
@@ -1,3725 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`TreeSelect.props allowClear functional works 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 0-0
-
-
-
- -
-
-
-
- Title 0-1
-
-
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props allowClear functional works 2`] = `
-
-
-
-
-
- Title 0-1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 0-0
-
-
-
- -
-
-
-
- Title 0-1
-
-
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props allowClear functional works 3`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 0-0
-
-
-
- -
-
-
-
- Title 0-1
-
-
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props animation 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props async update treeData when has searchInput 1`] = `
-
-`;
-
-exports[`TreeSelect.props async update treeData when has searchInput 2`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 111
-
-
-
-
- Not Found
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props basic 1`] = `
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props basic 2`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props choiceTransitionName 1`] = `
-
-`;
-
-exports[`TreeSelect.props choiceTransitionName 2`] = `
-
-`;
-
-exports[`TreeSelect.props className 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 0-0
-
-
-
- -
-
-
-
- Title 0-1
-
-
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props defaultValue 1`] = `
-
-
-
-
-
- Title 0-0
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props dropdownClassName 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 0-0
-
-
-
- -
-
-
-
- Title 0-1
-
-
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props dropdownMatchSelectWidth default 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 0-0
-
-
-
- -
-
-
-
- Title 0-1
-
-
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props dropdownMatchSelectWidth false 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 0-0
-
-
-
- -
-
-
-
- Title 0-1
-
-
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props dropdownMatchSelectWidth true 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 0-0
-
-
-
- -
-
-
-
- Title 0-1
-
-
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props dropdownStyle 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 0-0
-
-
-
- -
-
-
-
- Title 0-1
-
-
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props filterTreeNode false 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Title 1
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 0-0
-
-
-
- -
-
-
-
- Title 0-1
-
-
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props filterTreeNode function 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Title 1
-
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props filterTreeNode function 2`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0-0
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 0-0
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props loadData 1`] = `
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props maxTagTextLength 1`] = `
-
-`;
-
-exports[`TreeSelect.props notFoundContent 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Noting Matched!
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props placeholder 1`] = `
-
-
-
-
-
- RC Component
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props prefixCls 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 0-0
-
-
-
- -
-
-
-
- Title 0-1
-
-
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props searchPlaceholder 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- RC Component
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 0-0
-
-
-
- -
-
-
-
- Title 0-1
-
-
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props showArrow 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 0-0
-
-
-
- -
-
-
-
- Title 0-1
-
-
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props showSearch 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 0-0
-
-
-
- -
-
-
-
- Title 0-1
-
-
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props transitionName 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props treeDefaultExpandAll 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 0-0
-
-
-
- -
-
-
-
- Title 0-1
-
-
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props treeDefaultExpandAll 2`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props treeIcon 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Title 0
-
-
-
- -
-
-
-
-
- Title 0-0
-
-
-
- -
-
-
-
-
- Title 0-1
-
-
-
-
-
- -
-
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
-
-exports[`TreeSelect.props treeLine 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- Title 0
-
-
-
- -
-
-
-
- Title 0-0
-
-
-
- -
-
-
-
- Title 0-1
-
-
-
-
-
- -
-
-
-
- Title 1
-
-
-
-
-
-
-
-
-`;
diff --git a/tests/__snapshots__/Select.spec.js.snap b/tests/__snapshots__/Select.spec.js.snap
index ad828f0b..cf687a51 100644
--- a/tests/__snapshots__/Select.spec.js.snap
+++ b/tests/__snapshots__/Select.spec.js.snap
@@ -1,712 +1,1023 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`TreeSelect.basic check title when label is a object 1`] = `
-
-
-
-
- Do not show
-
-
+
-
-
-`;
-
-exports[`TreeSelect.basic render renders TreeNode correctly 1`] = `
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0 label
+
+
+
+
+
+
+
+
+ 1 label
+
+
+
+
+
+
+
+
+
+
+
+ 10 label
+
+
+
+
+
+
+
+
+
+
+
+ 11 label
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
`;
exports[`TreeSelect.basic render renders TreeNode correctly with falsy child 1`] = `
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0 label
+
+
+
+
+
+
+
+
+ 1 label
+
+
+
+
+
+
+
+
+
+
+
+ 10 label
+
+
+
+
+
+
+
+
+
+
+
+ 11 label
+
+
+
+
+
+
+
+
+
+
+
+
-
+
`;
exports[`TreeSelect.basic render renders correctly 1`] = `
-
-
-
-
-
+
+
+
+
+
+
`;
exports[`TreeSelect.basic render renders disabled correctly 1`] = `
-
-
-
+
+
+
-
+
`;
exports[`TreeSelect.basic render renders tree correctly 1`] = `
-
-
-
-
-
+
+
+
+
+
+
`;
exports[`TreeSelect.basic render renders treeDataSimpleMode correctly 1`] = `
-
-
-
-
-
-
-
+
+
-
-
-
-
+
+
+
-
- label0
-
-
-
- -
-
-
+
-
- label1
-
-
-
-
-
-
+
+
+
+
+ label0
+
+
+
+
+
+
+
+
+
+
+
+ label1
+
+
+
+
+
+
+
+
+
+
+
`;
exports[`TreeSelect.basic search nodes check tree changed by filter 1`] = `
-
-
-
-
-
-
-
-
+
+
-
-
-
-
+
+
+
-
- labela
-
-
-
-
+
+
+
+
+
+
+
+ labela
+
+
+
+
+
+
+
+
+
+
+
`;
exports[`TreeSelect.basic search nodes check tree changed by filter 2`] = `
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
- -
-
-
+
+
-
- labela
-
-
-
-
-
-
-
+
+
-
- labelb
-
-
-
-
+
+
+
+
+
+
+
+ labela
+
+
+
+
+
+
+
+
+ labelb
+
+
+
+
+
+
+
+
+
+
+
`;
exports[`TreeSelect.basic search nodes filter node but not remove then 1`] = `
-
-
-
-
-
-
-
-
+
+
-
-
-
-
+
+
+
-
- labela
-
-
-
-
-
-
-
- labelb
-
-
-
-
+
+
+
+
+
+
+
+ labela
+
+
+
+
+
+
+
+
+ labelb
+
+
+
+
+
+
+
+
+
+
+
`;
exports[`TreeSelect.basic search nodes renders search input 1`] = `
-Array [
-
+
-
-
-
-
- ,
+
+
-
-
-
+
+
-
-
-
-
-
-
- -
+
-
-
-
- labela
-
-
-
-
-
-
-
-
+
- labelb
-
-
-
-
+
+
+
+
+
+ labela
+
+
+
+
+
+
+
+
+ labelb
+
+
+
+
+
+
+
-
,
-]
+
+
+
+
+
`;
diff --git a/tests/__snapshots__/Select.tree.spec.js.snap b/tests/__snapshots__/Select.tree.spec.js.snap
deleted file mode 100644
index 6e559d44..00000000
--- a/tests/__snapshots__/Select.tree.spec.js.snap
+++ /dev/null
@@ -1,29 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`TreeSelect.tree empty string is also a value 1`] = `
-
-
-
-
- empty string
-
-
-
-
-
-`;
diff --git a/tests/propTypes.spec.js b/tests/propTypes.spec.js
deleted file mode 100644
index ae8b33ab..00000000
--- a/tests/propTypes.spec.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import ReactPropTypesSecret from 'prop-types/lib/ReactPropTypesSecret';
-import { valueProp } from '../src/propTypes';
-
-// React only console error once when the message is the same for the same propType.
-// Use native Type to check the validate.
-describe('TreeSelect.propTypes', () => {
- it('warns on invalid value when labelInValue', () => {
- const error = valueProp({
- labelInValue: true,
- value: 'foo',
- }, 'value', 'TreeSelect', '', '', ReactPropTypesSecret);
- expect(error.message).toBe(
- 'Invalid prop `value` supplied to `TreeSelect`. You should use { label: string, value: string | number } or [{ label: string, value: string | number }] instead.'
- );
- });
-
- it('warns on invalid value when treeCheckable and treeCheckStrictly', () => {
- const error = valueProp({
- treeCheckable: true,
- treeCheckStrictly: true,
- value: 'foo',
- }, 'value', 'TreeSelect', '', '', ReactPropTypesSecret);
- expect(error.message).toBe(
- 'Invalid prop `value` supplied to `TreeSelect`. You should use { label: string, value: string | number } or [{ label: string, value: string | number }] instead.'
- );
- });
-
- it('warns on invalid value when multiple', () => {
- const error = valueProp({
- multiple: true,
- value: '',
- }, 'value', 'TreeSelect', '', '', ReactPropTypesSecret);
- expect(error).toBeNull();
- });
-
- it('warns on invalid value when type is not string', () => {
- const error = valueProp({
- value: true,
- }, 'value', 'TreeSelect', '', '', ReactPropTypesSecret);
- expect(error.message).toBe(
- 'Invalid prop `value` supplied to `TreeSelect`. You should use string or [string] instead.'
- );
- });
-});
diff --git a/tests/setup.js b/tests/setup.js
index 7e2e2535..fdb38405 100644
--- a/tests/setup.js
+++ b/tests/setup.js
@@ -1,7 +1,8 @@
-
-global.requestAnimationFrame = global.requestAnimationFrame || function requestAnimationFrame(cb) {
- return setTimeout(cb, 0);
-};
+global.requestAnimationFrame =
+ global.requestAnimationFrame ||
+ function requestAnimationFrame(cb) {
+ return setTimeout(cb, 0);
+ };
const Enzyme = require('enzyme');
const Adapter = require('enzyme-adapter-react-16');
@@ -10,11 +11,46 @@ Enzyme.configure({ adapter: new Adapter() });
Object.assign(Enzyme.ReactWrapper.prototype, {
openSelect() {
- this.find('.rc-tree-select').simulate('click');
- jest.runAllTimers();
- this.update();
+ this.find('.rc-tree-select-selector').simulate('mousedown');
+ },
+ selectNode(index = 0) {
+ this.find('.rc-tree-select-tree-node-content-wrapper')
+ .at(index)
+ .simulate('click');
+ },
+ switchNode(index = 0) {
+ this.find('.rc-tree-select-tree-switcher')
+ .at(index)
+ .simulate('click');
+ },
+ getSelection(index) {
+ const selections = this.find('.rc-tree-select-selection-item');
+ if (index !== undefined) {
+ const selection = selections.at(index);
+ const content = selection.find('.rc-tree-select-selection-item-content');
+
+ return content.length ? content : selection;
+ }
+ return selections;
+ },
+ clearSelection(index = 0) {
+ return this.getSelection()
+ .at(index)
+ .find('.rc-tree-select-selection-item-remove')
+ .hostNodes()
+ .simulate('click');
+ },
+ clearAll() {
+ return this.find('.rc-tree-select-clear')
+ .first()
+ .simulate('mouseDown');
+ },
+ search(text) {
+ this.find('input.rc-tree-select-selection-search-input').simulate('change', {
+ target: { value: text },
+ });
},
- selectNode(index) {
- this.find('.rc-tree-select-tree-node-content-wrapper').at(index).simulate('click');
+ isOpen() {
+ return this.find('.rc-tree-select').hasClass('rc-tree-select-open');
},
});
diff --git a/tests/shared/focusTest.js b/tests/shared/focusTest.js
index 10a2cafe..7ec9de34 100644
--- a/tests/shared/focusTest.js
+++ b/tests/shared/focusTest.js
@@ -1,7 +1,7 @@
/* eslint-disable no-undef */
import React from 'react';
import { mount } from 'enzyme';
-import TreeSelect from '../../src/Select';
+import TreeSelect from '../../src';
export default function focusTest(mode) {
let container;
@@ -17,55 +17,35 @@ export default function focusTest(mode) {
it('focus()', () => {
const handleFocus = jest.fn();
- const treeData = [
- { key: '0', value: '0', title: '0 label' },
- ];
+ const treeData = [{ key: '0', value: '0', title: '0 label' }];
const wrapper = mount(
- ,
- { attachTo: container }
+ ,
+ { attachTo: container },
);
wrapper.instance().focus();
- expect(handleFocus).toBeCalled();
+ expect(handleFocus).toHaveBeenCalled();
});
-
it('blur()', () => {
const handleBlur = jest.fn();
- const treeData = [
- { key: '0', value: '0', title: '0 label' },
- ];
+ const treeData = [{ key: '0', value: '0', title: '0 label' }];
const wrapper = mount(
- ,
- { attachTo: container }
+ ,
+ { attachTo: container },
);
wrapper.instance().focus();
wrapper.instance().blur();
- expect(handleBlur).toBeCalled();
+ expect(handleBlur).toHaveBeenCalled();
});
it('autoFocus', () => {
const handleFocus = jest.fn();
- const treeData = [
- { key: '0', value: '0', title: '0 label' },
- ];
+ const treeData = [{ key: '0', value: '0', title: '0 label' }];
mount(
- ,
- { attachTo: container }
+ ,
+ { attachTo: container },
);
- expect(handleFocus).toBeCalled();
+ expect(handleFocus).toHaveBeenCalled();
});
}
diff --git a/tests/util.spec.js b/tests/util.spec.js
deleted file mode 100644
index 03ca6a5d..00000000
--- a/tests/util.spec.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { findPopupContainer } from '../src/util';
-
-describe('util', () => {
- it('should stop loop if get null', () => {
- const node = {
- parentNode: {
- parentNode: {},
- },
- };
- expect(findPopupContainer(node, 'not-exist')).toBe(null);
- });
-});
diff --git a/tests/utils.spec.js b/tests/utils.spec.js
new file mode 100644
index 00000000..d178d31a
--- /dev/null
+++ b/tests/utils.spec.js
@@ -0,0 +1,19 @@
+import { isValueDisabled } from '../src/utils/valueUtil';
+import { isDisabled } from '../src/hooks/useKeyValueMapping';
+
+describe('TreeSelect.util', () => {
+ it('isValueDisabled', () => {
+ const options = [{ data: { value: 'disabled', disabled: true } }, { data: { value: 'pass' } }];
+ expect(isValueDisabled('disabled', options)).toBeTruthy();
+ expect(isValueDisabled('pass', options)).toBeFalsy();
+ expect(isValueDisabled('not-exist', options)).toBeFalsy();
+ });
+
+ it('isDisabled', () => {
+ expect(isDisabled({ data: { disabled: true } }, 'select')).toBeTruthy();
+ expect(isDisabled({ data: { disableCheckbox: true } }, 'select')).toBeFalsy();
+ expect(isDisabled({ data: { disabled: true } }, 'checkbox')).toBeTruthy();
+ expect(isDisabled({ data: { disableCheckbox: true } }, 'checkbox')).toBeTruthy();
+ expect(isDisabled({ data: { disabled: true } }, null)).toBeFalsy();
+ });
+});