From d338444bc7467c2d448e0404d3bd72de0c23ca9a Mon Sep 17 00:00:00 2001 From: Siddhartha Gandhi Date: Sat, 6 Aug 2022 15:11:09 -0400 Subject: [PATCH] feat: Add new props for search, keyboard navigation, and scrolling The new prop searchTerm can be used to set the initial search term or fully control the search semantics for a higher-order component. The new prop onSearchChange allows specification of a callback for when the search term changes or the search mode is activated or deactivated. The new prop disableKeyboardNavigation allows disabling all actions associated with a key down event in the search input box. The new prop pageSize can be used to control the size of the scroll view before scrolling to near the bottom is required to show more nodes. --- README.md | 42 ++++++++++++++++++++++++++- docs/src/stories/Options/index.js | 31 ++++++++++++++++++++ docs/src/stories/Simple/index.js | 5 ++++ src/index.js | 24 +++++++++++++-- types/react-dropdown-tree-select.d.ts | 9 ++++++ 5 files changed, 108 insertions(+), 3 deletions(-) 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 {