diff --git a/README.md b/README.md index 41068257..80031daa 100644 --- a/README.md +++ b/README.md @@ -41,12 +41,14 @@ A lightweight and fast control to render a select component that can display hie - [Usage](#usage) - [Props](#props) - [className](#classname) + - [searchTerm](#searchterm) - [clearSearchOnChange](#clearsearchonchange) - [onChange](#onchange) - [onNodeToggle](#onnodetoggle) - [onAction](#onaction) - [onFocus](#onfocus) - [onBlur](#onblur) + - [onSearchChange](#onsearchchange) - [data](#data) - [texts](#texts) - [keepTreeOnSearch](#keeptreeonsearch) @@ -57,6 +59,7 @@ A lightweight and fast control to render a select component that can display hie - [hierarchical](#hierarchical) - [simpleSelect](#simpleselect) - [radioSelect](#radioselect) + - [pageSize](#pagesize) - [showPartiallySelected](#showpartiallyselected) - [showDropdown](#showdropdown) - [initial](#initial) @@ -66,7 +69,8 @@ A lightweight and fast control to render a select component that can display hie - [searchPredicate](#searchpredicate) - [inlineSearchInput](#inlinesearchinput) - [tabIndex](#tabIndex) - - [disablePoppingOnBackspace](#disablePoppingOnBackspace) + - [disablePoppingOnBackspace](#disablepoppingonbackspace) + - [disableKeyboardNavigation](#disablekeyboardnavigation) - [Styling and Customization](#styling-and-customization) - [Using default styles](#default-styles) - [Customizing with Bootstrap, Material Design styles](#customizing-styles) @@ -188,6 +192,12 @@ Type: `string` Additional classname for container. The container renders with a default classname of `react-dropdown-tree-select`. +### searchTerm + +Type: `string` + +Initializes or adjusts the active search term. Set to an empty string or `undefined` to turn search mode off. + ### clearSearchOnChange Type: `bool` @@ -256,6 +266,24 @@ Type: `function` Fires when input box loses focus or the dropdown arrow is clicked again (and the dropdown collapses). This is helpful for setting `dirty` or `touched` flags with forms. +### onSearchChange + +Type: `function` + +Called when the search input box is changed with the current search term. This can be fired either through user input or automatically due to `clearSearchOnChange`. Example: + +```jsx +function onSearchChange(searchTerm: str) { + if (searchTerm) { + console.log('New search term is', searchTerm) + } else { + console.log('Search mode has been disabled') + } +} + +return +``` + ### data Type: `Object` or `Array` @@ -361,6 +389,12 @@ Like `simpleSelect`, you can only select one value; but keeps the tree/children ⚠️ If multiple nodes in data are selected - by setting either `checked` or `isDefaultValue`, only the first visited node stays selected. +### pageSize + +Type: `number` (default: `100`) + +Customize the number of nodes displayed in the tree before a scroll to near the bottom is required to load additional nodes. + ### showPartiallySelected Type: `bool` (default: `false`) @@ -428,6 +462,12 @@ Type: `bool` (default: `false`) `disablePoppingOnBackspace=true` attribute indicates that when a user triggers a 'backspace' keyDown in the empty search bar, the tree will not deselect nodes. +### disableKeyboardNavigation + +Type: `bool` (default: `false`) + +`disableKeyboardNavigation=true` prevents keyboard navigation actions from being taken on the nodes when the user triggers a keyDown in the search bar. This restores standard input box semantics. + ## Styling and Customization ### Default styles diff --git a/docs/src/stories/Options/index.js b/docs/src/stories/Options/index.js index b4ce145c..ba8db35c 100644 --- a/docs/src/stories/Options/index.js +++ b/docs/src/stories/Options/index.js @@ -11,10 +11,12 @@ class WithOptions extends PureComponent { super(props) this.state = { + searchTerm: '', clearSearchOnChange: false, keepTreeOnSearch: false, keepOpenOnSelect: false, mode: 'multiSelect', + pageSize: undefined, inlineSearchInput: false, showPartiallySelected: false, disabled: false, @@ -39,12 +41,18 @@ class WithOptions extends PureComponent { this.setState({ [value]: !this.state[value] }) } + onSearchChange = searchTerm => { + this.setState({ searchTerm: searchTerm }) + } + render() { const { + searchTerm, clearSearchOnChange, keepTreeOnSearch, keepOpenOnSelect, mode, + pageSize, showPartiallySelected, disabled, readOnly, @@ -105,6 +113,26 @@ class WithOptions extends PureComponent { onChange={e => this.setState({ inlineSearchPlaceholder: e.target.value })} /> +
+ + this.setState({ searchTerm: e.target.value })} + /> +
+
+ + this.setState({ pageSize: e.target.valueAsNumber || undefined })} + /> +
{ console.log('onBlur') } +const onSearchChange = searchTerm => { + console.log('onSearchChange::', searchTerm) +} + const Simple = () => (

Basic component

@@ -45,6 +49,7 @@ const Simple = () => ( onNodeToggle={onNodeToggle} onFocus={onFocus} onBlur={onBlur} + onSearchChange={onSearchChange} className="demo" />
diff --git a/src/index.js b/src/index.js index 2944850f..90bebd7c 100644 --- a/src/index.js +++ b/src/index.js @@ -23,6 +23,7 @@ import { getAriaLabel } from './a11y' class DropdownTreeSelect extends Component { static propTypes = { data: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired, + searchTerm: PropTypes.string, clearSearchOnChange: PropTypes.bool, keepTreeOnSearch: PropTypes.bool, keepChildrenOnSearch: PropTypes.bool, @@ -41,7 +42,9 @@ class DropdownTreeSelect extends Component { onNodeToggle: PropTypes.func, onFocus: PropTypes.func, onBlur: PropTypes.func, + onSearchChange: PropTypes.func, mode: PropTypes.oneOf(['multiSelect', 'simpleSelect', 'radioSelect', 'hierarchical']), + pageSize: PropTypes.number, showPartiallySelected: PropTypes.bool, disabled: PropTypes.bool, readOnly: PropTypes.bool, @@ -50,6 +53,7 @@ class DropdownTreeSelect extends Component { inlineSearchInput: PropTypes.bool, tabIndex: PropTypes.number, disablePoppingOnBackspace: PropTypes.bool, + disableKeyboardNavigation: PropTypes.bool, } static defaultProps = { @@ -57,11 +61,13 @@ class DropdownTreeSelect extends Component { onFocus: () => {}, onBlur: () => {}, onChange: () => {}, + onSearchChange: (_) => {}, texts: {}, showDropdown: 'default', inlineSearchInput: false, tabIndex: 0, disablePoppingOnBackspace: false, + disableKeyboardNavigation: true, } constructor(props) { @@ -73,7 +79,7 @@ class DropdownTreeSelect extends Component { this.clientId = props.id || clientIdGenerator.get(this) } - initNewProps = ({ data, mode, showDropdown, showPartiallySelected, searchPredicate }) => { + initNewProps = ({ data, searchTerm, mode, showDropdown, showPartiallySelected, searchPredicate }) => { this.treeManager = new TreeManager({ data, mode, @@ -86,7 +92,12 @@ class DropdownTreeSelect extends Component { if (currentFocusNode) { currentFocusNode._focused = true } + const searchTermChanged = searchTerm !== prevState.searchTerm + if (this.searchInput && searchTermChanged) { + this.searchInput.value = searchTerm + } return { + searchModeOn: searchTermChanged, showDropdown: /initial|always/.test(showDropdown) || prevState.showDropdown === true, ...this.treeManager.getTreeAndTags(), } @@ -96,7 +107,10 @@ class DropdownTreeSelect extends Component { resetSearchState = () => { // clear the search criteria and avoid react controlled/uncontrolled warning if (this.searchInput) { - this.searchInput.value = '' + if (this.searchInput.value !== '') { + this.props.onSearchChange(this.searchInput.value) + this.searchInput.value = '' + } } return { @@ -154,6 +168,7 @@ class DropdownTreeSelect extends Component { this.props.keepChildrenOnSearch ) const searchModeOn = value.length > 0 + this.props.onSearchChange(value) this.setState({ tree, @@ -238,6 +253,10 @@ class DropdownTreeSelect extends Component { } onKeyboardKeyDown = e => { + if (this.props.disableKeyboardNavigation) { + return // Will fire the default action + } + const { readOnly, mode, disablePoppingOnBackspace } = this.props const { showDropdown, tags, searchModeOn, currentFocus } = this.state const tm = this.treeManager @@ -360,6 +379,7 @@ class DropdownTreeSelect extends Component { onCheckboxChange={this.onCheckboxChange} onNodeToggle={this.onNodeToggle} mode={mode} + pageSize={this.props.pageSize} showPartiallySelected={this.props.showPartiallySelected} {...commonProps} /> diff --git a/types/react-dropdown-tree-select.d.ts b/types/react-dropdown-tree-select.d.ts index b9e4f2b8..962601c7 100644 --- a/types/react-dropdown-tree-select.d.ts +++ b/types/react-dropdown-tree-select.d.ts @@ -10,6 +10,8 @@ declare module 'react-dropdown-tree-select' { export interface DropdownTreeSelectProps { data: TreeData + /** Initialize the search input with the specified term and search the nodes */ + searchTerm?: str /** Clear the input search if a node has been selected/unselected */ clearSearchOnChange?: boolean /** Displays search results as a tree instead of flattened results */ @@ -54,6 +56,9 @@ declare module 'react-dropdown-tree-select' { * This is helpful for setting dirty or touched flags with forms */ onBlur?: () => void + /** Fires when search input is modified. + */ + onSearchChange?: (searchTerm: str) => void /** Defines how the dropdown is rendered / behaves * * - multiSelect @@ -79,6 +84,8 @@ declare module 'react-dropdown-tree-select' { * * */ mode?: Mode + /** The size (in nodes) of a single page in the infinite scroll component. */ + pageSize?: number /** If set to true, shows checkboxes in a partial state when one, but not all of their children are selected. * Allows styling of partially selected nodes as well, by using :indeterminate pseudo class. * Simply add desired styles to .node.partial .checkbox-item:indeterminate { ... } in your CSS @@ -103,6 +110,8 @@ declare module 'react-dropdown-tree-select' { * search bar, the tree will not deselect nodes. */ disablePoppingOnBackspace?: boolean + /** dsiableKeyboardNavigation will disable keyboard navigation of the tree */ + dsiableKeyboardNavigation?: boolean } export interface DropdownTreeSelectState {