lukasbach / react-complex-tree

Unopinionated Accessible Tree Component with Multi-Select and Drag-And-Drop
https://rct.lukasbach.com
MIT License
944 stars 74 forks source link

Search whole tree #287

Open marchingband opened 10 months ago

marchingband commented 10 months ago

The search bar does not search collapsed parts of the tree. I would like an optiion to search the whole tree, and automatically expand any parents to show all the matching items.

lukasbach commented 9 months ago

Searching through closed items isn't really the scope of the search bar, it's mostly intended as an accessibility feature to more quickly traverse the tree DOM. Finding a specific item in the tree structure depends on the way how the tree data is structured, and this structure can vary greatly between use cases.

However, I added a function "tree.expandSubsequently" that can be used to expand a path of folders, to be able to more easily expand to a certain item. With that, and custom search implementation, something like this shouldn't be that hard to implement. I built a sample in the documentation, alongside an explanation of the idea: https://rct.lukasbach.com/docs/guides/search#finding-items-that-are-not-loaded-in

marchingband commented 9 months ago

I manually wrote a workaround, that essentially does this. My 2cents is that the ideal API would be to optionally search the whole tree and auto expand any branches that contain matches, and then as the search changes, close any branches that were auto-opened, should they no longer contain a match ... this is what I built, as it works for my personal use case, for accessibility, and I am just guessing that it would be a common one. I will look at what you added, thank you very much! I appreciate the response and effort!

lukasbach commented 9 months ago

Yes I agree. I might add a more direct interface for directly finding items in the future, but as I mentioned the kind of data structure can vary by use case, and the user can essentially provide an incomplete tree, though I guess this would still be doable with the onMissingItems handler. But maybe this new handler makes building a custom search easy enough.

justchaying commented 8 months ago

This feature would be extremely useful. In fact that's how I thought search would work by default.

@marchingband Would it be possible for you to share the workaround you implemented?

marchingband commented 8 months ago

@justchaying Here is a snippet, I basically manually do a search:

    const handleChangeSearch = useCallback(search=>{
        console.log(search.length)
        setSearchText(search)
        if(!search.length) return
        const files = Object.values(items)
        const children = files.filter(item=>(item.data.name || "").includes(search.toLowerCase())).map(item=>item.index)
        // const parents = files.filter(item=>item.children.some(c=>children.includes(c))).map(item=>item.index)
        const parents = files.filter(item=>hasDescendantIn(item, files, children)).map(item=>item.index)
        setExpandedItems([...expandedItems, ...parents])
    }, [expandedItems, searchText, items])

and update expandedItems in the viewState.

               <ControlledTreeEnvironment
                   ...
                    viewState={{
                        ["tree-1"]: {
                            focusedItem,
                            expandedItems : expandedItems,
                            // expandedItems: [...expandedItems, ...expandedInSearch],
                            selectedItems
                        }
                    }}
                    ...
                >

And I have a custom search bar to do the work

const Search = ({ props, handleChangeSearch, close }) => {
    console.log(props)
    const [text, setText] = useState()
    const dummy = useRef()
    return(
        <div className={'search-input-container'}>
        {/* <div className={'rct-tree-search-input-container'}> */}
            {/* <span className="rct-tree-input-icon" /> */}
            <input 
                {...props} 
                value={text}
                className={'rct-tree-search-input'} 
                onChange={e=>{
                    handleChangeSearch(e.target.value)
                    props.onChange(e)
                    setText(e.target.value)
                }}
                onBlur={()=>{
                    setTimeout(()=>{
                        handleChangeSearch("")
                        props.onBlur()
                    }, 100)
                    close()
                }}
            />
        </div>
    )
}

I don't know if that will help but good luck.