diff --git a/README.md b/README.md index aecabd37..b797d701 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ A lightweight and fast control to render a select component that can display hie - [keepOpenOnSelect](#keepopenonselect) - [mode](#mode) - [multiSelect](#multiSelect) + - [hierarchical](#hierarchical) - [simpleSelect](#simpleSelect) - [radioSelect](#radioSelect) - [showPartiallySelected](#showpartiallyselected) @@ -327,7 +328,13 @@ Defines how the dropdown is rendered / behaves #### multiSelect -This is the default mode. A multi selectable dropdown which supports hierarchical data. +A multi selectable dropdown which supports tree data with parent-child relationships. This is the default mode. + +#### hierarchical + +A multi selectable dropdown which supports tree data **without** parent-child relationships. In this mode, selecting a node has no ripple effects on its descendants or ancestors. Subsequently, `showPartiallySelected` becomes a moot flag and has no effect as well. + +⚠️ Note that `hierarchical=true` negates/overrides `showPartiallySelected`. #### simpleSelect diff --git a/docs/src/stories/Options/index.js b/docs/src/stories/Options/index.js index ed6a6c79..a75339b0 100644 --- a/docs/src/stories/Options/index.js +++ b/docs/src/stories/Options/index.js @@ -45,7 +45,6 @@ class WithOptions extends PureComponent { showPartiallySelected, disabled, readOnly, - hierarchical, } = this.state return ( @@ -66,6 +65,7 @@ class WithOptions extends PureComponent { + -
diff --git a/package.json b/package.json index 7f76eebb..c4a65e25 100644 --- a/package.json +++ b/package.json @@ -184,7 +184,7 @@ } }, "lint-staged": { - "*.{js,json,css,md}": [ + "*.{js,json,css,md,ts}": [ "prettier --write", "git add -f" ] diff --git a/src/index.js b/src/index.js index d373ca5b..6d03592c 100644 --- a/src/index.js +++ b/src/index.js @@ -42,11 +42,10 @@ class DropdownTreeSelect extends Component { onNodeToggle: PropTypes.func, onFocus: PropTypes.func, onBlur: PropTypes.func, - mode: PropTypes.oneOf(['multiSelect', 'simpleSelect', 'radioSelect']), + mode: PropTypes.oneOf(['multiSelect', 'simpleSelect', 'radioSelect', 'hierarchical']), showPartiallySelected: PropTypes.bool, disabled: PropTypes.bool, readOnly: PropTypes.bool, - hierarchical: PropTypes.bool, id: PropTypes.string, } @@ -67,12 +66,11 @@ class DropdownTreeSelect extends Component { this.clientId = props.id || clientIdGenerator.get(this) } - initNewProps = ({ data, mode, showPartiallySelected, hierarchical }) => { + initNewProps = ({ data, mode, showPartiallySelected }) => { this.treeManager = new TreeManager({ data, mode, showPartiallySelected, - hierarchical, rootPrefixId: this.clientId, }) // Restore focus-state @@ -94,8 +92,8 @@ class DropdownTreeSelect extends Component { } componentWillMount() { - const { data, hierarchical } = this.props - this.initNewProps({ data, hierarchical, ...this.props }) + const { data, mode, showPartiallySelected } = this.props + this.initNewProps({ data, mode, showPartiallySelected }) } componentWillUnmount() { diff --git a/src/tree-manager/index.js b/src/tree-manager/index.js index b5efa456..f345ffa1 100644 --- a/src/tree-manager/index.js +++ b/src/tree-manager/index.js @@ -5,23 +5,24 @@ import nodeVisitor from './nodeVisitor' import keyboardNavigation, { FocusActionNames } from './keyboardNavigation' class TreeManager { - constructor({ data, mode, showPartiallySelected, hierarchical, rootPrefixId }) { + constructor({ data, mode, showPartiallySelected, rootPrefixId }) { this._src = data this.simpleSelect = mode === 'simpleSelect' this.radioSelect = mode === 'radioSelect' + this.hierarchical = mode === 'hierarchical' const { list, defaultValues, singleSelectedNode } = flattenTree({ tree: JSON.parse(JSON.stringify(data)), simple: this.simpleSelect, radio: this.radioSelect, showPartialState: showPartiallySelected, - hierarchical, + hierarchical: this.hierarchical, rootPrefixId, }) this.tree = list this.defaultValues = defaultValues - this.showPartialState = !hierarchical && showPartiallySelected + this.showPartialState = !this.hierarchical && showPartiallySelected this.searchMaps = new Map() - this.hierarchical = hierarchical + if ((this.simpleSelect || this.radioSelect) && singleSelectedNode) { // Remembers initial check on single select dropdowns this.currentChecked = singleSelectedNode._id diff --git a/src/tree-node/index.js b/src/tree-node/index.js index de719944..cb94a2db 100644 --- a/src/tree-node/index.js +++ b/src/tree-node/index.js @@ -69,7 +69,7 @@ class TreeNode extends PureComponent { onNodeToggle: PropTypes.func, onAction: PropTypes.func, onCheckboxChange: PropTypes.func, - mode: PropTypes.oneOf(['multiSelect', 'simpleSelect', 'radioSelect']), + mode: PropTypes.oneOf(['multiSelect', 'simpleSelect', 'radioSelect', 'hierarchical']), showPartiallySelected: PropTypes.bool, readOnly: PropTypes.bool, clientId: PropTypes.string, diff --git a/src/tree-node/node-label.js b/src/tree-node/node-label.js index 1a2912b7..a5bc4ef6 100644 --- a/src/tree-node/node-label.js +++ b/src/tree-node/node-label.js @@ -19,7 +19,7 @@ class NodeLabel extends PureComponent { partial: PropTypes.bool, disabled: PropTypes.bool, dataset: PropTypes.object, - mode: PropTypes.oneOf(['multiSelect', 'simpleSelect', 'radioSelect']), + mode: PropTypes.oneOf(['multiSelect', 'simpleSelect', 'radioSelect', 'hierarchical']), showPartiallySelected: PropTypes.bool, onCheckboxChange: PropTypes.func, readOnly: PropTypes.bool, diff --git a/src/tree/index.js b/src/tree/index.js index c697b996..d8b53c82 100644 --- a/src/tree/index.js +++ b/src/tree/index.js @@ -23,7 +23,7 @@ class Tree extends Component { onNodeToggle: PropTypes.func, onAction: PropTypes.func, onCheckboxChange: PropTypes.func, - mode: PropTypes.oneOf(['multiSelect', 'simpleSelect', 'radioSelect']), + mode: PropTypes.oneOf(['multiSelect', 'simpleSelect', 'radioSelect', 'hierarchical']), showPartiallySelected: PropTypes.bool, pageSize: PropTypes.number, readOnly: PropTypes.bool, diff --git a/src/trigger/index.js b/src/trigger/index.js index 60d47f58..ae69745a 100644 --- a/src/trigger/index.js +++ b/src/trigger/index.js @@ -14,7 +14,7 @@ class Trigger extends PureComponent { disabled: PropTypes.bool, readOnly: PropTypes.bool, showDropdown: PropTypes.bool, - mode: PropTypes.oneOf(['multiSelect', 'simpleSelect', 'radioSelect']), + mode: PropTypes.oneOf(['multiSelect', 'simpleSelect', 'radioSelect', 'hierarchical']), texts: PropTypes.object, } diff --git a/types/react-dropdown-tree-select.d.ts b/types/react-dropdown-tree-select.d.ts index 27a2f5d1..ebe58afd 100644 --- a/types/react-dropdown-tree-select.d.ts +++ b/types/react-dropdown-tree-select.d.ts @@ -1,165 +1,174 @@ // tslint:disable:interface-name -declare module "react-dropdown-tree-select" { - import * as React from "react"; +declare module 'react-dropdown-tree-select' { + import * as React from 'react' - export type TreeData = Object | TreeNodeProps[]; + export type TreeData = Object | TreeNodeProps[] - export interface DropdownTreeSelectProps { - data: TreeData; - /** Clear the input search if a node has been selected/unselected */ - clearSearchOnChange?: boolean; - /** Displays search results as a tree instead of flattened results */ - keepTreeOnSearch?: boolean; - /** Displays children of found nodes to allow searching for a parent node on - * then selecting any child node of the found node. Defaults to false - * NOTE this works only in combination with keepTreeOnSearch - */ - keepChildrenOnSearch?: boolean; - /** Keeps single selects open after selection. Defaults to `false` - * NOTE this works only in combination with simpleSelect or radioSelect - */ - keepOpenOnSelect?: boolean; - /** Texts to override output for */ - texts?: TextProps; - /** If set to true, shows the dropdown when rendered. - * This can be used to render the component with the dropdown open as its initial state - */ - showDropdown?: boolean; - /** If set to true, always shows the dropdown when rendered, and toggling dropdown will be disabled. - */ - showDropdownAlways?: boolean; - /** Additional classname for container. - * The container renders with a default classname of react-dropdown-tree-select - */ - className?: string; - /** Fires when a node change event occurs. Currently the following actions trigger a node change: - * Checkbox click which checks/unchecks the item - * Closing of pill (which unchecks the corresponding checkbox item) + export interface DropdownTreeSelectProps { + data: TreeData + /** Clear the input search if a node has been selected/unselected */ + clearSearchOnChange?: boolean + /** Displays search results as a tree instead of flattened results */ + keepTreeOnSearch?: boolean + /** Displays children of found nodes to allow searching for a parent node on + * then selecting any child node of the found node. Defaults to false + * NOTE this works only in combination with keepTreeOnSearch + */ + keepChildrenOnSearch?: boolean + /** Keeps single selects open after selection. Defaults to `false` + * NOTE this works only in combination with simpleSelect or radioSelect + */ + keepOpenOnSelect?: boolean + /** Texts to override output for */ + texts?: TextProps + /** If set to true, shows the dropdown when rendered. + * This can be used to render the component with the dropdown open as its initial state + */ + showDropdown?: boolean + /** If set to true, always shows the dropdown when rendered, and toggling dropdown will be disabled. + */ + showDropdownAlways?: boolean + /** Additional classname for container. + * The container renders with a default classname of react-dropdown-tree-select + */ + className?: string + /** Fires when a node change event occurs. Currently the following actions trigger a node change: + * Checkbox click which checks/unchecks the item + * Closing of pill (which unchecks the corresponding checkbox item) + * + * Calls the handler with the current node object and all selected nodes (if any) + */ + onChange?: (currentNode: TreeNode, selectedNodes: TreeNode[]) => void + /** Fired on click of the action */ + onAction?: (currentNode: TreeNode, currentAction: NodeAction) => void + /** Fires when a node is expanded or collapsed. + * Calls the handler with the current node object + */ + onNodeToggle?: (currentNode: TreeNode) => void + /** Fires when input box receives focus or the dropdown arrow is clicked. + * This is helpful for setting dirty or touched flags with forms + */ + onFocus?: () => void + /** 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 + */ + onBlur?: () => void + /** Defines how the dropdown is rendered / behaves * - * Calls the handler with the current node object and all selected nodes (if any) - */ - onChange?: (currentNode: TreeNode, selectedNodes: TreeNode[]) => void; - /** Fired on click of the action */ - onAction?: (currentNode: TreeNode, currentAction: NodeAction) => void; - /** Fires when a node is expanded or collapsed. - * Calls the handler with the current node object - */ - onNodeToggle?: (currentNode: TreeNode) => void; - /** Fires when input box receives focus or the dropdown arrow is clicked. - * This is helpful for setting dirty or touched flags with forms - */ - onFocus?: () => void; - /** 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 - */ - onBlur?: () => void; - /** Defines how the dropdown is rendered / behaves - * * - multiSelect - * A multi selectable dropdown which supports hierarchial data. - * + * A multi selectable dropdown which supports tree data with parent-child relationships. This is the default mode. + * + * - hierarchical + * A multi selectable dropdown which supports tree data **without** parent-child relationships. In this mode, selecting a node has no ripple effects on its descendants or ancestors. Subsequently, `showPartiallySelected` becomes a moot flag and has no effect as well. + * + * ⚠️ Note that `hierarchical=true` negates/overrides `showPartiallySelected`. + * * - simpleSelect - * Turns the dropdown into a simple, single select dropdown. If you pass tree data, only immediate children are picked, grandchildren nodes are ignored. Defaults to false. - * - * NOTE if multiple nodes in data are selected, checked or isDefaultValue, only the first visited node is selected - * + * Turns the dropdown into a simple, single select dropdown. If you pass tree data, only immediate children are picked, grandchildren nodes are ignored. + * + * ⚠️ If multiple nodes in data are selected - by setting either `checked` or `isDefaultValue`, only the first visited node stays selected. + * * - radioSelect - * Turns the dropdown into radio select dropdown. Similar to simpleSelect but keeps tree/children. Defaults to false. - * - * NOTE if multiple nodes in data are selected, checked or isDefaultValue, only the first visited node is selected */ - mode?: 'multiSelect' | 'simpleSelect' | 'radioSelect'; - /** 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 - */ - showPartiallySelected?: boolean; - /** disabled=true disables the dropdown completely. This is useful during form submit events */ - disabled?: boolean; - /** readOnly=true makes the dropdown read only, - * which means that the user can still interact with it but cannot change any of its values. - * This can be useful for display only forms - */ - readOnly?: boolean; - hierarchical?: boolean; - /** Specific id for container. The container renders with a default id of `rdtsN` where N is count of the current component rendered - * Use to ensure a own unique id when a simple counter is not sufficient, e.g in a partial server render (SSR) - */ - id?: string; - } + * Turns the dropdown into radio select dropdown. + + * Like `simpleSelect`, you can only select one value; but keeps the tree/children structure. + * + * ⚠️ If multiple nodes in data are selected - by setting either `checked` or `isDefaultValue`, only the first visited node stays selected. + * + * + * */ + mode?: 'multiSelect' | 'simpleSelect' | 'radioSelect' | 'hierarchical' + /** 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 + */ + showPartiallySelected?: boolean + /** disabled=true disables the dropdown completely. This is useful during form submit events */ + disabled?: boolean + /** readOnly=true makes the dropdown read only, + * which means that the user can still interact with it but cannot change any of its values. + * This can be useful for display only forms + */ + readOnly?: boolean + /** Specific id for container. The container renders with a default id of `rdtsN` where N is count of the current component rendered + * Use to ensure a own unique id when a simple counter is not sufficient, e.g in a partial server render (SSR) + */ + id?: string + } - export interface DropdownTreeSelectState { - showDropdown: boolean; - searchModeOn: boolean; - allNodesHidden: boolean; - tree: TreeNode[]; - tags: TreeNode[]; - } + export interface DropdownTreeSelectState { + showDropdown: boolean + searchModeOn: boolean + allNodesHidden: boolean + tree: TreeNode[] + tags: TreeNode[] + } - export default class DropdownTreeSelect extends React.Component { - node: HTMLDivElement; - searchInput: HTMLInputElement; - keepDropdownActive: boolean; - handleClick(): void; - } + export default class DropdownTreeSelect extends React.Component { + node: HTMLDivElement + searchInput: HTMLInputElement + keepDropdownActive: boolean + handleClick(): void + } - export interface TreeNode { - /** Checkbox label */ - label: string; - /** Checkbox value */ - value: string; - /** Initial state of checkbox. if true, checkbox is selected and corresponding pill is rendered. */ - checked?: boolean; - /** Selectable state of checkbox. if true, the checkbox is disabled and the node is not selectable. */ - disabled?: boolean; - /** If true, the node is expanded - * (children of children nodes are not expanded by default unless children nodes also have expanded: true). - */ - expanded?: boolean; - /** Additional css class for the node. This is helpful to style the nodes your way */ - className?: string; - /** Css class for the corresponding tag. Use this to add custom style the pill corresponding to the node. */ - tagClassName?: string; - /** An array of extra action on the node (such as displaying an info icon or any custom icons/elements) */ - actions?: NodeAction[]; - /** Allows data-* attributes to be set on the node and tag elements */ - dataset?: NodeDataSet; - /** Indicate if a node is a default value. - * When true, the dropdown will automatically select the node(s) when there is no other selected node. - * Can be used on more than one node. - */ - isDefaultValue?: boolean; - /** Any extra properties that you'd like to receive during `onChange` event */ - [property: string]: any; - } + export interface TreeNode { + /** Checkbox label */ + label: string + /** Checkbox value */ + value: string + /** Initial state of checkbox. if true, checkbox is selected and corresponding pill is rendered. */ + checked?: boolean + /** Selectable state of checkbox. if true, the checkbox is disabled and the node is not selectable. */ + disabled?: boolean + /** If true, the node is expanded + * (children of children nodes are not expanded by default unless children nodes also have expanded: true). + */ + expanded?: boolean + /** Additional css class for the node. This is helpful to style the nodes your way */ + className?: string + /** Css class for the corresponding tag. Use this to add custom style the pill corresponding to the node. */ + tagClassName?: string + /** An array of extra action on the node (such as displaying an info icon or any custom icons/elements) */ + actions?: NodeAction[] + /** Allows data-* attributes to be set on the node and tag elements */ + dataset?: NodeDataSet + /** Indicate if a node is a default value. + * When true, the dropdown will automatically select the node(s) when there is no other selected node. + * Can be used on more than one node. + */ + isDefaultValue?: boolean + /** Any extra properties that you'd like to receive during `onChange` event */ + [property: string]: any + } - export interface TreeNodeProps extends TreeNode { - /** Array of child objects */ - children?: TreeNode[]; - } + export interface TreeNodeProps extends TreeNode { + /** Array of child objects */ + children?: TreeNode[] + } - export interface TextProps { - /** The text to display as placeholder on the search box. Defaults to Choose... */ - placeholder?: string; - /** The text to display when the search does not find results in the content list. Defaults to No matches found */ - noMatches?: string; - /** Adds `aria-labelledby` to search input when input starts with `#`, adds `aria-label` to search input when label has value (not containing '#') */ - label?: string; - /** The text to display for `aria-label` on tag delete buttons which is combined with `aria-labelledby` pointing to the node label. Defaults to `Remove */ - labelRemove?: string; - } + export interface TextProps { + /** The text to display as placeholder on the search box. Defaults to Choose... */ + placeholder?: string + /** The text to display when the search does not find results in the content list. Defaults to No matches found */ + noMatches?: string + /** Adds `aria-labelledby` to search input when input starts with `#`, adds `aria-label` to search input when label has value (not containing '#') */ + label?: string + /** The text to display for `aria-label` on tag delete buttons which is combined with `aria-labelledby` pointing to the node label. Defaults to `Remove */ + labelRemove?: string + } - export interface NodeAction { - /** CSS class for the node. e.g. `fa fa-info` */ - className: string; - /** HTML tooltip text */ - title?: string; - /** Any text to be displayed. This is helpful to pass ligatures if you're using ligature fonts */ - text?: string; - /** Any extra properties that you'd like to receive during `onChange` event */ - [property: string]: any; - } + export interface NodeAction { + /** CSS class for the node. e.g. `fa fa-info` */ + className: string + /** HTML tooltip text */ + title?: string + /** Any text to be displayed. This is helpful to pass ligatures if you're using ligature fonts */ + text?: string + /** Any extra properties that you'd like to receive during `onChange` event */ + [property: string]: any + } - export interface NodeDataSet { - [property: string]: any; - } -} \ No newline at end of file + export interface NodeDataSet { + [property: string]: any + } +}