Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RAC Tree #5756

Merged
merged 43 commits into from
Mar 1, 2024
Merged

RAC Tree #5756

merged 43 commits into from
Mar 1, 2024

Conversation

LFDanLu
Copy link
Member

@LFDanLu LFDanLu commented Jan 26, 2024

Closes

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

🧢 Your Project:

now that we are iterating over every tree item, we need to check if a rows parent is in the expanded key AND is also in the flattened row list. This is essentially checking if every parent above it is expanded as well since that is the only way its immediate parent would be in the flattened row list
…ridListItem

this is because we need to stop the grid list keyboard navigation if we are going to perform an expand or collapse but cant do so since it is a capturing listener. An alternative is to copy all the code from useGridListItem as is to useTreeGridListItem but that can be discussed.
@rspbot
Copy link

rspbot commented Jan 27, 2024

@rspbot
Copy link

rspbot commented Jan 29, 2024

Comment on lines +437 to +438
// TODO: missing selectionBehavior, hasAction and allowsSelection data attribute equivalents (available in renderProps). Do we want those?
data-expanded={hasChildRows ? isExpanded : undefined}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Table is similar here in that it doesn't have data attributes for the mentioned attributes which feels like it makes sense, not sure how helpful it would be to expose the above as data attributes

@rspbot
Copy link

rspbot commented Feb 16, 2024

@LFDanLu LFDanLu changed the title (WIP) RAC Tree RAC Tree Feb 16, 2024
@LFDanLu LFDanLu marked this pull request as ready for review February 16, 2024 19:15
@rspbot
Copy link

rspbot commented Feb 16, 2024

Copy link
Member

@reidbarber reidbarber left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

The only unexpected behavior I see is that typeahead has unpredictable behavior.

* and selection.
*/
const _Tree = /*#__PURE__*/ (forwardRef as forwardRefType)(Tree);
export {_Tree as Tree};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be called TreeView to better match the ARIA APG https://www.w3.org/WAI/ARIA/apg/patterns/treeview/

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

happy to change it, mainly was concerned about if the RSP tree would be called TreeView too haha. Anyone else have any thoughts? Maybe TreeGrid (except it is only one column...)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine if they have the same name. IMO: TreeView > Tree > TreeGrid

packages/react-aria-components/src/Tree.tsx Outdated Show resolved Hide resolved
Co-authored-by: Reid Barber <reid@reidbarber.com>
@LFDanLu
Copy link
Member Author

LFDanLu commented Feb 16, 2024

@reidbarber what specifically did you run into for the typeahead?

@reidbarber
Copy link
Member

@LFDanLu Looks like I was just pressing keys again before the cooldown period ended. No issues there, ignore that!

@rspbot
Copy link

rspbot commented Feb 16, 2024

Copy link
Member

@snowystinger snowystinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first pass through tests

packages/react-aria-components/test/Tree.test.js Outdated Show resolved Hide resolved
packages/react-aria-components/stories/Tree.stories.tsx Outdated Show resolved Hide resolved
packages/react-aria-components/test/Tree.test.js Outdated Show resolved Hide resolved
packages/react-aria-components/test/Tree.test.js Outdated Show resolved Hide resolved
packages/react-aria-components/test/Tree.test.js Outdated Show resolved Hide resolved
packages/react-aria-components/test/Tree.test.js Outdated Show resolved Hide resolved
packages/react-aria-components/test/Tree.test.js Outdated Show resolved Hide resolved
packages/react-aria-components/test/Tree.test.js Outdated Show resolved Hide resolved
@@ -199,8 +246,9 @@ export function useGridListItem<T>(props: AriaGridListItemOptions, state: ListSt
'aria-colindex': 1
};

// TODO: should isExpanded and hasChildRows be a item state that gets returned by the hook?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems useful to me

packages/@react-aria/tree/src/useTreeGridList.ts Outdated Show resolved Hide resolved
packages/@react-aria/tree/src/useTreeGridListItem.ts Outdated Show resolved Hide resolved
packages/@react-aria/tree/src/useTreeGridListItem.ts Outdated Show resolved Hide resolved
Comment on lines +93 to +96
// TODO: would be nice if the collection row information differentiated between childNodes vs childItems
// so we didn't have to keep iterating through info, perhaps make the user pass a prop to TreeItem for childItems/hasChildRows even in the static case?
updatedExpandedKeys = new Set([...collection].filter(row => {
return row.props.childItems || [...collection.getChildren(row.key)].filter(child => child.type === 'item').length > 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when you construct the collection, can you just mark the item node?

packages/react-aria-components/src/Tree.tsx Show resolved Hide resolved
Comment on lines +332 to +334
// TODO does this need ref or context? Its only used to shallowly render the Content node... If it was a more generic collection component then I could see an argument for it
// having those
function TreeItemContent(props: TreeItemContentProps) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what would the ref point to? there isn't an actual dom node corresponding to this right?

packages/react-aria-components/src/Tree.tsx Outdated Show resolved Hide resolved
added test util and updating some tests for consistency
Comment on lines +127 to +138
if ('expandedKeys' in state && document.activeElement === ref.current) {
if ((e.key === EXPANSION_KEYS['expand'][direction]) && state.selectionManager.focusedKey === node.key && hasChildRows && state.expandedKeys !== 'all' && !state.expandedKeys.has(node.key)) {
state.toggleKey(node.key);
e.stopPropagation();
return;
} else if ((e.key === EXPANSION_KEYS['collapse'][direction]) && state.selectionManager.focusedKey === node.key && hasChildRows && (state.expandedKeys === 'all' || state.expandedKeys.has(node.key))) {
state.toggleKey(node.key);
e.stopPropagation();
return;
}
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to modify the keyboard nav logic here for expansion and stuff since this is a capturing listener and thus can't be stopped in the useTreeGridListItem hook. If we want to keep this hooks clean from tree grid stuff, I can copy pasta the code here into useTreeGridListItem. Alternatively, I could make this hook and useGridList hook keep all the grid specific stuff and get rid of the TreeGridList hooks

@rspbot
Copy link

rspbot commented Feb 22, 2024

Copy link
Member

@snowystinger snowystinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Giving preliminary approval. Is it ok to make the rest followup? I'd like to get this into testing.

Copy link
Member

@reidbarber reidbarber left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, still have the question about naming, but we can change that later if we need to.

@rspbot
Copy link

rspbot commented Mar 1, 2024

@rspbot
Copy link

rspbot commented Mar 1, 2024

## API Changes

unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any', access: 'private' }
unknown top level export { type: 'any', access: 'private' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'identifier', name: 'Column' }
unknown top level export { type: 'identifier', name: 'Column' }
unknown type { type: 'link' }
unknown type { type: 'link' }
unknown type { type: 'link' }
unknown type { type: 'link' }
unknown type { type: 'link' }
unknown type { type: 'link' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }
unknown top level export { type: 'any' }

@react-aria/gridlist

useGridListItem

 useGridListItem<T> {
   props: AriaGridListItemOptions
-  state: ListState<T>
+  state: ListState<T> | TreeState<T>
   ref: RefObject<FocusableElement>
   returnVal: undefined
 }

@react-aria/tree

useTreeGridList

changed by:

  • AriaTreeGridListOptions
  • TreeGridListAria
-
+useTreeGridList<T> {
+  props: AriaTreeGridListOptions<T>
+  state: TreeState<T>
+  ref: RefObject<HTMLElement>
+  returnVal: undefined
+}

useTreeGridListItem

changed by:

  • AriaTreeGridListItemOptions
  • TreeGridListItemAria
-
+useTreeGridListItem<T> {
+  props: AriaTreeGridListItemOptions
+  state: TreeState<T>
+  ref: RefObject<FocusableElement>
+  returnVal: undefined
+}

AriaTreeGridListOptions

-
+AriaTreeGridListOptions<T> {
+  keyboardDelegate?: KeyboardDelegate
+}

it changed:

  • useTreeGridList

AriaTreeGridListProps

+AriaTreeGridListProps<T> {
 
+}

TreeGridListAria

-
+TreeGridListAria {
+  gridProps: DOMAttributes
+}

it changed:

  • useTreeGridList

TreeGridListProps

+TreeGridListProps<T> {
 
+}

AriaTreeGridListItemOptions

-
+AriaTreeGridListItemOptions {
+  node: Node<unknown>
+}

it changed:

  • useTreeGridListItem

TreeGridListItemAria

-
+TreeGridListItemAria {
+  descriptionProps: DOMAttributes
+  gridCellProps: DOMAttributes
+  rowProps: DOMAttributes
+}

it changed:

  • useTreeGridListItem

@react-stately/tree

TreeProps

 TreeProps<T> {
-
+  disabledBehavior?: DisabledBehavior
 }

it changed:

  • useTreeState

TreeState

 TreeState<T> {
   collection: Collection<Node<T>>
   disabledKeys: Set<Key>
-  expandedKeys: Set<Key>
+  expandedKeys: 'all' | Set<Key>
   selectionManager: SelectionManager
-  setExpandedKeys: (Set<Key>) => void
+  setExpandedKeys: ('all' | Set<Key>) => void
   toggleKey: (Key) => void
 }

it changed:

  • useTreeState

TreeCollection

 TreeCollection<T> {
   at: (number) => void
   constructor: (Iterable<Node<T>>, {
-    expandedKeys?: Set<Key>
+    expandedKeys?: 'all' | Set<Key>
 }) => void
   getFirstKey: () => void
   getItem: (Key) => void
   getKeyAfter: (Key) => void
   getKeys: () => void
   getLastKey: () => void
   size: any
   undefined: () => void
 }
 

@LFDanLu LFDanLu merged commit 57ff24d into main Mar 1, 2024
27 checks passed
@LFDanLu LFDanLu deleted the treeview_start branch March 1, 2024 19:47
@LFDanLu
Copy link
Member Author

LFDanLu commented Mar 1, 2024

Merged for testing, feel free to continue reviewing off this PR if need be. I'll capture the followups in a ticket

@@ -26,13 +29,13 @@ export interface TreeState<T> {
readonly disabledKeys: Set<Key>,

/** A set of keys for items that are expanded. */
readonly expandedKeys: Set<Key>,
readonly expandedKeys: 'all' | Set<Key>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changing this type could potentially be a breaking change. if someone was relying on expandedKeys being a set (and doing things like expandedKeys.has('foo') on it), then the 'all' value would cause an error to be thrown.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, good call, thats a bit annoying since it would be good for this to be consistent with our expandable rows table api... I can get rid of this 'all' support for now, but I guess I would have to create a separate tree state hook then to support 'all', thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where did the requirement for all come from? We don't have UI for an "expand all" button right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't have a hard requirement for all but it was added to the expandable table rows to help with the eventual async case where a team may want all their rows to be auto expanded as they are loaded in

let renderProps = useRenderProps({
...props,
id: undefined,
children: item.rendered,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do tree items support render props? seems like that could conflict with the function for providing children?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh hm, I hadn't really considered that, in my mind I figured users would use the renderProps that TreeItemContent provides. I'll see if I can get rid of the renderProps function for TreeItem entirely

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, actually I've already have the types setup such that the children of the TreeItem doesn't include the renderProps children definition. I've gotten rid of the ((item: T) => ReactElement) part of it since users should use a Collection for the dynamic case

@LFDanLu LFDanLu mentioned this pull request Apr 10, 2024
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants