Code Monkey home page Code Monkey logo

react-accessible-treeview's Introduction

react-accessible-treeview Build Status Greenkeeper badge npm version License: MIT

A react component that implements the treeview pattern as described by the WAI-ARIA Authoring Practices.

Features

  • Single and multiple selection.
  • Disabled nodes.
  • Extensive key bindings.
  • Highly customizable through the use of the render prop and prop getter patterns.
  • WAI-ARIA compliant.

Documentation and Demo

Prop Types

Prop name Type Default value Description
data array[node] required Tree data
nodeRenderer func required Render prop for the node (see below for more details)
onSelect func noop Function called when a node changes its selected state
onNodeSelect func noop Function called when a node was manually selected/deselected
onExpand func noop Function called when a node changes its expanded state
className string "" className to add to the outermost dom element, al ul with role = "tree"
multiSelect bool false Allows multiple nodes to be selected
propagateSelect bool false If set to true, selecting a node will also select its descendants
propagateSelectUpwards bool false If set to true, selecting a node will update the state of its parent (e.g. a parent node in a checkbox will be automatically selected if all of its children are selected
propagateCollapse bool false If set to true, collapsing a node will also collapse its descendants
expandOnKeyboardSelect bool false Selecting a node with a keyboard (using Space or Enter) will also toggle its expanded state
togglableSelect bool false Whether the selected state is togglable
defaultSelectedIds array [] Array with the ids of the default selected nodes
defaultExpandedIds array [] Array with the ids of the default expanded nodes
defaultDisabledIds array [] Array with the ids of the default disabled nodes
selectedIds array [] (Controlled) Array with the ids that should be selected
expandedIds array [] (Controlled) Array with the ids of branch node that should be expanded
clickAction enum SELECT Action to perform on click. One of: EXCLUSIVE_SELECT, FOCUS, SELECT
onBlur func noop Custom onBlur event that is triggered when focusing out of the component as a whole (moving focus between the nodes won't trigger it).



data

An array of nodes. Nodes are objects with the following structure:

Property Type Default Description
id number or string required A nonnegative integer or string that uniquely identifies the node
name string required Used to match on key press
children array[id] required An array with the ids of the children nodes.
parent id required The parent of the node. null for the root node
isBranch boolean optional Used to indicated whether a node is branch to be able load async data onExpand, default is false
metadata object optional Used to add metadata into node object. We do not currently support metadata that is a nested object

The item with parent:null of the array represents the root node and won't be displayed.

Example:

const data = [
  { name: "", children: [1, 4, 9, 10, 11], id: 0, parent: null },
  { name: "src", children: [2, 3], id: 1, parent: 0 },
  { name: "index.js", id: 2, parent: 1 },
  { name: "styles.css", id: 3, parent: 1 },
  { name: "node_modules", children: [5, 7], id: 4, parent: 0 },
  { name: "react-accessible-treeview", children: [6], id: 5, parent: 4 },
  { name: "bundle.js", id: 6, parent: 5 },
  { name: "react", children: [8], id: 7, parent: 4 },
  { name: "bundle.js", id: 8, parent: 7 },
  { name: ".npmignore", id: 9, parent: 0 },
  { name: "package.json", id: 10, parent: 0 },
  { name: "webpack.config.js", id: 11, parent: 0 },
];

The array can also be generated from a nested object using the flattenTree helper (see the examples below). flattenTree preserves metadata.

Data supports non-sequential ids provided by user.



nodeRenderer

  • Arguments: An object containing the following properties:
Property Type Description
element object The object that represents the rendered node
getNodeProps function A function which gives back the props to pass to the node
isBranch bool Whether the rendered node is a branch node
isSelected bool Whether the rendered node is selected
isHalfSelected bool or undefined If the node is a branch node, whether it is half-selected, else undefined
isExpanded bool or undefined If the node is a branch node, whether it is expanded, else undefined
isDisabled bool Whether the rendered node is disabled
level number A positive integer that corresponds to the aria-level attribute
setsize number A positive integer that corresponds to the aria-setsize attribute
posinset number A positive integer that corresponds to the aria-posinset attribute
handleSelect function Function to assign to the onClick event handler of the element(s) that will toggle the selected state
handleExpand function Function to assign to the onClick event handler of the element(s) that will toggle the expanded state
dispatch function Function to dispatch actions
treeState object state of the treeview



onSelect

  • Arguments: onSelect({element, isBranch, isExpanded, isSelected, isHalfSelected, isDisabled, treeState }) Note: the function uses the state after the selection.

onNodeSelect

  • Arguments: onNodeSelect({element, isBranch, isSelected, treeState }) Note: the function uses the state right after the selection before propagation.

onExpand

  • Arguments: onExpand({element, isExpanded, isSelected, isHalfSelected, isDisabled, treeState}) Note: the function uses the state after the expansion.

onLoadData

  • Arguments: onLoadData({element, isExpanded, isSelected, isHalfSelected, isDisabled, treeState}) Note: the function uses the state after inital data is loaded and on expansion.

Keyboard Navigation

Follows the same conventions described in https://www.w3.org/TR/wai-aria-practices/examples/treeview/treeview-1/treeview-1b.html and https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-22.

Key Function
Enter or Space Updates the selected node to the current node and triggers onSelect
Down Arrow
  • Moves focus to the next node that is tabbable without opening or closing a node.
  • If focus is on the last node, does nothing.
Up arrow
  • Moves focus to the previous node that is tabbable without opening or closing a node.
  • If focus is on the first node, does nothing.
Right Arrow
  • When focus is on a closed node, opens the node; focus does not move.
  • When focus is on an end node, does nothing.
  • When focus is on a open node, moves focus to the first child node.
Left Arrow
  • When focus is on an open node, closes the node.
  • When focus is on a child node that is also either an end node or a closed node, moves focus to its parent node.
  • When focus is on a root node that is also either an end node or a closed node, does nothing.
Home Moves focus to first node without opening or closing a node.
End Moves focus to the last node that can be focused without expanding any nodes that are closed.
a-z, A-Z
  • Focus moves to the next node with a name that starts with the typed character.
  • Search wraps to first node if a matching name is not found among the nodes that follow the focused node.
  • Search ignores nodes that are descendants of closed nodes.
* (asterisk)
  • Expands all closed sibling nodes that are at the same level as the focused node.
  • Focus does not move.
Shift + Down Arrow Moves focus to and toggles the selection state of the next node.
Shift + Up Arrow Moves focus to and toggles the selection state of the previous node.
Ctrl + A Selects all nodes in the tree. If all nodes are selected, unselects all nodes.



Mouse Navigation

Key Function
Click Toggles parent nodes and also performs one of clickActions = SELECT, EXCLUSIVE_SELECT, FOCUS
Ctrl+Click If multiselect is set to true, selects the node without dropping the current selected ones. If false, it selects the clicked node. Doesn't toggle parents.
Shift+Click If multiselect is set to true, selects from the node without dropping the current selected ones. If false, it focus the clicked node. Doesn't toggle parents.



Click actions

Variant Function
SELECT Selects the clicked node (default).
EXCLUSIVE_SELECT Selects the clicked node and deselects the rest.
FOCUS Focuses the clicked node



treeState

The internal state of the component.

Property Type Default Description
selectedIds Set new Set(defaultSelectedIds) Set of the ids of the selected nodes
controlledIds Set new Set(controlledSelectedIds) Set of the ids of the nodes selected programmatically
tabbableId number data[0].children[0] Id of the node with tabindex = 0
isFocused bool false Whether the tree has focus
expandedIds Set new Set(defaultExpandedIds) Set of the ids of the expanded nodes
halfSelectedIds Set new Set() Set of the ids of the selected nodes
lastUserSelect number data[0].children[0] Last selection made directly by the user
lastInteractedWith number or null null Last node interacted with
lastManuallyToggled number or null null Last node that was manually selected/deselected
disabledIds Set new Set(defaultDisabledIds) Set of the ids of the selected nodes

react-accessible-treeview's People

Contributors

blittle avatar bradestey avatar dartamonov-vertex avatar dependabot[bot] avatar dgreene1 avatar greenkeeper[bot] avatar hocdoc avatar ke1sy avatar kpustakhod avatar lissitz avatar megbailey avatar mehmetyararvx avatar mellis481 avatar philipp-spiess avatar sandro768 avatar sappharad avatar spanb4 avatar yhy-1 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

react-accessible-treeview's Issues

Expose dispatch

Hi @lissitz, great contribution to the open source community.

I wonder, are you willing to accept PRs to this repo? I want to expose dispatch function to onExpand and onSelect in addition to onBlur for which you have already exposed dispatch.

The purpose is to be able to code accordion-like functionality, that collapses previously expanded branches when clicking on a new collapsed branch.

New release?

Is it possible to publish a new release to NPM?
I would like to use the treeview with react-18.

flattenTree function doesn't persist isBranch property

Describe the bug
I'm trying to use isBranch property inside flattenTree function but it's doesn't persist

To Reproduce
Steps to reproduce the behavior:
Try to execute a function with an object that has isBranch property

Screen readers does not announce certain states correctly for checkbox tree views

aria-selected and aria-multiselectable are used for all instances of the TreeView. This results in a reasonable screen reader UX for TreeView instances which do not involve checkboxes when nodes can only be selected. For TreeView instances which do include checkboxes, however, it creates a user experience which could be better.

Specifically, because of the aria-selected usage:

  1. a checkbox node with indeterminate state (half selected) reads out "not selected".
  2. the selected state of a selected node is not read out when you navigate to it (it only announces nodes that are not selected for some reason).

If aria-checked is used instead, the screen reader will read "checked" or "not checked" instead of "selected" or "not selected" which IMO more accurate describes the action executed. More importantly, the use of aria-checked successfully:

  1. handles the indeterminate state (using aria-checked="mixed"), reading "<node name and level> checkbox half checked".
  2. announces the selected state of a selected node when you navigate to it. (eg. "avocados checked one of five level two").

The section of this W3C article on making a treeview accessible which starts "The selection state of each selectable node is indicated with either aria-selected or aria-checked" provides describes that aria-selected or aria-checked can be used in tree views.

A way to handle this which seems advisable to me is to provide a prop for the TreeView component to allow consumers to indicate if the TreeView will be using checkboxes or not (perhaps nodeAction?: 'check' | 'select'). If a consumer indicates their TreeView instances uses checkboxes, the resulting HTML will use the aria-checked attributes; else, it will use the existing aria-selected and aria-multiselectable attributes. These changes have been submitted in PR #33.

An in-range update of eslint-config-react-app is breaking the build 🚨

The devDependency eslint-config-react-app was updated from 5.1.0 to 5.2.0.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

eslint-config-react-app is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • ❌ continuous-integration/travis-ci/push: The Travis CI build failed (Details).
  • ❌ Travis CI - Branch: The build failed.

Commits

The new version differs by 41 commits.

  • d7c6842 Publish
  • a7b8732 Prepare 3.3.1 release
  • 4da41b4 docs: Add troubleshooting documentation on ENOSPC (#8380)
  • 03018d7 Update docs according to lint-staged v10 (#8394)
  • 6ee4e91 Add helpful message to the footer (#6548)
  • ed162a3 Add "Disallow:" to robots.txt (#8255)
  • e530598 Fix sass importLoaders (#8281)
  • dd0df73 Remove outdated docs regarding vscode eslint extension and type… (#8307)
  • ca9c61e Update setting-up-your-editor.md (#8247)
  • cafd602 Update custom template docs with instructions for testing custom template locally (#8092)
  • 720d90b Sync dependencies babel config (#8120)
  • ddcb7d5 Add titleProp to SVGR ReactComponent type definition (#8099)
  • b855da5 Remove outdated babel plugins (#8353)
  • 4bf14fa Downgrade open from 7.0.0 to 6.4.0 (#8364)
  • dada035 Remove React.FC from Typescript template (#8177)

There are 41 commits in total.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Cannot Keyboard focus into TreeView Component

Hi I was wondering if someone can help me please?

In the w3 APG TreeView Pattern it says:

When a single-select tree receives focus:
If none of the nodes are selected before the tree receives focus, focus is set on the first node.
If a node is selected before the tree receives focus, focus is set on the selected node.

If I tab to the first tree item of the tree view I can see the tabindex attribute is set to 0. This is good.
If I tab again and it takes me away from the tree view component to the next interactive element. This is expected.

What I noticed is that when navigating away, the component sets the first tree item to tabindex="-1" which means if I press shift and tab I will no longer be able to navigate to the treeview first item (or any item).

The first node should be selected when I tab back to the component but this isn't the case.

The same thing happens when you select a node and tab away and tab back more than once.

Codesandbox Link

An in-range update of @testing-library/dom is breaking the build 🚨

The devDependency @testing-library/dom was updated from 6.11.0 to 6.11.1.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

@testing-library/dom is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • ❌ continuous-integration/travis-ci/push: The Travis CI build failed (Details).
  • ❌ Travis CI - Branch: The build failed.

Release Notes for v6.11.1

6.11.1 (2020-01-28)

Bug Fixes

Commits

The new version differs by 5 commits.

  • 19afb4c chore: fix all contributors config
  • 984c270 docs: add Westbrook as a contributor (#432)
  • 905f4b3 fix: ensure 'process' is available before using (#431)
  • a2c3472 docs: add JonathanStoye as a contributor (#426)
  • 3f30564 chore(event): cleanup commented out code (#425)

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

updating selectedIds fires onNodeSelect

Describe the bug
docs of onNodeSelect says onNodeSelect called when a node was manually selected/deselected but
updating selectedIds array fires onNodeSelect

I've created a sandbox as an example. button click fires react-router-dom's useNavigate hook, after the navigation selectedIds will be updated (as its state depends on query params) therefor onNodeSelect will be fired even though I haven't selected/deselected anything manually

To Reproduce
Steps to reproduce the behavior:

  1. Visit sandbox
  2. open the console to observe
  3. Enter https://pmpsq8.csb.app/folders/2 in URL of the sandbox
  4. Click(expand) Node modules
  5. Click button which is at the top of the page
  6. you will see that after the navigation onNodeSelect gets fired

Disable all children of disabled node

As a maintainer, I'm logging this "feature request" because it's something my team plans on executing in the foreseeable future, but I want there to be visibility to the consumers of this library. Please provide feedback if you have any regarding this issue.

Core Principles
There are two principles for a tree component that must be upheld with regard to parent nodes to ensure good usability and accessibility. A parent node should:

  1. always correctly inform the checked status of its children.
  2. provide a convenient means for selecting/unselected children.

Problem Statement
Being able to disable a parent node and have its children not also be disabled automatically creates situation that doesn't make any sense and creates usability and accessibility issues

Details
Currently, only nodes passed in defaultDisabledIds will be disabled. This means that if a parent is disabled, its children will not automatically be disabled. eg:

image

In the example above, it doesn't make any logical sense to say "'Tea' should not be selectable, but all of its children should be."

More importantly, being able to select children of a disabled parent creates serious issues due to propagating selection to/through the disabled parent node. Currently, no selection (not even half-selection) is propagated to/through a disabled parent node when a child is selected. This causes usability and accessibility issues when the parent (or grandparent) is collapsed because a user can't tell what children are selected. If you argue "well then just propagate selection to/through the disabled parent node", that would create a situation where you could effectively select a disabled node by selecting all of its children which would be a huge issue.

For these reasons, children of a disabled parent node should be automatically disabled.

ScrollTo functionality

Note: For large trees - would be nice to have ability to scrollTo a specific node - see behavior from antD Tree -

https://ant.design/components/tree#tree-methods

Describe the solution you'd like
A method on the tree instance that allows developers to programmatically scroll to a specific node and align accordingly.

What value does this proposed solution bring to users?
When auto-selecting - like after a search - allows the tree to focus on the selected node
Displaying a large tree with selections - allows developers to focus on the actual selected value vs user having to manually find previously selected nodes.

Describe alternatives you've considered
Custom event to scroll using document.querySelector or scrollIntoView()

onSelect & onNodeSelect's element is not the element that was selected

Describe the bug
On Event, onSelect and onNodeSelect does not return the element that was selected, but returns the last selected parent element? Happy to make any changes but I'll likely need some direction if possible.

Code That Causes The Issue
Here is my implementation. I still unsure what would need to change upstream but I'll update the ticket as I learn more.

<TreeView
            className={"c-list--ul"}
            data={flattenedTreeItems}
            expandedIds={expandedIds}
            onExpand={allowMultipleExpanded ? null : oneNodeExpansion}
            onNodeSelect={ (event ) => { 
                console.log(event) // Here! event.element is always a parent element and not the element i would expect.
                //const { url, openNewTab } = event.element.metadata
                //if ( openNewTab ) window.open(url);
                //else window.location = url;
            }}
            nodeRenderer={({
                element,  
                isBranch,
                isExpanded,
                getNodeProps,
                level,
                handleExpand,
                treeState,
            }) => {

                const { name:text, metadata } = element
                const { icon, url, ...otherItemProps } = metadata

                    return (
                        <div    
                            {...getNodeProps({ onClick: handleExpand })}
                            className={"c-list__item"}
                            tabIndex={-1}
                        >
                            <Button
                                {...otherItemProps} 
                                {...other}
                                className={clsx(
                                    {
                                        ['c-btn--has-icon']: icon,
                                    },
                                )}
                             
                                href={url}
                                layout="block"
                                text={text}
                            ></Button>
                            {( isBranch && isExpanded ) && (
                                <Icon name='circle-minus' size='small' theme={active ? 'solid' : 'regular'} color={ active ? 'dark-blue': 'light-blue'} />
                            )}
                            {( isBranch && !isExpanded ) && (
                                <Icon name='plus-circle' size='small' theme={active ? 'solid' : 'regular'} color={ active ? 'dark-blue': 'light-blue'} />
                            )}
                        </div>
                    );
                
            }}
        />

To Reproduce
Steps to reproduce the behavior:

  1. Populate tree with buttons
  2. Tab past the first node
  3. Select Enter
  4. console.log of event shows that element is always the first element in the tree

Expected behavior
onSelect({element, isBranch, isExpanded, isSelected, isHalfSelected, isDisabled, treeState })
onNodeSelect({element, isBranch, isSelected, treeState })
I would expect element to return the attributes of the node that was selected/unselected

Desktop (please complete the following information):

  • Browser: Chrome
  • Version 117.0.5938.132 (Official Build) (x86_64)

Additional context
I'm utilizing this package to create an implementation for a site's side navigation where my node elements are buttons. In order for the buttons to work with keyboard controls, I need access to the element that was in focus on Enter. I have my implemenation published to our dev storybook, and its the sticky sidebar on the right. https://staging-camino.sandiego.edu/storybook/?path=/story/templates-detail--text

An in-range update of eslint-plugin-react is breaking the build 🚨

The devDependency eslint-plugin-react was updated from 7.18.0 to 7.18.1.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

eslint-plugin-react is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • ❌ continuous-integration/travis-ci/push: The Travis CI build failed (Details).
  • ❌ Travis CI - Branch: The build failed.

Commits

The new version differs by 9 commits.

  • 000d6b8 Update CHANGELOG and bump version
  • ffdf69a [Fix] jsx-indent: Does not check indents for JSXText
  • 182b045 [Docs] use markdown-magic to automatically sort all rules alphabetically
  • 45b9d32 [Docs] jsx-props-no-spreading: fix typo to use correct rule
  • f9aee94 [Fix] jsx-props-no-spreading: add support for namespaced jsx components
  • e69b113 [Fix] jsx-no-target-blank: allow rel to be an expression
  • fc6e406 [meta] fix changelog date
  • 50df78f [Fix] sort-comp: | isn’t a valid regex flag; u and s are
  • a7f6a8b [Tests] on node v13

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Selected Tree Item on Focus - no selected item behaviour

I have a concern and I would love to know if this is your expected behaviour (see video attached).

In the APG Tree View Design Pattern notes it says:

For a vertically oriented tree:

When a single-select tree receives focus:
If none of the nodes are selected before the tree receives focus, focus is set on the first node.
If a node is selected before the tree receives focus, focus is set on the selected node.

When a tree item receives focus using the keyboard it has a tabindex value of 0. Then every other field is set to tabindex -1. This is using roving tabindex I guess.

If I tab away from the tree view, the currently focused item will have a tabindex of 0. When I tab back into the tree view component that previous item will regain keyboard focus because it still has a tabindex of 0.

But should it? That item is not in a "selected" state. See the video, I did not select the item but it regained focus. Shouldn't item 0 (from default state) regain focus? or is this a behaviour you desire?

example.mov

onNodeSelect is not invoked when selection is from selected -> halfSelected

Describe the bug
The onNodeSelect callback should be invoked in the scenario where an already selected node is selected to go from a selected -> halfSelected state.

Code That Causes The Issue
See failing unit test in PR #118

Test in TestViewOnNodeSelect.test.tsx - "onNodeSelect should be called when node is deselected to a half-selected state"

To Reproduce
See failing unit test in PR

Expected behavior
See failing unit test in PR

Additional context
The use case here is when the selectedIds are controlled with custom logic determining the propagation, therefore the propagate props are disabled. In this scenario the lack of callback to onNodeSelect prevents the consumer from being notified of the interaction with the node.

baseClassNames controlled via props

It would be nice if the classNames set by baseClassNames could be controlled via props. So you could use e.g. the Bootstrap classes without writing custom CSS.

Don't call onExpand for initial expandedIds

Describe the bug
There are some issues with using controlled state for expanded IDs. Consider this example where expanded state is controlled in the parent component (a practical example would be where state is persisted in local/session storage).

Code That Causes The Issue

const data = flattenTree({
  name: '',
  children: names,
})
const parentIds = useMemo(
  () =>
    data.reduce<NodeId[]>(
      (acc, node) => (node.children.length ? acc.concat(node.id) : acc),
      []
    ),
  [data]
)
const [expandedIds, setExpandedIds] = useSessionStorage(storageKey, parentIds)

<TreeView
  data={data}
  onExpand={({ element: { id }, isExpanded }) => {
    setExpandedIds((ids) =>
      produce(ids, (draft) => {
        if (!isExpanded) {
          remove(draft, (nodeId) => nodeId === id)
        } else {
          draft.push(id)
        }
      })
    )
  }}
  expandedIds={expandedIds}
  onNodeSelect={({ element }) => {
    // ...handle
  }}
  nodeRenderer={(props) => (
    <NodeRenderer {...props} itemProps={nodePropsMap[props.element.name]} />
  )}
  clickAction="EXCLUSIVE_SELECT"
  aria-label="Navigation"
  expandOnKeyboardSelect
/>

The values passed into expandedIds cause onExpand to be called on the initial render. This also causes the list items to be focused on the initial page load which is an accessibility issue.

To Reproduce
Use parent state to control the expanded IDs.

Expected behavior
Like with form inputs, I'd expect that controlled state to accept an initial value, and subsequent changes to the state from user input to call a callback, such as onExpand.

Desktop (please complete the following information):

  • OS: macOS
  • Browser: chrome
  • Version: 114.0.5735.133 (Official Build) (arm64)

Pass additional data to nodes

It would be great to be able to pass an object of properties to the node on its definition, in order to use it in the renderer. Something like this:

data: [
  name: 'aNode',
  data: {
    ...someProps
  }
}

Currently when I use flattenTree, its not possible to pass a different id to get the props from another place and the name is not a good aproach due to there it could be many nodes with the same name.

Thank you

Expanding/collapsing child deselects parent when propagateSelect is false

Summary
Expanding/collapsing child deselects parent when propagateSelect is false.

Reproduction environment
https://codesandbox.io/s/hungry-keller-7dg0cb

Steps to recreate in example

  1. Expand the "Drinks" node.
  2. Select "Drinks" node.
  3. Expand "Tea" node.

Expected outcome:
"Drinks" selected status does not change.

Actual outcome
"Drinks" is de-selected.

Additional notes
If propagateSelectUpwards is set to false this issue does not occur.

Do not lock issues

I do not like the fact that tickets are locked immediately. This is not only disrespectful, because you no longer allow feedback, but you also prevent a productive exchange within the community.

In any case, it makes the impression that feedback is unwanted and I think that should not be the case. I wanted to start a discussion on a topic that would also benefit the reach of this library and my ticket was locked after the first reply. I would have more to say about it and besides, the same question could come up to other developers.

I would love to use this library and would also be willing to contribute.

Thank you and keep up the good work.

No Typescript types

Hi @lissitz. I noticed that there are no typescript types for this library in DefinitelyTyped and the library itself does not export any type information.

I'm one of those people who loves converting JS libraries to TypeScript.

Would you be interested in either:

  1. A PR that provides types from within the library but keeps the library?
  2. Or, a PR that converts the library to Typescript?

Option 2 is significantly easier for me and is more accurate from consumers since it doesn't allow the type definitions to drift away from the runtime types since they would be derived from the same source.

Remove ability to disable selection propagation

As a maintainer, I'm logging this "feature request" because it's something my team plans on executing in the foreseeable future, but I want there to be visibility to the consumers of this library. Please provide feedback if you have any regarding this issue.

Problem Statement
Disabling selection propagation upward and/or downward creates serious usability and accessibility issues.

Core Principles
There are two principles for a tree component that must be upheld with regard to parent nodes to ensure good usability and accessibility. A parent node should:

  1. always correctly inform the checked status of its children.
  2. provide a convenient means for selecting/unselected children.

Allowing selection propagation to be disabled in either direction goes against both of those principles.

Details
The usability and a11y issues are most easily seen when selection propagation is disabled in either direction and a parent node is collapsed. In this first example, if "Fruits" was collapsed, a user would believe no children were selected even though one or even all could be.

image

In this second example, if "Fruits" was collapsed, a user would believe all children would be selected even though none are.

image

From an accessibility standpoint, looking again at the first example, if a screen reader user focuses the "Fruits" checkbox, the screen reader would announced "Fruits not checked collapsed 1 of 3 level 1". This would have the user think "Since 'Fruits' is collapsed, that must mean it has children, but since it was announced as 'not checked', neither 'Fruits' nor any children are checked."

For these reasons, the ability to disable selection propagation should be removed.

Remove onBlur from examples

In some of the examples we have

onBlur={({ treeState, dispatch }) => {
  dispatch({
    type: "DESELECT",
    id: Array.from(treeState.selectedIds)[0],
  });
}}

This deselects the selected node when we focus away from the component. It also breaks the focus tabindex issue #48.
I suggest we remove this from all examples/docs.

The `flattenTree` utility is mutative

Not expecting a fix, just a heads up for folks who encounter the same issue.

You may see something like:

TypeError: Cannot create property 'id' on number '2'

🚨 Be wary of shallow cloning, objects are copied by reference:

flattenTree({ name: '', children: [...data] }) // `children` arrays (and other objects) are still mutated!

βœ… Consider something like lodash.cloneDeep:

flattenTree({ name: '', children: cloneDeep(data) }) // original `data` remains intact

Updating controlled selectedIds steals focus and gives it to the tree

Describe the bug
When trying to use controlled selectedIds to check checkboxes in the tree, every time you change the value of selectedIds the tree suddenly steals keyboard focus from whatever control on the page had it last.

Code That Causes The Issue
I believe this was introduced in 1f9b062 after dispatching controlledSelectMany for the parents if propagateSelect is enabled it will use the regular changeSelectMany, passing in id for lastInteractedWith, which causes focus to be stolen by the tree.

To Reproduce
You can see this in your "Checkbox with controlled selectedIds" example included with the project.

  1. Type a node number into the textbox and press enter instead of clicking the submit button.
  2. Focus leaves the text field and immediately jumps into the tree.

Expected behavior
The most obvious use for a controlled set of Ids would be if you were applying changes as the result of an action performed elsewhere on the page. I would not expect performing an action elsewhere to move my keyboard focus into the grid.

Desktop (please complete the following information):

  • OS: N/A Desktop PC
  • Browser Firefox, Chrome
  • Version It doesn't matter

Additional context
I was looking to display some data in a tree and I found this control. It seems to fit that need perfectly, but the actual behavior when setting node selections currently makes it an awkward choice.

In the case where I want to use this I have a regular list of items at the top of the page and a tree with properties for those items on top. Switching the item in the list should update the tree to show the properties for the item selected. But if I use the arrow keys to try and navigate the list of items, every time I arrow down on the list and update the tree with the appropriate selections of checkboxes, the tree immediately steals focus making just general browsing of data a challenge. Additionally I have "Check" and "Uncheck all" buttons similar to the uncheck button on the sample page, clicking those buttons also steals focus and sends it to the tree. When someone is using the mouse this isn't a huge problem but it's annoying when you're trying to use the keyboard.

1f9b062 appears to be fixing some kind of problem with focus being lost, but could we fix this new problem just by setting lastInteractedWith to null if the grid does not have focus so that it does not steal focus? In my case, just changing lastInteractedWith to null on line 276 of index.tsx completely fixes the stolen focus problem, but I suspect it's breaking whatever that previous commit was trying to solve.

trunk currently broken

When I pull down the master branch and try to run npm i, I get the following error:

image

Not sure why this codebase does not have a package-lock.json checked in...

Question: Select any node through function call

Is there a way to select any node through a function call where the parent nodes will automatically expand, and that node will be selected?

Something look like this: treeRef.dispatchAction({type: 'SELECT', id: 15});?

Bug with node deletion when controlling node expansion

Describe the bug
This bug happens when the user wants to have all branches expanded and chooses to have a dynamic node adding/deleting system.
By adding two or more nested branches and then deleting them an error will occur : "Node with id=x doesn't exist in the tree"

As an example i have created a sandbox. Clicking the button "add node" will add a node as a child to a node that has been selected, clicking the button "delete" will delete node that has been selected (and all it's children).

To Reproduce
Steps to reproduce the behavior:

  1. Go to this sandbox
  2. Click on 'node 1'
  3. Click on 'add node' button
  4. Repeat until you have 3 levels of nested nodes (see screenshot)
  5. Start deleting nodes from the lowest level up ('node 3', 'node 2', 'node 1')
  6. See error

Expected behavior
It is expected that you get an error "Node with id=x doesn't exist in the tree"

Screenshot
This is the look of the tree at step number 4:

image

Desktop (please complete the following information):

  • OS: [Windows 10]
  • Browser [Firefox]
  • Version [114.0.2]

Option to turn off keyboard navigation

I want to use this component in combination with a multiline textfield in a sub node.
But in the textfield when I press the enter key or space key, the onSelect event of the tree triggers this key press event.
Is there a way to turn off keyboard navigation completely in the tree?

An in-range update of storybook is breaking the build 🚨

There have been updates to the storybook monorepo:

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

This monorepo update includes releases of one or more dependencies which all belong to the storybook group definition.

storybook is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • ❌ continuous-integration/travis-ci/push: The Travis CI build failed (Details).
  • ❌ Travis CI - Branch: The build failed.

Commits

The new version differs by 2566 commits.

  • ee7b959 v5.3.0
  • 8c50a7a Publish CLI aliases
  • 866603f Update peer dependencies to 5.3.0
  • 4dfaf58 5.3.0 changelog
  • 4e9d8a1 5.3.0-rc.14 next.json version file
  • bc8e933 Update version.ts to 5.3.0-rc.14
  • aaef883 v5.3.0-rc.14
  • 29f2055 Update peer dependencies to 5.3.0-rc.14
  • e771cde 5.3.0-rc.14 changelog
  • 6064b80 Merge master into next for 5.3.0 release (#9388)
  • b09bcbb Disable chromatic on error stories
  • 8fd9aa0 Disable prop table tests in chromatic
  • b3f0886 Merge branch 'master' into shilman/test-53-release-from-next
  • 67609c0 Update CSF docs to clarify how naming works (#9040)
  • 5182155 Fix minor spelling mistake (#9284)

There are 250 commits in total.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Additional data in flattenTree & Node

it is posible to add additional data ? like i want to add object category_id

const data = [
{ category_id: null, name: "", children: [1, 4, 9, 10, 11], id: 0, parent: null },
{ category_id: 1, name: "src", children: [2, 3], id: 1, parent: 0 },
{ category_id: 2, name: "index.js", id: 2, parent: 1 },
{ category_id: 3, name: "styles.css", id: 3, parent: 1 },
{ category_id: 4, name: "node_modules", children: [5, 7], id: 4, parent: 0 },
{ category_id: 5, name: "react-accessible-treeview", children: [6], id: 5, parent: 4 },
{ category_id: 6, name: "bundle.js", id: 6, parent: 5 },
{ category_id: 7, name: "react", children: [8], id: 7, parent: 4 },
{ category_id: 8, name: "bundle.js", id: 8, parent: 7 },
{ category_id: 9, name: ".npmignore", id: 9, parent: 0 },
{ category_id: 10, name: "package.json", id: 10, parent: 0 },
{ category_id: 11, name: "webpack.config.js", id: 11, parent: 0 },
];

Half-selected state only set for parent

The indeterminate (half-selected) state is only being set for the parent. eg:

image

It should be set for all ancestors (assuming propagateSelectUpwards is true).

The main issue this causes is that you can't see if any children are selected when nodes are collapsed. eg:

image

Allow us to override the list component

First of all, thank you for the work!

I would like to implement this component in Mantine, for which I would need the ability to override the list component so that I can use a custom component for <ul>.

I wonder if this would be feasible?

An in-range update of rollup is breaking the build 🚨

The devDependency rollup was updated from 1.29.0 to 1.29.1.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

rollup is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • ❌ continuous-integration/travis-ci/push: The Travis CI build failed (Details).
  • ❌ Travis CI - Branch: The build failed.

Release Notes for v1.29.1

2020-01-21

Bug Fixes

  • Avoid crashes for circular reexports when named exports cannot be found (#3350)

Pull Requests

Commits

The new version differs by 5 commits.

  • 21a1775 1.29.1
  • a49d951 Update changelog
  • e82410d Properly handle circular reexports (#3350)
  • 56cbbdc fix tpyo (#3335)
  • 63644db Remove : from test file names for Windows, update dependencies (#3342)

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

An in-range update of lint-staged is breaking the build 🚨

The devDependency lint-staged was updated from 9.2.2 to 9.2.3.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

lint-staged is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • βœ… continuous-integration/travis-ci/push: The Travis CI build passed (Details).
  • ❌ netlify/react-accessible-treeview/deploy-preview: Deploy preview failed. (Details).
  • ❌ Mixed content - react-accessible-treeview: Please check the logs.
  • ❌ Redirect rules - react-accessible-treeview: Please check the logs.
  • ❌ Header rules - react-accessible-treeview: Please check the logs.
  • ❌ Pages changed - react-accessible-treeview: Please check the logs.
  • βœ… Travis CI - Branch: The build passed.

Release Notes for v9.2.3

9.2.3 (2019-08-17)

Bug Fixes

  • don't normalize path gitDir path for better Windows compatibility (eb3fa83)
  • generateTasks handles parent dir globs correctly (82b5182)
  • normalize gitDir path to posix using normalize-path (f485e51)
Commits

The new version differs by 7 commits.

  • c9424ad test: mock resolveGitDir to fix runAll tests
  • 82b5182 fix: generateTasks handles parent dir globs correctly
  • f485e51 fix: normalize gitDir path to posix using normalize-path
  • f77cefa refactor: generateTasks is not async
  • 23019a5 refactor: remove unnecessary try/catch
  • 94dbeda refactor: no need to create absolute paths since cwd is already absolute
  • eb3fa83 fix: don't normalize path gitDir path for better Windows compatibility

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

An in-range update of babel7 is breaking the build 🚨

There have been updates to the babel7 monorepo:

    • The devDependency @babel/core was updated from 7.7.7 to 7.8.0.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

This monorepo update includes releases of one or more dependencies which all belong to the babel7 group definition.

babel7 is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • ❌ continuous-integration/travis-ci/push: The Travis CI build failed (Details).
  • ❌ Travis CI - Branch: The build failed.

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

An in-range update of eslint-plugin-import is breaking the build 🚨

The devDependency eslint-plugin-import was updated from 2.20.0 to 2.20.1.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

eslint-plugin-import is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • ❌ continuous-integration/travis-ci/push: The Travis CI build failed (Details).
  • ❌ Travis CI - Branch: The build failed.

Commits

The new version differs by 23 commits.

  • 45f0860 Bump to v2.20.1
  • 5d00854 [Fix] order: Fix alphabetize for mixed requires and imports
  • bbd166b [Fix] export: Handle function overloading in *.d.ts
  • 4665ec5 [Fix] no-absolute-path: fix a crash with invalid import syntax
  • 392c6b9 [Fix] named: for importing from a module which re-exports named exports from a node_modules module
  • cc5bde5 [Tests] named: add failing test for #1446
  • aff3a46 [meta] fix "files" field to include/exclude the proper files
  • 986ba74 docs: fix a few spelling mistakes
  • 6274d96 [Tests] set eslint-plugin/consistent-output lint rule to always require test case output assertions
  • a4d301b [meta] add missing changelog links
  • 2d42464 [Tests] only run the linter once, not on every build
  • 26f232b [Tests] add eslint-plugin-eslint-plugin internally and fix violations
  • 99647f1 [Docs]: Update Tidelift language to enterprise
  • f84d457 no-duplicates: allow duplicate if one is namespace and other not
  • 7e71b50 [Fix] extensions: Fix scope regex

There are 23 commits in total.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

An in-range update of react-icons is breaking the build 🚨

The devDependency react-icons was updated from 3.8.0 to 3.9.0.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

react-icons is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • ❌ continuous-integration/travis-ci/push: The Travis CI build failed (Details).
  • ❌ Travis CI - Branch: The build failed.

Release Notes for v3.9.0
  • Fixed a minor typo #268
  • fix(build): add entry point. Solve issue #235 #273
  • Add Bootstrap Icons & Sort Sidebar Items #275
  • Bump handlebars from 4.1.2 to 4.5.3 #278
  • Add "No icons found" message when no icons are found #280
FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Give NPM publishing permissions to me and/or deprecate this library

Hi @lissitz, I'd like to formerly request (a) to be made admin of this repo and (b) to be given NPM publishing access. This is a continuing from the conversation in #23 (comment) where you said:

lissitz commented on Apr 1
Yes, I'm open to eventually adding you as a maintainer πŸ™‚.

My team and I have made significant updates to this library and have motivation to continue maintaining it for the foreseeable future. But in order for us to do that, we need to be able to publish it whenever we have a business objective or a customer commitment. Having to rely on someone outside of our team causes us to miss those deadlines. For example, we have PRs that haven't been released in 20 days as of this writing.

So @lissitz, please choose whichever option is best for you. The options I see are as follows (in order of my preference):

  1. Full transfer:
  2. Additive:
    • You add me as an NPM owner. This has the negative consequence that one of us might release the library at the wrong time and cause race condition type of situations. But I'm sure we could make it work.
    • You add me as a Github admin for this repo so that I can update the protected branch status rules and move away from Travis (This saves you from having to grant me Travis access and it lets my team build the repo using Github Actions which we are highly skilled in).
  3. We deprecate this library and fork:
    • I would create a fork of this library publish it myself on NPM as the owner as the sequel to this library
    • We'd add a deprecation warning so that anyone who installed this library were informed to use the new fork that I own
    • We'd update the readme to explain the where to go for the official fork

I want to emphasize that the choice is yours. I want this to feel like we're here to support you and to celebrate your wonderful accomplishments of creating (what I believe is) the only accessible tree view for React. Well done! You've done great work so now you should be able to rest on those laurels and let us take it into maintenance mode.

collapse/uncollapse all

Is there a way to collapse or uncollapse all nodes and their descendants without setting the defaultExpandedIds prop?

Type INode should have an additional property to store meta data.

Describe the solution you'd like
I want to pass some metadata to each node whilst creating the checkbox tree data.

type INode<T> = INode & {
    metadata: T
}

What value does this proposed solution bring to users?
The users will then be able to customize the outlook of each node, for example, if they want to add a number of children alongside the name they can pass that information to the parent nodes' metadata. Another example would be to be able to add tags, which can show as span elements alongside the element's name.

Describe alternatives you've considered
The alternative currently is to pass all this information somehow inside the name string and then parse that information in the nodeRenderer method. But that seems sub-optimal.

Expansion of the `metadata` object

Describe the solution you'd like

As per my comment here, I think we can improve/expand on the metadata object that was recently added to tree nodes.

In general, metadata is used to provide additional static properties that can be used to provide unique rendering or functionality to the given node. In many (similar) libraries, we can also use this metadata object to pass mutable data between rerenders. With this in mind, the current scope of the metadata object is too limited. Therefore, I propose that we do 2 things:

  1. Do not clone metadata in the flattenTree function
  2. Allow any object (keys/properties) as metadata

1. Do not clone metadata

As a user, I would expect that metadata object that I pass into a node would be the same object before and after the flattenTree call. In most javascript/typescript libraries, it is generally expected that if you pass an object to a function, then that object is mutable. If you (the user) don't want your data to be mutable, then you (the user) should handle the immutability yourself.

Therefore, we should not be doing a shallow clone of the metadata object as this breaks the expected way in which javascript objects should be handled

2. Allow any object (in the types)

The only reason we currently only allow a non-nested object is so that flattenTree can do a shallow clone. However, users who build their INode trees manually, either through async data calls or through manual data mapping, do not use flattenTree and therefore should not be limited in how metadata is handled in their nodes.

If we are no longer cloning the metadata object (see my first point), then there is nothing stopping us from supporting any object as metadata. This would allow us to pass anything as metadata including nested objects, functions or even an instances of a class.

For example, you could pass a helper function to determine which color to use, rather than having to pass a hard-coded color to the metadata object:

const metadata = {
  defaultColor: 'red',
  getNodeColor: (rendererProps: INodeRendererProps): string => {
    return rendererProps.isSelected
      ? someComplexWayToCalculateColor()
      : renderProps.element.metadata.defaultColor;
  },
}

If there are changes to the interfaces, please describe them here:

  1. Rename IFlatMetadata to IMetadata
  2. Don't use the Record<> helper type. The metadata object is completely user controlled and operated on, we don't do anything with it except detecting if it exists. Therefore, the type of metadata should be unknown.
export type IMetadata = unknown;

We should also allow users to set the type of the metadata when defining the props of their tree. Therefore, we should add generics for metadata to any interface or function that contains a reference to the INode type so that user defined metadata correctly propagates to all internal functions. This includes:

ITreeViewProps
ITreeViewOnLoadDataProps
ITreeViewOnExpandProps
ITreeViewOnNodeSelectProps
ITreeViewOnSelectProps
INodeRendererProps
INodeProps
INodeGroupProps

usePreviousData
isBranchNode
getParent
getAncestors
getDescendants
getChildren
getSibling
getLastAccessible
getPreviousAccessible
getNextAccessible
propagateSelectChange
getAccessibleRange
propagatedIds
isBranchSelectedAndHasSelectedDescendants
isBranchNotSelectedAndHasAllSelectedDescendants
isBranchNotSelectedAndHasOnlySelectedChild
isBranchSelectedAndHasOnlySelectedChild
getTreeParent
getTreeNode
validateTreeViewData

Node
NodeGroup

Describe alternatives you've considered

I'm not sure this applicable here.

Additional context

I would be happy to submit a PR to handle this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.