From eeb960372fa352175dff27bcd9d2185c1690fd5c Mon Sep 17 00:00:00 2001 From: Flavien DELANGLE Date: Tue, 19 Mar 2024 13:49:08 +0100 Subject: [PATCH 1/9] [TreeView] Set focus on the focused Tree Item instead of the Tree View (#12226) --- .../migration-tree-view-v6.md | 32 + .../SimpleTreeView/SimpleTreeView.test.tsx | 273 +++--- .../src/TreeItem/TreeItem.test.tsx | 832 ++++++++---------- .../x-tree-view/src/TreeItem/TreeItem.tsx | 23 +- .../useTreeViewExpansion.ts | 2 +- .../useTreeViewExpansion.types.ts | 2 +- .../useTreeViewFocus/useTreeViewFocus.ts | 105 ++- .../useTreeViewFocus.types.ts | 9 +- .../useTreeViewKeyboardNavigation.ts | 334 ++++--- .../useTreeViewKeyboardNavigation.types.ts | 6 + .../useTreeViewNodes/useTreeViewNodes.ts | 13 +- .../useTreeViewSelection.ts | 4 +- .../useTreeViewSelection.types.ts | 4 +- .../src/internals/useTreeView/useTreeView.ts | 1 - .../x-tree-view/src/internals/utils/utils.ts | 1 + .../src/useTreeItem2/useTreeItem2.ts | 36 +- .../src/useTreeItem2/useTreeItem2.types.ts | 6 +- 17 files changed, 827 insertions(+), 856 deletions(-) diff --git a/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md b/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md index 389ee9b382188..bd4557049c723 100644 --- a/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md +++ b/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md @@ -402,6 +402,38 @@ you can use the new `onItemSelectionToggle` prop which is called whenever an ite ::: +### Focus the Tree Item instead of the Tree View + +The focus is now applied to the Tree Item root element instead of the Tree View root element. + +This change will allow new features that require the focus to be on the Tree Item, +like the drag and drop reordering of items. +It also solves several issues with focus management, +like the inability to scroll to the focused item when a lot of items are rendered. + +This will mostly impact how you write tests to interact with the Tree View: + +For example, if you were writing a test with `react-testing-library`, here is what the changes could look like: + +```diff + it('test example on first item', () => { + const { getByRole } = render( + + One + Two + + ); +- const tree = getByRole('tree'); ++ const treeItem = getByRole('treeitem', { name: 'One' }); + act(() => { +- tree.focus(); ++ treeItem.focus(); + }); +- fireEvent.keyDown(tree, { key: 'ArrowDown' }); ++ fireEvent.keyDown(treeItem, { key: 'ArrowDown' }); + }) +``` + ### ✅ Use `useTreeItemState` instead of `useTreeItem` The `useTreeItem` hook has been renamed `useTreeItemState`. diff --git a/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.test.tsx b/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.test.tsx index 69e2f4b8bccab..ee134b0e87a23 100644 --- a/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.test.tsx +++ b/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.test.tsx @@ -79,16 +79,6 @@ describe('', () => { expect(screen.getByTestId('item-2')).to.have.attribute('aria-selected', 'true'); }); - it('should not crash on keydown on an empty tree', () => { - render(); - - act(() => { - screen.getByRole('tree').focus(); - }); - - fireEvent.keyDown(screen.getByRole('tree'), { key: ' ' }); - }); - it('should not crash when unmounting with duplicate ids', () => { // eslint-disable-next-line @typescript-eslint/no-unused-vars function CustomTreeItem(props: any) { @@ -128,76 +118,47 @@ describe('', () => { }); it('should call onKeyDown when a key is pressed', () => { - const handleKeyDown = spy(); + const handleTreeViewKeyDown = spy(); + const handleTreeItemKeyDown = spy(); - const { getByRole } = render( - - + const { getByTestId } = render( + + , ); + + const itemOne = getByTestId('one'); act(() => { - getByRole('tree').focus(); + itemOne.focus(); }); - fireEvent.keyDown(getByRole('tree'), { key: 'Enter' }); - fireEvent.keyDown(getByRole('tree'), { key: 'A' }); - fireEvent.keyDown(getByRole('tree'), { key: ']' }); + fireEvent.keyDown(itemOne, { key: 'Enter' }); + fireEvent.keyDown(itemOne, { key: 'A' }); + fireEvent.keyDown(itemOne, { key: ']' }); - expect(handleKeyDown.callCount).to.equal(3); + expect(handleTreeViewKeyDown.callCount).to.equal(3); + expect(handleTreeItemKeyDown.callCount).to.equal(3); }); it('should select item when Enter key is pressed ', () => { const handleKeyDown = spy(); - const { getByRole, getByTestId } = render( + const { getByTestId } = render( - + , ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); expect(getByTestId('one')).not.to.have.attribute('aria-selected'); - fireEvent.keyDown(getByRole('tree'), { key: 'Enter' }); + fireEvent.keyDown(getByTestId('one'), { key: 'Enter' }); expect(getByTestId('one')).to.have.attribute('aria-selected'); }); - it('should call onFocus when tree is focused', () => { - const handleFocus = spy(); - const { getByRole } = render( - - - , - ); - - act(() => { - getByRole('tree').focus(); - }); - - expect(handleFocus.callCount).to.equal(1); - }); - - it('should call onBlur when tree is blurred', () => { - const handleBlur = spy(); - const { getByRole } = render( - - - , - ); - - act(() => { - getByRole('tree').focus(); - }); - act(() => { - getByRole('tree').blur(); - }); - - expect(handleBlur.callCount).to.equal(1); - }); - it('should be able to be controlled with the expandedItems prop', () => { function MyComponent() { const [expandedState, setExpandedState] = React.useState([]); @@ -206,20 +167,20 @@ describe('', () => { }; return ( - - + + ); } - const { getByRole, getByTestId, getByText } = render(); + const { getByTestId, getByText } = render(); expect(getByTestId('one')).to.have.attribute('aria-expanded', 'false'); fireEvent.click(getByText('one')); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); @@ -228,7 +189,7 @@ describe('', () => { expect(getByTestId('one')).to.have.attribute('aria-expanded', 'false'); - fireEvent.keyDown(getByRole('tree'), { key: '*' }); + fireEvent.keyDown(getByTestId('one'), { key: '*' }); expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); }); @@ -303,39 +264,35 @@ describe('', () => { return ( { + defaultExpandedItems={['one']} + onItemFocus={() => { setState(Math.random); }} - id="tree" > - - + + ); } - const { getByRole, getByText, getByTestId } = render(); - - fireEvent.click(getByText('one')); - // Clicks would normally focus tree - act(() => { - getByRole('tree').focus(); - }); + const { getByTestId } = render(); - expect(getByTestId('one')).toHaveVirtualFocus(); + fireEvent.focus(getByTestId('one')); + fireEvent.focus(getByTestId('one')); + expect(getByTestId('one')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowDown' }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowUp' }); + fireEvent.keyDown(getByTestId('two'), { key: 'ArrowUp' }); - expect(getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowDown' }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); }); it('should support conditional rendered tree items', () => { @@ -360,49 +317,48 @@ describe('', () => { }); it('should work in a portal', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( - - - - - + + + + + , ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowDown' }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByTestId('two'), { key: 'ArrowDown' }); - expect(getByTestId('three')).toHaveVirtualFocus(); + expect(getByTestId('three')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByTestId('three'), { key: 'ArrowDown' }); - expect(getByTestId('four')).toHaveVirtualFocus(); + expect(getByTestId('four')).toHaveFocus(); }); describe('onItemFocus', () => { - it('should be called when item is focused', () => { - const focusSpy = spy(); - const { getByRole } = render( - - + it('should be called when an item is focused', () => { + const onFocus = spy(); + const { getByTestId } = render( + + , ); - // First item receives focus when tree focused act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - expect(focusSpy.callCount).to.equal(1); - expect(focusSpy.args[0][1]).to.equal('1'); + expect(onFocus.callCount).to.equal(1); + expect(onFocus.args[0][1]).to.equal('one'); }); }); @@ -443,95 +399,70 @@ describe('', () => { }); describe('useTreeViewFocus', () => { - it('should focus the selected item when the tree is focused', () => { - const onItemFocus = spy(); - - const { getByRole } = render( - - - + it('should set tabIndex={0} on the selected item', () => { + const { getByTestId } = render( + + + , ); - act(() => { - getByRole('tree').focus(); - }); - - expect(onItemFocus.lastCall.lastArg).to.equal('2'); + expect(getByTestId('one').tabIndex).to.equal(0); + expect(getByTestId('two').tabIndex).to.equal(-1); }); - it('should focus the selected item when the tree is focused (multi select)', () => { - const onItemFocus = spy(); - - const { getByRole } = render( - - - + it('should set tabIndex={0} on the selected item (multi select)', () => { + const { getByTestId } = render( + + + , ); - act(() => { - getByRole('tree').focus(); - }); - - expect(onItemFocus.lastCall.lastArg).to.equal('2'); + expect(getByTestId('one').tabIndex).to.equal(0); + expect(getByTestId('two').tabIndex).to.equal(-1); }); - it('should focus the first visible selected item when the tree is focused (multi select)', () => { - const onItemFocus = spy(); - - const { getByRole } = render( - - - + it('should set tabIndex={0} on the first visible selected item (multi select)', () => { + const { getByTestId } = render( + + + - + , ); - act(() => { - getByRole('tree').focus(); - }); - - expect(onItemFocus.lastCall.lastArg).to.equal('2'); + expect(getByTestId('one').tabIndex).to.equal(-1); + expect(getByTestId('three').tabIndex).to.equal(0); }); - it('should focus the first item if the selected item is not visible', () => { - const onItemFocus = spy(); - - const { getByRole } = render( - - - + it('should set tabIndex={0} on the first item if the selected item is not visible', () => { + const { getByTestId } = render( + + + - + , ); - act(() => { - getByRole('tree').focus(); - }); - - expect(onItemFocus.lastCall.lastArg).to.equal('1'); + expect(getByTestId('one').tabIndex).to.equal(0); + expect(getByTestId('three').tabIndex).to.equal(-1); }); - it('should focus the first item if no selected item is visible (multi select)', () => { - const onItemFocus = spy(); - - const { getByRole } = render( - - - + it('should set tabIndex={0} on the first item if no selected item is visible (multi select)', () => { + const { getByTestId } = render( + + + - + , ); - act(() => { - getByRole('tree').focus(); - }); - - expect(onItemFocus.lastCall.lastArg).to.equal('1'); + expect(getByTestId('one').tabIndex).to.equal(0); + expect(getByTestId('three').tabIndex).to.equal(-1); }); it('should focus specific item using `apiRef`', () => { @@ -542,22 +473,22 @@ describe('', () => { apiRef = useTreeViewApiRef(); return ( - - + + - + ); } - const { getByRole } = render(); + const { getByTestId } = render(); act(() => { - apiRef.current?.focusItem({} as React.SyntheticEvent, '2'); + apiRef.current?.focusItem({} as React.SyntheticEvent, 'three'); }); - expect(getByRole('tree')).toHaveFocus(); - expect(onItemFocus.lastCall.lastArg).to.equal('2'); + expect(getByTestId('three')).toHaveFocus(); + expect(onItemFocus.lastCall.lastArg).to.equal('three'); }); it('should not focus item if parent is collapsed', () => { diff --git a/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx b/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx index 87728e407c48a..92ee6764f4ed8 100644 --- a/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx +++ b/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { expect } from 'chai'; import PropTypes from 'prop-types'; import { spy } from 'sinon'; -import { act, createEvent, createRenderer, fireEvent, screen } from '@mui-internal/test-utils'; +import { act, createEvent, createRenderer, fireEvent } from '@mui-internal/test-utils'; import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; import { SimpleTreeViewPlugins } from '@mui/x-tree-view/SimpleTreeView/SimpleTreeView.plugins'; import { TreeItem, treeItemClasses as classes } from '@mui/x-tree-view/TreeItem'; @@ -19,6 +19,7 @@ const TEST_TREE_VIEW_CONTEXT_VALUE: TreeViewContextValue isNodeDisabled: (itemId: string | null): itemId is string => !!itemId, getTreeItemId: () => '', mapFirstCharFromJSX: () => () => {}, + canItemBeTabbed: () => false, } as any, publicAPI: { focusItem: () => {}, @@ -251,14 +252,14 @@ describe('', () => { }); it('should be able to use a custom id', () => { - const { getByRole } = render( + const { getByRole, getByTestId } = render( - + , ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); expect(getByRole('tree')).to.have.attribute('aria-activedescendant', 'customId'); @@ -401,10 +402,10 @@ describe('', () => { }); }); - describe('when a tree receives focus', () => { + describe('when an item receives focus', () => { it('should focus the first node if none of the nodes are selected before the tree receives focus', () => { - const { getByRole, getByTestId, queryAllByRole } = render( - + const { getByTestId, queryAllByRole } = render( + @@ -414,51 +415,33 @@ describe('', () => { expect(queryAllByRole('treeitem', { selected: true })).to.have.length(0); act(() => { - getByRole('tree').focus(); - }); - - expect(getByTestId('one')).toHaveVirtualFocus(); - }); - - it('should focus the selected node if a node is selected before the tree receives focus', () => { - const { getByTestId, getByRole } = render( - - - - - , - ); - - expect(getByTestId('two')).to.have.attribute('aria-selected', 'true'); - - act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); }); it('should work with programmatic focus', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( - - + + , ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - expect(getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); act(() => { getByTestId('two').focus(); }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); }); - it('should work when focused item is removed', () => { + it('should work when focused node is removed', () => { let removeActiveItem; // a TreeItem which can remove from the tree by calling `removeActiveItem` function ControlledTreeItem(props) { @@ -471,25 +454,19 @@ describe('', () => { return ; } - const { getByRole, getByTestId, getByText } = render( - - - - + const { getByTestId } = render( + + + + , ); - const tree = getByRole('tree'); act(() => { - tree.focus(); + getByTestId('three').focus(); }); - - expect(getByTestId('parent')).toHaveVirtualFocus(); - - fireEvent.click(getByText('two')); - - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('three')).toHaveFocus(); // generic action that removes an item. // Could be promise based, or timeout, or another user interaction @@ -497,31 +474,15 @@ describe('', () => { removeActiveItem(); }); - expect(getByTestId('parent')).toHaveVirtualFocus(); - }); - - it('should focus on tree with scroll prevented', () => { - const { getByRole, getByTestId } = render( - - - - , - ); - const focus = spy(getByRole('tree'), 'focus'); - - act(() => { - getByTestId('one').focus(); - }); - - expect(focus.calledOnceWithExactly({ preventScroll: true })).to.equals(true); + expect(getByTestId('one')).toHaveFocus(); }); }); describe('Navigation', () => { describe('right arrow interaction', () => { it('should open the node and not move the focus if focus is on a closed node', () => { - const { getByRole, getByTestId } = render( - + const { getByTestId } = render( + @@ -531,17 +492,17 @@ describe('', () => { expect(getByTestId('one')).to.have.attribute('aria-expanded', 'false'); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowRight' }); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowRight' }); expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - expect(getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); }); - it('should move focus to the first child if focus is on an open item', () => { - const { getByTestId, getByRole } = render( - + it('should move focus to the first child if focus is on an open node', () => { + const { getByTestId } = render( + @@ -551,87 +512,81 @@ describe('', () => { expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowRight' }); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowRight' }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); }); it('should do nothing if focus is on an end item', () => { - const { getByRole, getByTestId, getByText } = render( - + const { getByTestId } = render( + , ); - fireEvent.click(getByText('two')); act(() => { - getByRole('tree').focus(); + getByTestId('two').focus(); }); - expect(getByTestId('two')).toHaveVirtualFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowRight' }); + expect(getByTestId('two')).toHaveFocus(); + fireEvent.keyDown(getByTestId('two'), { key: 'ArrowRight' }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); }); }); describe('left arrow interaction', () => { - it('should close the node if focus is on an open node', () => { - render( - + it('should close the item if focus is on an open item', () => { + const { getByTestId, getByText } = render( + , ); - const [firstItem] = screen.getAllByRole('treeitem'); - const firstItemLabel = screen.getByText('one'); - - fireEvent.click(firstItemLabel); - - expect(firstItem).to.have.attribute('aria-expanded', 'true'); + fireEvent.click(getByText('one')); act(() => { - screen.getByRole('tree').focus(); + getByTestId('one').focus(); }); - fireEvent.keyDown(screen.getByRole('tree'), { key: 'ArrowLeft' }); - expect(firstItem).to.have.attribute('aria-expanded', 'false'); - expect(screen.getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); + + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowLeft' }); + + expect(getByTestId('one')).to.have.attribute('aria-expanded', 'false'); + expect(getByTestId('one')).toHaveFocus(); }); - it("should move focus to the item's parent item if focus is on a child that is an end node", () => { - render( - + it("should move focus to the item's parent item if focus is on a child node that is an end item", () => { + const { getByTestId } = render( + , ); - const [firstItem] = screen.getAllByRole('treeitem'); - const secondItemLabel = screen.getByText('two'); - expect(firstItem).to.have.attribute('aria-expanded', 'true'); + expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - fireEvent.click(secondItemLabel); act(() => { - screen.getByRole('tree').focus(); + getByTestId('two').focus(); }); - expect(screen.getByTestId('two')).toHaveVirtualFocus(); - fireEvent.keyDown(screen.getByRole('tree'), { key: 'ArrowLeft' }); + expect(getByTestId('two')).toHaveFocus(); + fireEvent.keyDown(getByTestId('two'), { key: 'ArrowLeft' }); - expect(screen.getByTestId('one')).toHaveVirtualFocus(); - expect(firstItem).to.have.attribute('aria-expanded', 'true'); + expect(getByTestId('one')).toHaveFocus(); + expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); }); it("should move focus to the node's parent node if focus is on a child node that is closed", () => { - render( - + const { getByTestId } = render( + @@ -640,25 +595,23 @@ describe('', () => { , ); - fireEvent.click(screen.getByText('one')); - - expect(screen.getByTestId('one')).to.have.attribute('aria-expanded', 'true'); + expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); act(() => { - screen.getByTestId('two').focus(); + getByTestId('two').focus(); }); - expect(screen.getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); - fireEvent.keyDown(screen.getByRole('tree'), { key: 'ArrowLeft' }); + fireEvent.keyDown(getByTestId('two'), { key: 'ArrowLeft' }); - expect(screen.getByTestId('one')).toHaveVirtualFocus(); - expect(screen.getByTestId('one')).to.have.attribute('aria-expanded', 'true'); + expect(getByTestId('one')).toHaveFocus(); + expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); }); it('should do nothing if focus is on a root node that is closed', () => { - const { getByRole, getByTestId } = render( - + const { getByTestId } = render( + @@ -666,50 +619,50 @@ describe('', () => { ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); expect(getByTestId('one')).to.have.attribute('aria-expanded', 'false'); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowLeft' }); - expect(getByTestId('one')).toHaveVirtualFocus(); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowLeft' }); + expect(getByTestId('one')).toHaveFocus(); }); it('should do nothing if focus is on a root node that is an end node', () => { - const { getByRole, getByTestId } = render( - + const { getByTestId } = render( + , ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowLeft' }); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowLeft' }); - expect(getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); }); }); describe('down arrow interaction', () => { it('moves focus to a sibling node', () => { - const { getByRole, getByTestId } = render( - + const { getByTestId } = render( + , ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowDown' }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); }); it('moves focus to a child item', () => { - const { getByRole, getByTestId } = render( - + const { getByTestId } = render( + @@ -719,11 +672,11 @@ describe('', () => { expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowDown' }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); }); it('moves focus to a child item works with a dynamic tree', () => { @@ -739,7 +692,7 @@ describe('', () => { > Toggle Hide - + {!hide && ( @@ -751,7 +704,7 @@ describe('', () => { ); } - const { getByRole, queryByTestId, getByTestId, getByText } = render(); + const { queryByTestId, getByTestId, getByText } = render(); expect(getByTestId('one')).not.to.equal(null); fireEvent.click(getByText('Toggle Hide')); @@ -760,16 +713,16 @@ describe('', () => { expect(getByTestId('one')).not.to.equal(null); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowDown' }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); }); it("moves focus to a parent's sibling", () => { - const { getByRole, getByTestId, getByText } = render( - + const { getByTestId } = render( + @@ -779,43 +732,41 @@ describe('', () => { expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - fireEvent.click(getByText('two')); act(() => { - getByRole('tree').focus(); + getByTestId('two').focus(); }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByTestId('two'), { key: 'ArrowDown' }); - expect(getByTestId('three')).toHaveVirtualFocus(); + expect(getByTestId('three')).toHaveFocus(); }); }); describe('up arrow interaction', () => { it('moves focus to a sibling node', () => { - const { getByRole, getByTestId, getByText } = render( - + const { getByTestId } = render( + , ); - fireEvent.click(getByText('two')); act(() => { - getByRole('tree').focus(); + getByTestId('two').focus(); }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowUp' }); + fireEvent.keyDown(getByTestId('two'), { key: 'ArrowUp' }); - expect(getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); }); it('moves focus to a parent', () => { - const { getByRole, getByTestId, getByText } = render( - + const { getByTestId } = render( + @@ -824,21 +775,20 @@ describe('', () => { expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - fireEvent.click(getByText('two')); act(() => { - getByRole('tree').focus(); + getByTestId('two').focus(); }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowUp' }); + fireEvent.keyDown(getByTestId('two'), { key: 'ArrowUp' }); - expect(getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); }); it("moves focus to a sibling's child", () => { - const { getByRole, getByTestId, getByText } = render( - + const { getByTestId } = render( + @@ -848,23 +798,22 @@ describe('', () => { expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); - fireEvent.click(getByText('three')); act(() => { - getByRole('tree').focus(); + getByTestId('three').focus(); }); - expect(getByTestId('three')).toHaveVirtualFocus(); + expect(getByTestId('three')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowUp' }); + fireEvent.keyDown(getByTestId('three'), { key: 'ArrowUp' }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); }); }); describe('home key interaction', () => { it('moves focus to the first node in the tree', () => { - const { getByRole, getByTestId, getByText } = render( - + const { getByTestId } = render( + @@ -872,23 +821,22 @@ describe('', () => { , ); - fireEvent.click(getByText('four')); act(() => { - getByRole('tree').focus(); + getByTestId('four').focus(); }); - expect(getByTestId('four')).toHaveVirtualFocus(); + expect(getByTestId('four')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'Home' }); + fireEvent.keyDown(getByTestId('four'), { key: 'Home' }); - expect(getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); }); }); describe('end key interaction', () => { it('moves focus to the last node in the tree without expanded items', () => { - const { getByRole, getByTestId } = render( - + const { getByTestId } = render( + @@ -897,19 +845,19 @@ describe('', () => { ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - expect(getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'End' }); + fireEvent.keyDown(getByTestId('one'), { key: 'End' }); - expect(getByTestId('four')).toHaveVirtualFocus(); + expect(getByTestId('four')).toHaveFocus(); }); - it('moves focus to the last item in the tree with expanded items', () => { - const { getByRole, getByTestId } = render( - + it('moves focus to the last node in the tree with expanded items', () => { + const { getByTestId } = render( + @@ -922,21 +870,21 @@ describe('', () => { ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - expect(getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'End' }); + fireEvent.keyDown(getByTestId('one'), { key: 'End' }); - expect(getByTestId('six')).toHaveVirtualFocus(); + expect(getByTestId('six')).toHaveFocus(); }); }); describe('type-ahead functionality', () => { it('moves focus to the next node with a name that starts with the typed character', () => { - const { getByRole, getByTestId } = render( - + const { getByTestId } = render( + two} data-testid="two" /> @@ -945,27 +893,27 @@ describe('', () => { ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - expect(getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 't' }); + fireEvent.keyDown(getByTestId('one'), { key: 't' }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'f' }); + fireEvent.keyDown(getByTestId('two'), { key: 'f' }); - expect(getByTestId('four')).toHaveVirtualFocus(); + expect(getByTestId('four')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'o' }); + fireEvent.keyDown(getByTestId('four'), { key: 'o' }); - expect(getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); }); it('moves focus to the next node with the same starting character', () => { - const { getByRole, getByTestId } = render( - + const { getByTestId } = render( + @@ -974,51 +922,51 @@ describe('', () => { ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - expect(getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 't' }); + fireEvent.keyDown(getByTestId('one'), { key: 't' }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 't' }); + fireEvent.keyDown(getByTestId('two'), { key: 't' }); - expect(getByTestId('three')).toHaveVirtualFocus(); + expect(getByTestId('three')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 't' }); + fireEvent.keyDown(getByTestId('three'), { key: 't' }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); }); it('should not move focus when pressing a modifier key + letter', () => { - const { getByRole, getByTestId } = render( - - - - - + const { getByTestId } = render( + + + + + , ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - expect(getByTestId('apple')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'v', ctrlKey: true }); + fireEvent.keyDown(getByTestId('one'), { key: 'f', ctrlKey: true }); - expect(getByTestId('apple')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'v', metaKey: true }); + fireEvent.keyDown(getByTestId('one'), { key: 'f', metaKey: true }); - expect(getByTestId('apple')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'v', shiftKey: true }); + fireEvent.keyDown(getByTestId('one'), { key: 'f', shiftKey: true }); - expect(getByTestId('apple')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); }); it('should not throw when an item is removed', () => { @@ -1029,7 +977,7 @@ describe('', () => { - + {!hide && } @@ -1038,21 +986,21 @@ describe('', () => { ); } - const { getByRole, getByText, getByTestId } = render(); + const { getByText, getByTestId } = render(); fireEvent.click(getByText('Hide')); - expect(getByTestId('navTo')).not.toHaveVirtualFocus(); + expect(getByTestId('navTo')).not.toHaveFocus(); expect(() => { act(() => { - getByRole('tree').focus(); + getByTestId('keyDown').focus(); }); - expect(getByTestId('keyDown')).toHaveVirtualFocus(); + expect(getByTestId('keyDown')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'a' }); + fireEvent.keyDown(getByTestId('keyDown'), { key: 'a' }); }).not.to.throw(); - expect(getByTestId('navTo')).toHaveVirtualFocus(); + expect(getByTestId('navTo')).toHaveFocus(); }); }); @@ -1060,7 +1008,7 @@ describe('', () => { it('expands all siblings that are at the same level as the current item', () => { const onExpandedItemsChange = spy(); - const { getByRole, getByTestId } = render( + const { getByTestId } = render( @@ -1078,14 +1026,14 @@ describe('', () => { ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); expect(getByTestId('one')).to.have.attribute('aria-expanded', 'false'); expect(getByTestId('three')).to.have.attribute('aria-expanded', 'false'); expect(getByTestId('five')).to.have.attribute('aria-expanded', 'false'); - fireEvent.keyDown(getByRole('tree'), { key: '*' }); + fireEvent.keyDown(getByTestId('one'), { key: '*' }); expect(onExpandedItemsChange.args[0][1]).to.have.length(3); @@ -1101,7 +1049,7 @@ describe('', () => { describe('Expansion', () => { describe('enter key interaction', () => { it('expands a node with children', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( @@ -1110,18 +1058,18 @@ describe('', () => { ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); expect(getByTestId('one')).to.have.attribute('aria-expanded', 'false'); - fireEvent.keyDown(getByRole('tree'), { key: 'Enter' }); + fireEvent.keyDown(getByTestId('one'), { key: 'Enter' }); expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); }); it('collapses a node with children', () => { - const { getByRole, getByTestId, getByText } = render( + const { getByTestId } = render( @@ -1129,15 +1077,16 @@ describe('', () => { , ); - fireEvent.click(getByText('one')); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); + expect(getByTestId('one')).to.have.attribute('aria-expanded', 'false'); - fireEvent.keyDown(getByRole('tree'), { key: 'Enter' }); + fireEvent.keyDown(getByTestId('one'), { key: 'Enter' }); + expect(getByTestId('one')).to.have.attribute('aria-expanded', 'true'); + fireEvent.keyDown(getByTestId('one'), { key: 'Enter' }); expect(getByTestId('one')).to.have.attribute('aria-expanded', 'false'); }); }); @@ -1146,83 +1095,83 @@ describe('', () => { describe('Single Selection', () => { describe('keyboard', () => { it('should select a node when space is pressed', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( , ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); expect(getByTestId('one')).not.to.have.attribute('aria-selected'); - fireEvent.keyDown(getByRole('tree'), { key: ' ' }); + fireEvent.keyDown(getByTestId('one'), { key: ' ' }); expect(getByTestId('one')).to.have.attribute('aria-selected', 'true'); }); it('should not deselect a node when space is pressed on a selected node', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( , ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - expect(getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); expect(getByTestId('one')).to.have.attribute('aria-selected', 'true'); - fireEvent.keyDown(getByRole('tree'), { key: ' ' }); + fireEvent.keyDown(getByTestId('one'), { key: ' ' }); expect(getByTestId('one')).to.have.attribute('aria-selected', 'true'); }); it('should not select a node when space is pressed and disableSelection', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( , ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - fireEvent.keyDown(getByRole('tree'), { key: ' ' }); + fireEvent.keyDown(getByTestId('one'), { key: ' ' }); expect(getByTestId('one')).not.to.have.attribute('aria-selected'); }); it('should select a node when Enter is pressed and the node is not selected', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( , ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - fireEvent.keyDown(getByRole('tree'), { key: 'Enter' }); + fireEvent.keyDown(getByTestId('one'), { key: 'Enter' }); expect(getByTestId('one')).to.have.attribute('aria-selected'); }); it('should not un-select a node when Enter is pressed and the node is selected', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( , ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - fireEvent.keyDown(getByRole('tree'), { key: 'Enter' }); + fireEvent.keyDown(getByTestId('one'), { key: 'Enter' }); expect(getByTestId('one')).to.have.attribute('aria-selected'); }); @@ -1269,7 +1218,7 @@ describe('', () => { describe('Multi Selection', () => { describe('deselection', () => { describe('mouse behavior when multiple nodes are selected', () => { - specify('clicking a selected node holding ctrl should deselect the node', () => { + it('clicking a selected node holding ctrl should deselect the node', () => { const { getByText, getByTestId } = render( @@ -1284,7 +1233,7 @@ describe('', () => { expect(getByTestId('two')).to.have.attribute('aria-selected', 'true'); }); - specify('clicking a selected node holding meta should deselect the node', () => { + it('clicking a selected node holding meta should deselect the node', () => { const { getByText, getByTestId } = render( @@ -1318,27 +1267,27 @@ describe('', () => { }); it('should deselect the item when pressing space on a selected item', () => { - const { getByTestId, getByRole } = render( + const { getByTestId } = render( , ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - expect(getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); expect(getByTestId('one')).to.have.attribute('aria-selected', 'true'); - fireEvent.keyDown(getByRole('tree'), { key: ' ' }); + fireEvent.keyDown(getByTestId('one'), { key: ' ' }); expect(getByTestId('one')).to.have.attribute('aria-selected', 'false'); }); }); describe('range selection', () => { - specify('keyboard arrow', () => { - const { getByRole, getByTestId, getByText, queryAllByRole } = render( - + it('keyboard arrow', () => { + const { getByTestId, queryAllByRole, getByText } = render( + @@ -1349,37 +1298,37 @@ describe('', () => { fireEvent.click(getByText('three')); act(() => { - getByRole('tree').focus(); + getByTestId('three').focus(); }); expect(getByTestId('three')).to.have.attribute('aria-selected', 'true'); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown', shiftKey: true }); + fireEvent.keyDown(getByTestId('three'), { key: 'ArrowDown', shiftKey: true }); - expect(getByTestId('four')).toHaveVirtualFocus(); + expect(getByTestId('four')).toHaveFocus(); expect(queryAllByRole('treeitem', { selected: true })).to.have.length(2); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown', shiftKey: true }); + fireEvent.keyDown(getByTestId('four'), { key: 'ArrowDown', shiftKey: true }); expect(getByTestId('three')).to.have.attribute('aria-selected', 'true'); expect(getByTestId('four')).to.have.attribute('aria-selected', 'true'); expect(getByTestId('five')).to.have.attribute('aria-selected', 'true'); expect(queryAllByRole('treeitem', { selected: true })).to.have.length(3); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowUp', shiftKey: true }); + fireEvent.keyDown(getByTestId('five'), { key: 'ArrowUp', shiftKey: true }); - expect(getByTestId('four')).toHaveVirtualFocus(); + expect(getByTestId('four')).toHaveFocus(); expect(queryAllByRole('treeitem', { selected: true })).to.have.length(2); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowUp', shiftKey: true }); + fireEvent.keyDown(getByTestId('four'), { key: 'ArrowUp', shiftKey: true }); expect(queryAllByRole('treeitem', { selected: true })).to.have.length(1); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowUp', shiftKey: true }); + fireEvent.keyDown(getByTestId('three'), { key: 'ArrowUp', shiftKey: true }); expect(queryAllByRole('treeitem', { selected: true })).to.have.length(2); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowUp', shiftKey: true }); + fireEvent.keyDown(getByTestId('two'), { key: 'ArrowUp', shiftKey: true }); expect(getByTestId('one')).to.have.attribute('aria-selected', 'true'); expect(getByTestId('two')).to.have.attribute('aria-selected', 'true'); @@ -1389,9 +1338,9 @@ describe('', () => { expect(queryAllByRole('treeitem', { selected: true })).to.have.length(3); }); - specify('keyboard arrow does not select when selectionDisabled', () => { - const { getByRole, getByTestId, queryAllByRole } = render( - + it('keyboard arrow does not select when selectionDisabled', () => { + const { getByTestId, queryAllByRole } = render( + @@ -1401,21 +1350,21 @@ describe('', () => { ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown', shiftKey: true }); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowDown', shiftKey: true }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); expect(queryAllByRole('treeitem', { selected: true })).to.have.length(0); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowUp', shiftKey: true }); + fireEvent.keyDown(getByTestId('two'), { key: 'ArrowUp', shiftKey: true }); expect(queryAllByRole('treeitem', { selected: true })).to.have.length(0); }); - specify('keyboard arrow merge', () => { - const { getByRole, getByTestId, getByText, queryAllByRole } = render( + it('keyboard arrow merge', () => { + const { getByTestId, getByText, queryAllByRole } = render( @@ -1428,28 +1377,28 @@ describe('', () => { fireEvent.click(getByText('three')); act(() => { - getByRole('tree').focus(); + getByTestId('three').focus(); }); expect(getByTestId('three')).to.have.attribute('aria-selected', 'true'); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowUp', shiftKey: true }); + fireEvent.keyDown(getByTestId('three'), { key: 'ArrowUp', shiftKey: true }); fireEvent.click(getByText('six'), { ctrlKey: true }); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowUp', shiftKey: true }); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowUp', shiftKey: true }); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowUp', shiftKey: true }); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowUp', shiftKey: true }); + fireEvent.keyDown(getByTestId('six'), { key: 'ArrowUp', shiftKey: true }); + fireEvent.keyDown(getByTestId('five'), { key: 'ArrowUp', shiftKey: true }); + fireEvent.keyDown(getByTestId('four'), { key: 'ArrowUp', shiftKey: true }); + fireEvent.keyDown(getByTestId('three'), { key: 'ArrowUp', shiftKey: true }); expect(queryAllByRole('treeitem', { selected: true })).to.have.length(5); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown', shiftKey: true }); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown', shiftKey: true }); + fireEvent.keyDown(getByTestId('two'), { key: 'ArrowDown', shiftKey: true }); + fireEvent.keyDown(getByTestId('three'), { key: 'ArrowDown', shiftKey: true }); expect(queryAllByRole('treeitem', { selected: true })).to.have.length(3); }); - specify('keyboard space', () => { - const { getByRole, getByTestId, getByText } = render( + it('keyboard space', () => { + const { getByTestId, getByText } = render( @@ -1464,26 +1413,36 @@ describe('', () => { , ); - const tree = getByRole('tree'); fireEvent.click(getByText('five')); act(() => { - tree.focus(); + getByTestId('five').focus(); }); - for (let i = 0; i < 5; i += 1) { - fireEvent.keyDown(tree, { key: 'ArrowDown' }); - } - fireEvent.keyDown(tree, { key: ' ', shiftKey: true }); + + fireEvent.keyDown(getByTestId('five'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByTestId('six'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByTestId('seven'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByTestId('eight'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByTestId('nine'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByTestId('nine'), { key: ' ', shiftKey: true }); expect(getByTestId('five')).to.have.attribute('aria-selected', 'true'); expect(getByTestId('six')).to.have.attribute('aria-selected', 'true'); expect(getByTestId('seven')).to.have.attribute('aria-selected', 'true'); expect(getByTestId('eight')).to.have.attribute('aria-selected', 'true'); expect(getByTestId('nine')).to.have.attribute('aria-selected', 'true'); - for (let i = 0; i < 9; i += 1) { - fireEvent.keyDown(tree, { key: 'ArrowUp' }); - } - fireEvent.keyDown(tree, { key: ' ', shiftKey: true }); + + fireEvent.keyDown(getByTestId('nine'), { key: 'ArrowUp' }); + fireEvent.keyDown(getByTestId('eight'), { key: 'ArrowUp' }); + fireEvent.keyDown(getByTestId('seven'), { key: 'ArrowUp' }); + fireEvent.keyDown(getByTestId('six'), { key: 'ArrowUp' }); + fireEvent.keyDown(getByTestId('five'), { key: 'ArrowUp' }); + fireEvent.keyDown(getByTestId('four'), { key: 'ArrowUp' }); + fireEvent.keyDown(getByTestId('three'), { key: 'ArrowUp' }); + fireEvent.keyDown(getByTestId('two'), { key: 'ArrowUp' }); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowUp' }); + + fireEvent.keyDown(getByTestId('one'), { key: ' ', shiftKey: true }); expect(getByTestId('one')).to.have.attribute('aria-selected', 'true'); expect(getByTestId('two')).to.have.attribute('aria-selected', 'true'); expect(getByTestId('three')).to.have.attribute('aria-selected', 'true'); @@ -1495,8 +1454,8 @@ describe('', () => { expect(getByTestId('nine')).to.have.attribute('aria-selected', 'false'); }); - specify('keyboard home and end', () => { - const { getByRole, getByTestId } = render( + it('keyboard home and end', () => { + const { getByTestId } = render( @@ -1516,7 +1475,7 @@ describe('', () => { getByTestId('five').focus(); }); - fireEvent.keyDown(getByRole('tree'), { + fireEvent.keyDown(getByTestId('five'), { key: 'End', shiftKey: true, ctrlKey: true, @@ -1528,7 +1487,7 @@ describe('', () => { expect(getByTestId('eight')).to.have.attribute('aria-selected', 'true'); expect(getByTestId('nine')).to.have.attribute('aria-selected', 'true'); - fireEvent.keyDown(getByRole('tree'), { + fireEvent.keyDown(getByTestId('nine'), { key: 'Home', shiftKey: true, ctrlKey: true, @@ -1545,8 +1504,8 @@ describe('', () => { expect(getByTestId('nine')).to.have.attribute('aria-selected', 'false'); }); - specify('keyboard home and end do not select when selectionDisabled', () => { - const { getByRole, getByText, queryAllByRole } = render( + it('keyboard home and end do not select when selectionDisabled', () => { + const { getByTestId, getByText, queryAllByRole } = render( @@ -1563,12 +1522,10 @@ describe('', () => { ); fireEvent.click(getByText('five')); - fireEvent.click(getByText('five')); - // Focus node five act(() => { - getByRole('tree').focus(); + getByTestId('five').focus(); }); - fireEvent.keyDown(getByRole('tree'), { + fireEvent.keyDown(getByTestId('five'), { key: 'End', shiftKey: true, ctrlKey: true, @@ -1576,7 +1533,7 @@ describe('', () => { expect(queryAllByRole('treeitem', { selected: true })).to.have.length(0); - fireEvent.keyDown(getByRole('tree'), { + fireEvent.keyDown(getByTestId('nine'), { key: 'Home', shiftKey: true, ctrlKey: true, @@ -1585,7 +1542,7 @@ describe('', () => { expect(queryAllByRole('treeitem', { selected: true })).to.have.length(0); }); - specify('mouse', () => { + it('mouse', () => { const { getByTestId, getByText } = render( @@ -1650,7 +1607,7 @@ describe('', () => { expect(getByTestId('five')).to.have.attribute('aria-selected', 'false'); }); - specify('mouse does not range select when selectionDisabled', () => { + it('mouse does not range select when selectionDisabled', () => { const { getByText, queryAllByRole } = render( @@ -1674,8 +1631,8 @@ describe('', () => { }); describe('multi selection', () => { - specify('keyboard', () => { - const { getByRole, getByTestId } = render( + it('keyboard', () => { + const { getByTestId } = render( @@ -1683,26 +1640,26 @@ describe('', () => { ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); expect(getByTestId('one')).to.have.attribute('aria-selected', 'false'); expect(getByTestId('two')).to.have.attribute('aria-selected', 'false'); - fireEvent.keyDown(getByRole('tree'), { key: ' ' }); + fireEvent.keyDown(getByTestId('one'), { key: ' ' }); expect(getByTestId('one')).to.have.attribute('aria-selected', 'true'); expect(getByTestId('two')).to.have.attribute('aria-selected', 'false'); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown' }); - fireEvent.keyDown(getByRole('tree'), { key: ' ' }); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByTestId('two'), { key: ' ' }); expect(getByTestId('one')).to.have.attribute('aria-selected', 'true'); expect(getByTestId('two')).to.have.attribute('aria-selected', 'true'); }); - specify('keyboard holding ctrl', () => { - const { getByRole, getByTestId } = render( + it('keyboard holding ctrl', () => { + const { getByTestId } = render( @@ -1710,25 +1667,25 @@ describe('', () => { ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); expect(getByTestId('one')).to.have.attribute('aria-selected', 'false'); expect(getByTestId('two')).to.have.attribute('aria-selected', 'false'); - fireEvent.keyDown(getByRole('tree'), { key: ' ' }); + fireEvent.keyDown(getByTestId('one'), { key: ' ' }); expect(getByTestId('one')).to.have.attribute('aria-selected', 'true'); expect(getByTestId('two')).to.have.attribute('aria-selected', 'false'); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown' }); - fireEvent.keyDown(getByRole('tree'), { key: ' ', ctrlKey: true }); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByTestId('two'), { key: ' ', ctrlKey: true }); expect(getByTestId('one')).to.have.attribute('aria-selected', 'true'); expect(getByTestId('two')).to.have.attribute('aria-selected', 'true'); }); - specify('mouse', () => { + it('mouse', () => { const { getByText, getByTestId } = render( @@ -1750,7 +1707,7 @@ describe('', () => { expect(getByTestId('two')).to.have.attribute('aria-selected', 'true'); }); - specify('mouse using ctrl', () => { + it('mouse using ctrl', () => { const { getByTestId, getByText } = render( @@ -1768,7 +1725,7 @@ describe('', () => { expect(getByTestId('two')).to.have.attribute('aria-selected', 'true'); }); - specify('mouse using meta', () => { + it('mouse using meta', () => { const { getByTestId, getByText } = render( @@ -1787,8 +1744,8 @@ describe('', () => { }); }); - specify('ctrl + a selects all', () => { - const { getByRole, queryAllByRole } = render( + it('ctrl + a selects all', () => { + const { getByTestId, queryAllByRole } = render( @@ -1799,15 +1756,15 @@ describe('', () => { ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - fireEvent.keyDown(getByRole('tree'), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(getByTestId('one'), { key: 'a', ctrlKey: true }); expect(queryAllByRole('treeitem', { selected: true })).to.have.length(5); }); - specify('ctrl + a does not select all when disableSelection', () => { - const { getByRole, queryAllByRole } = render( + it('ctrl + a does not select all when disableSelection', () => { + const { getByTestId, queryAllByRole } = render( @@ -1818,9 +1775,9 @@ describe('', () => { ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - fireEvent.keyDown(getByRole('tree'), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(getByTestId('one'), { key: 'a', ctrlKey: true }); expect(queryAllByRole('treeitem', { selected: true })).to.have.length(0); }); @@ -1899,7 +1856,7 @@ describe('', () => { describe('keyboard', () => { describe('`disabledItemsFocusable={true}`', () => { it('should prevent selection by keyboard', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( , @@ -1908,13 +1865,13 @@ describe('', () => { act(() => { getByTestId('one').focus(); }); - expect(getByTestId('one')).toHaveVirtualFocus(); - fireEvent.keyDown(getByRole('tree'), { key: ' ' }); + expect(getByTestId('one')).toHaveFocus(); + fireEvent.keyDown(getByTestId('one'), { key: ' ' }); expect(getByTestId('one')).not.to.have.attribute('aria-selected'); }); it('should not prevent next node being range selected by keyboard', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( @@ -1926,15 +1883,15 @@ describe('', () => { act(() => { getByTestId('one').focus(); }); - expect(getByTestId('one')).toHaveVirtualFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown', shiftKey: true }); + expect(getByTestId('one')).toHaveFocus(); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowDown', shiftKey: true }); expect(getByTestId('one')).to.have.attribute('aria-selected', 'false'); expect(getByTestId('two')).to.have.attribute('aria-selected', 'true'); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); }); it('should prevent range selection by keyboard + arrow down', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( @@ -1944,17 +1901,17 @@ describe('', () => { act(() => { getByTestId('one').focus(); }); - expect(getByTestId('one')).toHaveVirtualFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown', shiftKey: true }); + expect(getByTestId('one')).toHaveFocus(); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowDown', shiftKey: true }); expect(getByTestId('one')).to.have.attribute('aria-selected', 'false'); expect(getByTestId('two')).to.have.attribute('aria-selected', 'false'); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); }); }); - describe('`disabledItemsFocusable=false`', () => { + describe('`disabledItemsFocusable={false}`', () => { it('should select the next non disabled node by keyboard + arrow down', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( @@ -1965,11 +1922,11 @@ describe('', () => { act(() => { getByTestId('one').focus(); }); - expect(getByTestId('one')).toHaveVirtualFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown', shiftKey: true }); + expect(getByTestId('one')).toHaveFocus(); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowDown', shiftKey: true }); expect(getByTestId('one')).to.have.attribute('aria-selected', 'false'); expect(getByTestId('two')).to.have.attribute('aria-selected', 'false'); - expect(getByTestId('three')).toHaveVirtualFocus(); + expect(getByTestId('three')).toHaveFocus(); expect(getByTestId('one')).to.have.attribute('aria-selected', 'false'); expect(getByTestId('two')).to.have.attribute('aria-selected', 'false'); expect(getByTestId('three')).to.have.attribute('aria-selected', 'true'); @@ -1977,7 +1934,7 @@ describe('', () => { }); it('should prevent range selection by keyboard + space', () => { - const { getByRole, getByTestId, getByText } = render( + const { getByTestId, getByText } = render( @@ -1986,16 +1943,17 @@ describe('', () => { , ); - const tree = getByRole('tree'); fireEvent.click(getByText('one')); act(() => { - tree.focus(); + getByTestId('one').focus(); }); - for (let i = 0; i < 5; i += 1) { - fireEvent.keyDown(tree, { key: 'ArrowDown' }); - } - fireEvent.keyDown(tree, { key: ' ', shiftKey: true }); + + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByTestId('two'), { key: 'ArrowDown' }); + fireEvent.keyDown(getByTestId('four'), { key: 'ArrowDown' }); + + fireEvent.keyDown(getByTestId('five'), { key: ' ', shiftKey: true }); expect(getByTestId('one')).to.have.attribute('aria-selected', 'true'); expect(getByTestId('two')).to.have.attribute('aria-selected', 'true'); @@ -2005,7 +1963,7 @@ describe('', () => { }); it('should prevent selection by ctrl + a', () => { - const { getByRole, queryAllByRole } = render( + const { getByTestId, queryAllByRole } = render( @@ -2016,15 +1974,15 @@ describe('', () => { ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - fireEvent.keyDown(getByRole('tree'), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(getByTestId('one'), { key: 'a', ctrlKey: true }); expect(queryAllByRole('treeitem', { selected: true })).to.have.length(4); }); it('should prevent selection by keyboard end', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( @@ -2035,10 +1993,10 @@ describe('', () => { ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - expect(getByTestId('one')).toHaveVirtualFocus(); - fireEvent.keyDown(getByRole('tree'), { + expect(getByTestId('one')).toHaveFocus(); + fireEvent.keyDown(getByTestId('one'), { key: 'End', shiftKey: true, ctrlKey: true, @@ -2052,7 +2010,7 @@ describe('', () => { }); it('should prevent selection by keyboard home', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( @@ -2065,8 +2023,8 @@ describe('', () => { act(() => { getByTestId('five').focus(); }); - expect(getByTestId('five')).toHaveVirtualFocus(); - fireEvent.keyDown(getByRole('tree'), { + expect(getByTestId('five')).toHaveFocus(); + fireEvent.keyDown(getByTestId('five'), { key: 'Home', shiftKey: true, ctrlKey: true, @@ -2084,16 +2042,16 @@ describe('', () => { describe('focus', () => { describe('`disabledItemsFocusable={true}`', () => { it('should prevent focus by mouse', () => { - const focusSpy = spy(); + const onItemFocus = spy(); const { getByText } = render( - + , ); fireEvent.click(getByText('two')); - expect(focusSpy.callCount).to.equal(0); + expect(onItemFocus.callCount).to.equal(0); }); it('should not prevent programmatic focus', () => { @@ -2107,11 +2065,11 @@ describe('', () => { act(() => { getByTestId('one').focus(); }); - expect(getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); }); it('should not prevent focus by type-ahead', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( @@ -2119,15 +2077,15 @@ describe('', () => { ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - expect(getByTestId('one')).toHaveVirtualFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 't' }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); + fireEvent.keyDown(getByTestId('one'), { key: 't' }); + expect(getByTestId('two')).toHaveFocus(); }); it('should not prevent focus by arrow keys', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( @@ -2135,61 +2093,52 @@ describe('', () => { ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - expect(getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown' }); - expect(getByTestId('two')).toHaveVirtualFocus(); - }); - - it('should be focused on tree focus', () => { - const { getByRole, getByTestId } = render( - - - - , - ); - - act(() => { - getByRole('tree').focus(); - }); - - expect(getByTestId('one')).toHaveVirtualFocus(); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowDown' }); + expect(getByTestId('two')).toHaveFocus(); }); }); describe('`disabledItemsFocusable=false`', () => { it('should prevent focus by mouse', () => { - const focusSpy = spy(); + const onItemFocus = spy(); const { getByText } = render( - + , ); fireEvent.click(getByText('two')); - expect(focusSpy.callCount).to.equal(0); + expect(onItemFocus.callCount).to.equal(0); }); - it('should prevent programmatic focus', () => { - const { getByTestId } = render( + it('should prevent focus when clicking', () => { + const handleMouseDown = spy(); + + const { getByText } = render( - + , ); - act(() => { - getByTestId('one').focus(); - }); - expect(getByTestId('one')).not.toHaveVirtualFocus(); + fireEvent.mouseDown(getByText('one')); + expect(handleMouseDown.lastCall.firstArg.defaultPrevented).to.equal(true); }); it('should prevent focus by type-ahead', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( @@ -2197,15 +2146,15 @@ describe('', () => { ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - expect(getByTestId('one')).toHaveVirtualFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 't' }); - expect(getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); + fireEvent.keyDown(getByTestId('one'), { key: 't' }); + expect(getByTestId('one')).toHaveFocus(); }); it('should be skipped on navigation with arrow keys', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( @@ -2214,36 +2163,33 @@ describe('', () => { ); act(() => { - getByRole('tree').focus(); + getByTestId('one').focus(); }); - expect(getByTestId('one')).toHaveVirtualFocus(); + expect(getByTestId('one')).toHaveFocus(); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowDown' }); - expect(getByTestId('three')).toHaveVirtualFocus(); + fireEvent.keyDown(getByTestId('one'), { key: 'ArrowDown' }); + expect(getByTestId('three')).toHaveFocus(); }); - it('should not be focused on tree focus', () => { - const { getByRole, getByTestId } = render( + it('should set tabIndex={-1} and tabIndex={0} on next item', () => { + const { getByTestId } = render( , ); - act(() => { - getByRole('tree').focus(); - }); - - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('one').tabIndex).to.equal(-1); + expect(getByTestId('two').tabIndex).to.equal(0); }); }); }); describe('expansion', () => { describe('`disabledItemsFocusable={true}`', () => { - it('should prevent expansion on enter', () => { - const { getByRole, getByTestId } = render( + it('should prevent expansion on Enter', () => { + const { getByTestId } = render( @@ -2255,14 +2201,14 @@ describe('', () => { act(() => { getByTestId('two').focus(); }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); expect(getByTestId('two')).to.have.attribute('aria-expanded', 'false'); - fireEvent.keyDown(getByRole('tree'), { key: 'Enter' }); + fireEvent.keyDown(getByTestId('two'), { key: 'Enter' }); expect(getByTestId('two')).to.have.attribute('aria-expanded', 'false'); }); it('should prevent expansion on right arrow', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( @@ -2274,14 +2220,14 @@ describe('', () => { act(() => { getByTestId('two').focus(); }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); expect(getByTestId('two')).to.have.attribute('aria-expanded', 'false'); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowRight' }); + fireEvent.keyDown(getByTestId('two'), { key: 'ArrowRight' }); expect(getByTestId('two')).to.have.attribute('aria-expanded', 'false'); }); it('should prevent collapse on left arrow', () => { - const { getByRole, getByTestId } = render( + const { getByTestId } = render( @@ -2293,9 +2239,9 @@ describe('', () => { act(() => { getByTestId('two').focus(); }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); expect(getByTestId('two')).to.have.attribute('aria-expanded', 'true'); - fireEvent.keyDown(getByRole('tree'), { key: 'ArrowLeft' }); + fireEvent.keyDown(getByTestId('two'), { key: 'ArrowLeft' }); expect(getByTestId('two')).to.have.attribute('aria-expanded', 'true'); }); }); @@ -2418,7 +2364,7 @@ describe('', () => { const { getByText, getByTestId, getByRole } = render( - + @@ -2427,10 +2373,10 @@ describe('', () => { fireEvent.click(getByText('two')); act(() => { - getByRole('tree').focus(); + getByTestId('two').focus(); }); - expect(getByTestId('two')).toHaveVirtualFocus(); + expect(getByTestId('two')).toHaveFocus(); act(() => { getByRole('button').focus(); diff --git a/packages/x-tree-view/src/TreeItem/TreeItem.tsx b/packages/x-tree-view/src/TreeItem/TreeItem.tsx index 17450737dbe01..bfa635fd4f3c1 100644 --- a/packages/x-tree-view/src/TreeItem/TreeItem.tsx +++ b/packages/x-tree-view/src/TreeItem/TreeItem.tsx @@ -176,6 +176,9 @@ export const TreeItem = React.forwardRef(function TreeItem( label, onClick, onMouseDown, + onFocus, + onBlur, + onKeyDown, ...other } = props; @@ -287,18 +290,24 @@ export const TreeItem = React.forwardRef(function TreeItem( } function handleFocus(event: React.FocusEvent) { - // DOM focus stays on the tree which manages focus with aria-activedescendant - if (event.target === event.currentTarget) { - instance.focusRoot(); - } - const canBeFocused = !disabled || disabledItemsFocusable; if (!focused && canBeFocused && event.currentTarget === event.target) { instance.focusItem(event, itemId); } } + function handleBlur(event: React.FocusEvent) { + onBlur?.(event); + instance.removeFocusedItem(); + } + + const handleKeyDown = (event: React.KeyboardEvent) => { + onKeyDown?.(event); + instance.handleItemKeyDown(event, itemId); + }; + const idAttribute = instance.getTreeItemId(itemId, id); + const tabIndex = instance.canItemBeTabbed(itemId) ? 0 : -1; return ( @@ -309,10 +318,12 @@ export const TreeItem = React.forwardRef(function TreeItem( aria-selected={ariaSelected} aria-disabled={disabled || undefined} id={idAttribute} - tabIndex={-1} + tabIndex={tabIndex} {...other} ownerState={ownerState} onFocus={handleFocus} + onBlur={handleBlur} + onKeyDown={handleKeyDown} ref={handleRootRef} > }, ); - const expandAllSiblings = (event: React.KeyboardEvent, itemId: string) => { + const expandAllSiblings = (event: React.KeyboardEvent, itemId: string) => { const node = instance.getNode(itemId); const siblings = instance.getChildrenIds(node.parentId); diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.ts index 54fff61870b76..1a66b9dd86fd0 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.ts @@ -6,7 +6,7 @@ export interface UseTreeViewExpansionInstance { isNodeExpanded: (itemId: string) => boolean; isNodeExpandable: (itemId: string) => boolean; toggleNodeExpansion: (event: React.SyntheticEvent, value: string) => void; - expandAllSiblings: (event: React.KeyboardEvent, itemId: string) => void; + expandAllSiblings: (event: React.KeyboardEvent, itemId: string) => void; } export interface UseTreeViewExpansionParameters { diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.ts index e0aa4229e38d2..68d3337ec9378 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.ts @@ -2,12 +2,35 @@ import * as React from 'react'; import useEventCallback from '@mui/utils/useEventCallback'; import { EventHandlers } from '@mui/base/utils'; import ownerDocument from '@mui/utils/ownerDocument'; -import { TreeViewPlugin } from '../../models'; +import { TreeViewPlugin, TreeViewUsedInstance } from '../../models'; import { populateInstance, populatePublicAPI } from '../../useTreeView/useTreeView.utils'; import { UseTreeViewFocusSignature } from './useTreeViewFocus.types'; import { useInstanceEventHandler } from '../../hooks/useInstanceEventHandler'; import { getActiveElement } from '../../utils/utils'; +const useTabbableItemId = ( + instance: TreeViewUsedInstance, + selectedItems: string | string[] | null, +) => { + const isItemVisible = (itemId: string) => { + const node = instance.getNode(itemId); + return node && (node.parentId == null || instance.isNodeExpanded(node.parentId)); + }; + + let tabbableItemId: string | null | undefined; + if (Array.isArray(selectedItems)) { + tabbableItemId = selectedItems.find(isItemVisible); + } else if (selectedItems != null && isItemVisible(selectedItems)) { + tabbableItemId = selectedItems; + } + + if (tabbableItemId == null) { + tabbableItemId = instance.getNavigableChildrenIds(null)[0]; + } + + return tabbableItemId; +}; + export const useTreeViewFocus: TreeViewPlugin = ({ instance, publicAPI, @@ -17,6 +40,8 @@ export const useTreeViewFocus: TreeViewPlugin = ({ models, rootRef, }) => { + const tabbableItemId = useTabbableItemId(instance, models.selectedItems.value); + const setFocusedItemId = useEventCallback((itemId: React.SetStateAction) => { const cleanItemId = typeof itemId === 'function' ? itemId(state.focusedNodeId) : itemId; if (state.focusedNodeId !== cleanItemId) { @@ -25,7 +50,9 @@ export const useTreeViewFocus: TreeViewPlugin = ({ }); const isTreeViewFocused = React.useCallback( - () => !!rootRef.current && rootRef.current === getActiveElement(ownerDocument(rootRef.current)), + () => + !!rootRef.current && + rootRef.current.contains(getActiveElement(ownerDocument(rootRef.current))), [rootRef], ); @@ -39,20 +66,27 @@ export const useTreeViewFocus: TreeViewPlugin = ({ return node && (node.parentId == null || instance.isNodeExpanded(node.parentId)); }; - const focusItem = useEventCallback((event: React.SyntheticEvent, itemId: string | null) => { - // if we receive an itemId, and it is visible, the focus will be set to it - if (itemId && isNodeVisible(itemId)) { - if (!isTreeViewFocused()) { - instance.focusRoot(); - } - setFocusedItemId(itemId); - if (params.onItemFocus) { - params.onItemFocus(event, itemId); - } + const innerFocusItem = (event: React.SyntheticEvent | null, itemId: string) => { + const node = instance.getNode(itemId); + const itemElement = document.getElementById(instance.getTreeItemId(itemId, node.idAttribute)); + if (itemElement) { + itemElement.focus(); + } + + setFocusedItemId(itemId); + if (params.onItemFocus) { + params.onItemFocus(event, itemId); + } + }; + + const focusItem = useEventCallback((event: React.SyntheticEvent, nodeId: string) => { + // If we receive a nodeId, and it is visible, the focus will be set to it + if (isNodeVisible(nodeId)) { + innerFocusItem(event, nodeId); } }); - const focusDefaultNode = useEventCallback((event: React.SyntheticEvent) => { + const focusDefaultNode = useEventCallback((event: React.SyntheticEvent | null) => { let nodeToFocusId: string | null | undefined; if (Array.isArray(models.selectedItems.value)) { nodeToFocusId = models.selectedItems.value.find(isNodeVisible); @@ -64,21 +98,33 @@ export const useTreeViewFocus: TreeViewPlugin = ({ nodeToFocusId = instance.getNavigableChildrenIds(null)[0]; } - setFocusedItemId(nodeToFocusId); - if (params.onItemFocus) { - params.onItemFocus(event, nodeToFocusId); - } + innerFocusItem(event, nodeToFocusId); }); - const focusRoot = useEventCallback(() => { - rootRef.current?.focus({ preventScroll: true }); + const removeFocusedItem = useEventCallback(() => { + if (state.focusedNodeId == null) { + return; + } + + const node = instance.getNode(state.focusedNodeId); + const itemElement = document.getElementById( + instance.getTreeItemId(state.focusedNodeId, node.idAttribute), + ); + if (itemElement) { + itemElement.blur(); + } + + setFocusedItemId(null); }); + const canItemBeTabbed = (itemId: string) => itemId === tabbableItemId; + populateInstance(instance, { isNodeFocused, + canItemBeTabbed, focusItem, - focusRoot, focusDefaultNode, + removeFocusedItem, }); populatePublicAPI(publicAPI, { @@ -86,15 +132,9 @@ export const useTreeViewFocus: TreeViewPlugin = ({ }); useInstanceEventHandler(instance, 'removeNode', ({ id }) => { - setFocusedItemId((oldFocusedItemId) => { - if ( - oldFocusedItemId === id && - rootRef.current === ownerDocument(rootRef.current).activeElement - ) { - return instance.getChildrenIds(null)[0]; - } - return oldFocusedItemId; - }); + if (state.focusedNodeId === id) { + instance.focusDefaultNode(null); + } }); const createHandleFocus = @@ -106,12 +146,6 @@ export const useTreeViewFocus: TreeViewPlugin = ({ } }; - const createHandleBlur = - (otherHandlers: EventHandlers) => (event: React.FocusEvent) => { - otherHandlers.onBlur?.(event); - setFocusedItemId(null); - }; - const focusedNode = instance.getNode(state.focusedNodeId!); const activeDescendant = focusedNode ? instance.getTreeItemId(focusedNode.id, focusedNode.idAttribute) @@ -120,7 +154,6 @@ export const useTreeViewFocus: TreeViewPlugin = ({ return { getRootProps: (otherHandlers) => ({ onFocus: createHandleFocus(otherHandlers), - onBlur: createHandleBlur(otherHandlers), 'aria-activedescendant': activeDescendant ?? undefined, }), }; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.ts index f6013ad19ab7c..2b9fe5f6f1e66 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.ts @@ -7,9 +7,10 @@ import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion'; export interface UseTreeViewFocusInstance { isNodeFocused: (itemId: string) => boolean; - focusItem: (event: React.SyntheticEvent, itemId: string | null) => void; - focusDefaultNode: (event: React.SyntheticEvent) => void; - focusRoot: () => void; + canItemBeTabbed: (itemId: string) => boolean; + focusItem: (event: React.SyntheticEvent, nodeId: string) => void; + focusDefaultNode: (event: React.SyntheticEvent | null) => void; + removeFocusedItem: () => void; } export interface UseTreeViewFocusPublicAPI extends Pick {} @@ -21,7 +22,7 @@ export interface UseTreeViewFocusParameters { * @param {string} itemId The id of the focused item. * @param {string} value of the focused item. */ - onItemFocus?: (event: React.SyntheticEvent, itemId: string) => void; + onItemFocus?: (event: React.SyntheticEvent | null, itemId: string) => void; } export type UseTreeViewFocusDefaultizedParameters = UseTreeViewFocusParameters; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts index f5abd106a5761..762573b4dada2 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts @@ -1,6 +1,5 @@ import * as React from 'react'; import { useTheme } from '@mui/material/styles'; -import { EventHandlers } from '@mui/base/utils'; import useEventCallback from '@mui/utils/useEventCallback'; import { TreeViewPlugin } from '../../models'; import { @@ -32,7 +31,7 @@ function findNextFirstChar(firstChars: string[], startIndex: number, char: strin export const useTreeViewKeyboardNavigation: TreeViewPlugin< UseTreeViewKeyboardNavigationSignature -> = ({ instance, params, state }) => { +> = ({ instance, params }) => { const theme = useTheme(); const isRTL = theme.direction === 'rtl'; const firstCharMap = React.useRef({}); @@ -63,10 +62,6 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< firstCharMap.current = newFirstCharMap; }, [params.items, params.getItemId, instance]); - populateInstance(instance, { - updateFirstCharMap, - }); - const getFirstMatchingItem = (itemId: string, firstChar: string) => { let start: number; let index: number; @@ -118,209 +113,200 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< }; // ARIA specification: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/#keyboardinteraction - const createHandleKeyDown = - (otherHandlers: EventHandlers) => - (event: React.KeyboardEvent & MuiCancellableEvent) => { - otherHandlers.onKeyDown?.(event); + const handleItemKeyDown = ( + event: React.KeyboardEvent & MuiCancellableEvent, + itemId: string, + ) => { + if (event.defaultMuiPrevented) { + return; + } - if (event.defaultMuiPrevented) { - return; - } + if (event.altKey || event.currentTarget !== event.target) { + return; + } - // If the tree is empty, there will be no focused node - if (event.altKey || event.currentTarget !== event.target || state.focusedNodeId == null) { - return; + const ctrlPressed = event.ctrlKey || event.metaKey; + const key = event.key; + + // eslint-disable-next-line default-case + switch (true) { + // Select the node when pressing "Space" + case key === ' ' && canToggleItemSelection(itemId): { + event.preventDefault(); + if (params.multiSelect && event.shiftKey) { + instance.selectRange(event, { end: itemId }); + } else if (params.multiSelect) { + instance.selectNode(event, itemId, true); + } else { + instance.selectNode(event, itemId); + } + break; } - const ctrlPressed = event.ctrlKey || event.metaKey; - const key = event.key; - - // eslint-disable-next-line default-case - switch (true) { - // Select the node when pressing "Space" - case key === ' ' && canToggleItemSelection(state.focusedNodeId): { + // If the focused node has children, we expand it. + // If the focused node has no children, we select it. + case key === 'Enter': { + if (canToggleItemExpansion(itemId)) { + instance.toggleNodeExpansion(event, itemId); event.preventDefault(); - if (params.multiSelect && event.shiftKey) { - instance.selectRange(event, { end: state.focusedNodeId }); - } else if (params.multiSelect) { - instance.selectNode(event, state.focusedNodeId, true); - } else { - instance.selectNode(event, state.focusedNodeId); + } else if (canToggleItemSelection(itemId)) { + if (params.multiSelect) { + event.preventDefault(); + instance.selectNode(event, itemId, true); + } else if (!instance.isNodeSelected(itemId)) { + instance.selectNode(event, itemId); + event.preventDefault(); } - break; } - // If the focused node has children, we expand it. - // If the focused node has no children, we select it. - case key === 'Enter': { - if (canToggleItemExpansion(state.focusedNodeId)) { - instance.toggleNodeExpansion(event, state.focusedNodeId); - event.preventDefault(); - } else if (canToggleItemSelection(state.focusedNodeId)) { - if (params.multiSelect) { - event.preventDefault(); - instance.selectNode(event, state.focusedNodeId, true); - } else if (!instance.isNodeSelected(state.focusedNodeId)) { - instance.selectNode(event, state.focusedNodeId); - event.preventDefault(); - } - } + break; + } - break; + // Focus the next focusable item + case key === 'ArrowDown': { + const nextItem = getNextNode(instance, itemId); + if (nextItem) { + event.preventDefault(); + instance.focusItem(event, nextItem); + + // Multi select behavior when pressing Shift + ArrowDown + // Toggles the selection state of the next item + if (params.multiSelect && event.shiftKey && canToggleItemSelection(nextItem)) { + instance.selectRange( + event, + { + end: nextItem, + current: itemId, + }, + true, + ); + } } - // Focus the next focusable item - case key === 'ArrowDown': { - const nextItem = getNextNode(instance, state.focusedNodeId); - if (nextItem) { - event.preventDefault(); - instance.focusItem(event, nextItem); - - // Multi select behavior when pressing Shift + ArrowDown - // Toggles the selection state of the next item - if (params.multiSelect && event.shiftKey && canToggleItemSelection(nextItem)) { - instance.selectRange( - event, - { - end: nextItem, - current: state.focusedNodeId, - }, - true, - ); - } - } + break; + } - break; + // Focuses the previous focusable item + case key === 'ArrowUp': { + const previousItem = getPreviousNode(instance, itemId); + if (previousItem) { + event.preventDefault(); + instance.focusItem(event, previousItem); + + // Multi select behavior when pressing Shift + ArrowUp + // Toggles the selection state of the previous item + if (params.multiSelect && event.shiftKey && canToggleItemSelection(previousItem)) { + instance.selectRange( + event, + { + end: previousItem, + current: itemId, + }, + true, + ); + } } - // Focuses the previous focusable item - case key === 'ArrowUp': { - const previousItem = getPreviousNode(instance, state.focusedNodeId); - if (previousItem) { + break; + } + + // If the focused item is expanded, we move the focus to its first child + // If the focused item is collapsed and has children, we expand it + case (key === 'ArrowRight' && !isRTL) || (key === 'ArrowLeft' && isRTL): { + if (instance.isNodeExpanded(itemId)) { + const nextNodeId = getNextNode(instance, itemId); + if (nextNodeId) { + instance.focusItem(event, nextNodeId); event.preventDefault(); - instance.focusItem(event, previousItem); - - // Multi select behavior when pressing Shift + ArrowUp - // Toggles the selection state of the previous item - if (params.multiSelect && event.shiftKey && canToggleItemSelection(previousItem)) { - instance.selectRange( - event, - { - end: previousItem, - current: state.focusedNodeId, - }, - true, - ); - } } - - break; + } else if (canToggleItemExpansion(itemId)) { + instance.toggleNodeExpansion(event, itemId); + event.preventDefault(); } - // If the focused item is expanded, we move the focus to its first child - // If the focused item is collapsed and has children, we expand it - case (key === 'ArrowRight' && !isRTL) || (key === 'ArrowLeft' && isRTL): { - if (instance.isNodeExpanded(state.focusedNodeId)) { - instance.focusItem(event, getNextNode(instance, state.focusedNodeId)); - event.preventDefault(); - } else if (canToggleItemExpansion(state.focusedNodeId)) { - instance.toggleNodeExpansion(event, state.focusedNodeId); + break; + } + + // If the focused item is expanded, we collapse it + // If the focused item is collapsed and has a parent, we move the focus to this parent + case (key === 'ArrowLeft' && !isRTL) || (key === 'ArrowRight' && isRTL): { + if (canToggleItemExpansion(itemId) && instance.isNodeExpanded(itemId)) { + instance.toggleNodeExpansion(event, itemId); + event.preventDefault(); + } else { + const parent = instance.getNode(itemId).parentId; + if (parent) { + instance.focusItem(event, parent); event.preventDefault(); } - - break; } - // If the focused item is expanded, we collapse it - // If the focused item is collapsed and has a parent, we move the focus to this parent - case (key === 'ArrowLeft' && !isRTL) || (key === 'ArrowRight' && isRTL): { - if ( - canToggleItemExpansion(state.focusedNodeId) && - instance.isNodeExpanded(state.focusedNodeId) - ) { - instance.toggleNodeExpansion(event, state.focusedNodeId!); - event.preventDefault(); - } else { - const parent = instance.getNode(state.focusedNodeId).parentId; - if (parent) { - instance.focusItem(event, parent); - event.preventDefault(); - } - } + break; + } - break; + // Focuses the first node in the tree + case key === 'Home': { + instance.focusItem(event, getFirstNode(instance)); + + // Multi select behavior when pressing Ctrl + Shift + Home + // Selects the focused node and all nodes up to the first node. + if (canToggleItemSelection(itemId) && params.multiSelect && ctrlPressed && event.shiftKey) { + instance.rangeSelectToFirst(event, itemId); } - // Focuses the first node in the tree - case key === 'Home': { - instance.focusItem(event, getFirstNode(instance)); - - // Multi select behavior when pressing Ctrl + Shift + Home - // Selects the focused node and all nodes up to the first node. - if ( - canToggleItemSelection(state.focusedNodeId) && - params.multiSelect && - ctrlPressed && - event.shiftKey - ) { - instance.rangeSelectToFirst(event, state.focusedNodeId); - } + event.preventDefault(); + break; + } - event.preventDefault(); - break; + // Focuses the last item in the tree + case key === 'End': { + instance.focusItem(event, getLastNode(instance)); + + // Multi select behavior when pressing Ctrl + Shirt + End + // Selects the focused item and all the items down to the last item. + if (canToggleItemSelection(itemId) && params.multiSelect && ctrlPressed && event.shiftKey) { + instance.rangeSelectToLast(event, itemId); } - // Focuses the last item in the tree - case key === 'End': { - instance.focusItem(event, getLastNode(instance)); - - // Multi select behavior when pressing Ctrl + Shirt + End - // Selects the focused item and all the items down to the last item. - if ( - canToggleItemSelection(state.focusedNodeId) && - params.multiSelect && - ctrlPressed && - event.shiftKey - ) { - instance.rangeSelectToLast(event, state.focusedNodeId); - } + event.preventDefault(); + break; + } - event.preventDefault(); - break; - } + // Expand all siblings that are at the same level as the focused item + case key === '*': { + instance.expandAllSiblings(event, itemId); + event.preventDefault(); + break; + } - // Expand all siblings that are at the same level as the focused item - case key === '*': { - instance.expandAllSiblings(event, state.focusedNodeId); - event.preventDefault(); - break; - } + // Multi select behavior when pressing Ctrl + a + // Selects all the nodes + case key === 'a' && ctrlPressed && params.multiSelect && !params.disableSelection: { + instance.selectRange(event, { + start: getFirstNode(instance), + end: getLastNode(instance), + }); + event.preventDefault(); + break; + } - // Multi select behavior when pressing Ctrl + a - // Selects all the nodes - case key === 'a' && ctrlPressed && params.multiSelect && !params.disableSelection: { - instance.selectRange(event, { - start: getFirstNode(instance), - end: getLastNode(instance), - }); + // Type-ahead + // TODO: Support typing multiple characters + case !ctrlPressed && !event.shiftKey && isPrintableCharacter(key): { + const matchingNode = getFirstMatchingItem(itemId, key); + if (matchingNode != null) { + instance.focusItem(event, matchingNode); event.preventDefault(); - break; - } - - // Type-ahead - // TODO: Support typing multiple characters - case !ctrlPressed && !event.shiftKey && isPrintableCharacter(key): { - const matchingNode = getFirstMatchingItem(state.focusedNodeId, key); - if (matchingNode != null) { - instance.focusItem(event, matchingNode); - event.preventDefault(); - } - break; } + break; } - }; + } + }; - return { getRootProps: (otherHandlers) => ({ onKeyDown: createHandleKeyDown(otherHandlers) }) }; + populateInstance(instance, { + updateFirstCharMap, + handleItemKeyDown, + }); }; useTreeViewKeyboardNavigation.params = {}; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.ts index 9727caa58e113..729b8a875c47f 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.ts @@ -1,11 +1,17 @@ +import * as React from 'react'; import { TreeViewPluginSignature } from '../../models'; import { UseTreeViewNodesSignature } from '../useTreeViewNodes'; import { UseTreeViewSelectionSignature } from '../useTreeViewSelection'; import { UseTreeViewFocusSignature } from '../useTreeViewFocus'; import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion'; +import { MuiCancellableEvent } from '../../models/MuiCancellableEvent'; export interface UseTreeViewKeyboardNavigationInstance { updateFirstCharMap: (updater: (map: TreeViewFirstCharMap) => TreeViewFirstCharMap) => void; + handleItemKeyDown: ( + event: React.KeyboardEvent & MuiCancellableEvent, + itemId: string, + ) => void; } export type UseTreeViewKeyboardNavigationSignature = TreeViewPluginSignature<{ diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.ts index 15fa2ab2665b9..39a63d0281d8e 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.ts @@ -1,5 +1,4 @@ import * as React from 'react'; -import useEventCallback from '@mui/utils/useEventCallback'; import { TreeViewPlugin } from '../../models'; import { populateInstance, populatePublicAPI } from '../../useTreeView/useTreeView.utils'; import { @@ -138,11 +137,13 @@ export const useTreeViewNodes: TreeViewPlugin = ({ [instance], ); - const getChildrenIds = useEventCallback((itemId: string | null) => - Object.values(state.nodes.nodeMap) - .filter((item) => item.parentId === itemId) - .sort((a, b) => a.index - b.index) - .map((child) => child.id), + const getChildrenIds = React.useCallback( + (itemId: string | null) => + Object.values(state.nodes.nodeMap) + .filter((item) => item.parentId === itemId) + .sort((a, b) => a.index - b.index) + .map((child) => child.id), + [state.nodes.nodeMap], ); const getNavigableChildrenIds = (itemId: string | null) => { diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.ts index af9da8dffe5c7..c382816220cb2 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.ts @@ -161,7 +161,7 @@ export const useTreeViewSelection: TreeViewPlugin lastSelectionWasRange.current = true; }; - const rangeSelectToFirst = (event: React.KeyboardEvent, itemId: string) => { + const rangeSelectToFirst = (event: React.KeyboardEvent, itemId: string) => { if (!lastSelectedNode.current) { lastSelectedNode.current = itemId; } @@ -174,7 +174,7 @@ export const useTreeViewSelection: TreeViewPlugin }); }; - const rangeSelectToLast = (event: React.KeyboardEvent, itemId: string) => { + const rangeSelectToLast = (event: React.KeyboardEvent, itemId: string) => { if (!lastSelectedNode.current) { lastSelectedNode.current = itemId; } diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.ts index e14cf7c8666d6..37d6324909cee 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.ts @@ -7,8 +7,8 @@ export interface UseTreeViewSelectionInstance { isNodeSelected: (itemId: string) => boolean; selectNode: (event: React.SyntheticEvent, itemId: string, multiple?: boolean) => void; selectRange: (event: React.SyntheticEvent, nodes: TreeViewItemRange, stacked?: boolean) => void; - rangeSelectToFirst: (event: React.KeyboardEvent, itemId: string) => void; - rangeSelectToLast: (event: React.KeyboardEvent, itemId: string) => void; + rangeSelectToFirst: (event: React.KeyboardEvent, itemId: string) => void; + rangeSelectToLast: (event: React.KeyboardEvent, itemId: string) => void; } type TreeViewSelectionValue = Multiple extends true diff --git a/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts b/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts index b8e847672800c..17f7d3769f4af 100644 --- a/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts +++ b/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts @@ -155,7 +155,6 @@ export const useTreeView = { const rootProps: UseTreeViewRootSlotProps = { role: 'tree', - tabIndex: 0, ...otherHandlers, ref: handleRootRef, }; diff --git a/packages/x-tree-view/src/internals/utils/utils.ts b/packages/x-tree-view/src/internals/utils/utils.ts index 71dc45b2f144d..5401ae664aab2 100644 --- a/packages/x-tree-view/src/internals/utils/utils.ts +++ b/packages/x-tree-view/src/internals/utils/utils.ts @@ -1,3 +1,4 @@ +// https://www.abeautifulsite.net/posts/finding-the-active-element-in-a-shadow-root/ export const getActiveElement = (root: Document | ShadowRoot = document): Element | null => { const activeEl = root.activeElement; diff --git a/packages/x-tree-view/src/useTreeItem2/useTreeItem2.ts b/packages/x-tree-view/src/useTreeItem2/useTreeItem2.ts index a23ecf339d03e..fcfcb93a19f0e 100644 --- a/packages/x-tree-view/src/useTreeItem2/useTreeItem2.ts +++ b/packages/x-tree-view/src/useTreeItem2/useTreeItem2.ts @@ -34,24 +34,44 @@ export const useTreeItem2 = (event: React.FocusEvent & MuiCancellableEvent) => { + (otherHandlers: EventHandlers) => + (event: React.FocusEvent & MuiCancellableEvent) => { otherHandlers.onFocus?.(event); if (event.defaultMuiPrevented) { return; } - // DOM focus stays on the tree which manages focus with aria-activedescendant - if (event.target === event.currentTarget) { - instance.focusRoot(); - } - const canBeFocused = !status.disabled || disabledItemsFocusable; if (!status.focused && canBeFocused && event.currentTarget === event.target) { instance.focusItem(event, itemId); } }; + const createRootHandleBlur = + (otherHandlers: EventHandlers) => + (event: React.FocusEvent & MuiCancellableEvent) => { + otherHandlers.onBlur?.(event); + + if (event.defaultMuiPrevented) { + return; + } + + instance.removeFocusedItem(); + }; + + const createRootHandleKeyDown = + (otherHandlers: EventHandlers) => + (event: React.KeyboardEvent & MuiCancellableEvent) => { + otherHandlers.onKeyDown?.(event); + + if (event.defaultMuiPrevented) { + return; + } + + instance.handleItemKeyDown(event, itemId); + }; + const createContentHandleClick = (otherHandlers: EventHandlers) => (event: React.MouseEvent & MuiCancellableEvent) => { otherHandlers.onClick?.(event); @@ -103,13 +123,15 @@ export const useTreeItem2 = ; + onFocus: MuiCancellableEventHandler>; + onBlur: MuiCancellableEventHandler>; + onKeyDown: MuiCancellableEventHandler>; ref: React.RefCallback; } From 22c774ee22a0e8c28406587c02b52455361060a7 Mon Sep 17 00:00:00 2001 From: Alexandre Fauquette <45398769+alexfauquette@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:34:22 +0100 Subject: [PATCH 2/9] Bump core (#12499) --- docs/package.json | 12 +- package.json | 6 +- packages/x-charts/package.json | 6 +- packages/x-data-grid-generator/package.json | 2 +- packages/x-data-grid-premium/package.json | 4 +- packages/x-data-grid-pro/package.json | 4 +- packages/x-data-grid/package.json | 4 +- packages/x-date-pickers-pro/package.json | 6 +- packages/x-date-pickers/package.json | 6 +- packages/x-license/package.json | 2 +- packages/x-tree-view/package.json | 6 +- yarn.lock | 144 ++++++++++---------- 12 files changed, 101 insertions(+), 101 deletions(-) diff --git a/docs/package.json b/docs/package.json index 75a7b5f3ee778..753e6bcd105bb 100644 --- a/docs/package.json +++ b/docs/package.json @@ -26,12 +26,12 @@ "@emotion/react": "^11.11.4", "@emotion/server": "^11.11.0", "@emotion/styled": "^11.11.0", - "@mui/icons-material": "^5.15.9", - "@mui/joy": "^5.0.0-beta.27", - "@mui/lab": "^5.0.0-alpha.165", - "@mui/material": "^5.15.9", - "@mui/styles": "^5.15.9", - "@mui/utils": "^5.15.9", + "@mui/icons-material": "^5.15.14", + "@mui/joy": "^5.0.0-beta.32", + "@mui/lab": "^5.0.0-alpha.169", + "@mui/material": "^5.15.14", + "@mui/styles": "^5.15.14", + "@mui/utils": "^5.15.14", "@react-spring/web": "^9.7.3", "@trendmicro/react-interpolate": "^0.5.5", "@types/lodash": "^4.14.202", diff --git a/package.json b/package.json index 9c2387ee2ca1c..24d1c700fac1e 100644 --- a/package.json +++ b/package.json @@ -84,10 +84,10 @@ "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", "@mnajdova/enzyme-adapter-react-18": "^0.2.0", - "@mui/icons-material": "^5.15.9", - "@mui/material": "^5.15.9", + "@mui/icons-material": "^5.15.14", + "@mui/material": "^5.15.14", "@mui/monorepo": "https://github.com/mui/material-ui.git#master", - "@mui/utils": "^5.15.9", + "@mui/utils": "^5.15.14", "@next/eslint-plugin-next": "14.0.4", "@octokit/plugin-retry": "^6.0.1", "@octokit/rest": "^20.0.2", diff --git a/packages/x-charts/package.json b/packages/x-charts/package.json index 85f3bb57bb507..f159427f6eae6 100644 --- a/packages/x-charts/package.json +++ b/packages/x-charts/package.json @@ -39,9 +39,9 @@ }, "dependencies": { "@babel/runtime": "^7.24.0", - "@mui/base": "^5.0.0-beta.36", - "@mui/system": "^5.15.9", - "@mui/utils": "^5.15.9", + "@mui/base": "^5.0.0-beta.40", + "@mui/system": "^5.15.14", + "@mui/utils": "^5.15.14", "@react-spring/rafz": "^9.7.3", "@react-spring/web": "^9.7.3", "clsx": "^2.1.0", diff --git a/packages/x-data-grid-generator/package.json b/packages/x-data-grid-generator/package.json index 0ba9efa091f9e..e097faf0b225e 100644 --- a/packages/x-data-grid-generator/package.json +++ b/packages/x-data-grid-generator/package.json @@ -33,7 +33,7 @@ }, "dependencies": { "@babel/runtime": "^7.24.0", - "@mui/base": "^5.0.0-beta.36", + "@mui/base": "^5.0.0-beta.40", "@mui/x-data-grid-premium": "7.0.0-beta.7", "chance": "^1.1.11", "clsx": "^2.1.0", diff --git a/packages/x-data-grid-premium/package.json b/packages/x-data-grid-premium/package.json index 7e362f5b6ac16..98b0369aeb8d2 100644 --- a/packages/x-data-grid-premium/package.json +++ b/packages/x-data-grid-premium/package.json @@ -43,8 +43,8 @@ }, "dependencies": { "@babel/runtime": "^7.24.0", - "@mui/system": "^5.15.9", - "@mui/utils": "^5.15.9", + "@mui/system": "^5.15.14", + "@mui/utils": "^5.15.14", "@mui/x-data-grid": "7.0.0-beta.7", "@mui/x-data-grid-pro": "7.0.0-beta.7", "@mui/x-license": "7.0.0-beta.6", diff --git a/packages/x-data-grid-pro/package.json b/packages/x-data-grid-pro/package.json index 86eacf60fabbe..e854a3da2c97d 100644 --- a/packages/x-data-grid-pro/package.json +++ b/packages/x-data-grid-pro/package.json @@ -43,8 +43,8 @@ }, "dependencies": { "@babel/runtime": "^7.24.0", - "@mui/system": "^5.15.9", - "@mui/utils": "^5.15.9", + "@mui/system": "^5.15.14", + "@mui/utils": "^5.15.14", "@mui/x-data-grid": "7.0.0-beta.7", "@mui/x-license": "7.0.0-beta.6", "@types/format-util": "^1.0.4", diff --git a/packages/x-data-grid/package.json b/packages/x-data-grid/package.json index 3d0fb9944d210..41f0619973426 100644 --- a/packages/x-data-grid/package.json +++ b/packages/x-data-grid/package.json @@ -47,8 +47,8 @@ }, "dependencies": { "@babel/runtime": "^7.24.0", - "@mui/system": "^5.15.9", - "@mui/utils": "^5.15.9", + "@mui/system": "^5.15.14", + "@mui/utils": "^5.15.14", "clsx": "^2.1.0", "prop-types": "^15.8.1", "reselect": "^4.1.8" diff --git a/packages/x-date-pickers-pro/package.json b/packages/x-date-pickers-pro/package.json index 63f7862cfe44a..9d3fb05c38a89 100644 --- a/packages/x-date-pickers-pro/package.json +++ b/packages/x-date-pickers-pro/package.json @@ -42,9 +42,9 @@ }, "dependencies": { "@babel/runtime": "^7.24.0", - "@mui/base": "^5.0.0-beta.36", - "@mui/system": "^5.15.9", - "@mui/utils": "^5.15.9", + "@mui/base": "^5.0.0-beta.40", + "@mui/system": "^5.15.14", + "@mui/utils": "^5.15.14", "@mui/x-date-pickers": "7.0.0-beta.7", "@mui/x-license": "7.0.0-beta.6", "clsx": "^2.1.0", diff --git a/packages/x-date-pickers/package.json b/packages/x-date-pickers/package.json index 4bf22951c0ce0..f632410d6f6ea 100644 --- a/packages/x-date-pickers/package.json +++ b/packages/x-date-pickers/package.json @@ -45,9 +45,9 @@ }, "dependencies": { "@babel/runtime": "^7.24.0", - "@mui/base": "^5.0.0-beta.36", - "@mui/system": "^5.15.9", - "@mui/utils": "^5.15.9", + "@mui/base": "^5.0.0-beta.40", + "@mui/system": "^5.15.14", + "@mui/utils": "^5.15.14", "@types/react-transition-group": "^4.4.10", "clsx": "^2.1.0", "prop-types": "^15.8.1", diff --git a/packages/x-license/package.json b/packages/x-license/package.json index f640f9a2e22ce..87851acac3a19 100644 --- a/packages/x-license/package.json +++ b/packages/x-license/package.json @@ -34,7 +34,7 @@ }, "dependencies": { "@babel/runtime": "^7.24.0", - "@mui/utils": "^5.15.9" + "@mui/utils": "^5.15.14" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0" diff --git a/packages/x-tree-view/package.json b/packages/x-tree-view/package.json index 47319dacd2d45..b8986a8762bf7 100644 --- a/packages/x-tree-view/package.json +++ b/packages/x-tree-view/package.json @@ -43,9 +43,9 @@ }, "dependencies": { "@babel/runtime": "^7.24.0", - "@mui/base": "^5.0.0-beta.36", - "@mui/system": "^5.15.9", - "@mui/utils": "^5.15.9", + "@mui/base": "^5.0.0-beta.40", + "@mui/system": "^5.15.14", + "@mui/utils": "^5.15.14", "@types/react-transition-group": "^4.4.10", "clsx": "^2.1.0", "prop-types": "^15.8.1", diff --git a/yarn.lock b/yarn.lock index f6bb4b7a86e37..8471de5ce33c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1912,28 +1912,28 @@ rimraf "^5.0.5" typescript "^5.3.3" -"@mui/base@5.0.0-beta.36", "@mui/base@^5.0.0-beta.36": - version "5.0.0-beta.36" - resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.36.tgz#29ca2de9d387f6d3943b6f18a84415c43e5f206c" - integrity sha512-6A8fYiXgjqTO6pgj31Hc8wm1M3rFYCxDRh09dBVk0L0W4cb2lnurRJa3cAyic6hHY+we1S58OdGYRbKmOsDpGQ== +"@mui/base@5.0.0-beta.40", "@mui/base@^5.0.0-beta.40": + version "5.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.40.tgz#1f8a782f1fbf3f84a961e954c8176b187de3dae2" + integrity sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ== dependencies: "@babel/runtime" "^7.23.9" "@floating-ui/react-dom" "^2.0.8" - "@mui/types" "^7.2.13" - "@mui/utils" "^5.15.9" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" "@popperjs/core" "^2.11.8" clsx "^2.1.0" prop-types "^15.8.1" -"@mui/core-downloads-tracker@^5.15.9": - version "5.15.9" - resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.9.tgz#c29138c70cc0fb49cd909c29beef3fb0647e5af7" - integrity sha512-CSDpVevGaxsvMkiYBZ8ztki1z/eT0mM2MqUT21eCRiMz3DU4zQw5rXG5ML/yTuJF9Z2Wv9SliIeaRAuSR/9Nig== +"@mui/core-downloads-tracker@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.14.tgz#f7c57b261904831877220182303761c012d05046" + integrity sha512-on75VMd0XqZfaQW+9pGjSNiqW+ghc5E2ZSLRBXwcXl/C4YzjfyjrLPhrEpKnR9Uym9KXBvxrhoHfPcczYHweyA== -"@mui/icons-material@^5.15.9": - version "5.15.9" - resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.15.9.tgz#8d11839d35cf3cfd62df40934d8e9485f66be620" - integrity sha512-6tLQoM6RylQuDnHR6qQay0G0pJgKmrhn5MIm0IfrwtmSO8eV5iUFR+nNUTXsWa24gt7ZbIKnJ962UlYaeXa4bg== +"@mui/icons-material@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.15.14.tgz#333468c94988d96203946d1cfeb8f4d7e8e7de34" + integrity sha512-vj/51k7MdFmt+XVw94sl30SCvGx6+wJLsNYjZRgxhS6y3UtnWnypMOsm3Kmg8TN+P0dqwsjy4/fX7B1HufJIhw== dependencies: "@babel/runtime" "^7.23.9" @@ -1953,44 +1953,44 @@ typescript "^5.3.3" uuid "^9.0.1" -"@mui/joy@^5.0.0-beta.27": - version "5.0.0-beta.27" - resolved "https://registry.yarnpkg.com/@mui/joy/-/joy-5.0.0-beta.27.tgz#b408b3312d2408d6c4648ccb14881af68545ad88" - integrity sha512-WuyE3c8l77Ph5aBPd9FZAfScx+bMxDS0dLpe5+cno2fMFW1Hua1muTd1Id2e3hebNMwos1NzqaeYFXHgd1yfBw== +"@mui/joy@^5.0.0-beta.32": + version "5.0.0-beta.32" + resolved "https://registry.yarnpkg.com/@mui/joy/-/joy-5.0.0-beta.32.tgz#a7cd6b35b18f812210fe5e089a0aedda8dddd286" + integrity sha512-QJW5Mu2GTJUX4sXjxt4nQBugpJAiSkUT49S/bwoKCCWx8bCfsEyplTzZPK+FraweiGhGgZWExWOTAPpxH83RUQ== dependencies: "@babel/runtime" "^7.23.9" - "@mui/base" "5.0.0-beta.36" - "@mui/core-downloads-tracker" "^5.15.9" - "@mui/system" "^5.15.9" - "@mui/types" "^7.2.13" - "@mui/utils" "^5.15.9" + "@mui/base" "5.0.0-beta.40" + "@mui/core-downloads-tracker" "^5.15.14" + "@mui/system" "^5.15.14" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" clsx "^2.1.0" prop-types "^15.8.1" -"@mui/lab@^5.0.0-alpha.165": - version "5.0.0-alpha.165" - resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.165.tgz#7fd60d26597f60b1dbf7d3ddbd646fcece276385" - integrity sha512-8/zJStT10nh9yrAzLOPTICGhpf5YiGp/JpM0bdTP7u5AE+YT+X2u6QwMxuCrVeW8/WVLAPFg0vtzyfgPcN5T7g== +"@mui/lab@^5.0.0-alpha.169": + version "5.0.0-alpha.169" + resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.169.tgz#85b88b2f06ad78c586cde2b47970653e5fd895eb" + integrity sha512-h6xe1K6ISKUbyxTDgdvql4qoDP6+q8ad5fg9nXQxGLUrIeT2jVrBuT/jRECSTufbnhzP+V5kulvYxaMfM8rEdA== dependencies: "@babel/runtime" "^7.23.9" - "@mui/base" "5.0.0-beta.36" - "@mui/system" "^5.15.9" - "@mui/types" "^7.2.13" - "@mui/utils" "^5.15.9" + "@mui/base" "5.0.0-beta.40" + "@mui/system" "^5.15.14" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" clsx "^2.1.0" prop-types "^15.8.1" -"@mui/material@^5.15.9": - version "5.15.9" - resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.9.tgz#4d6a4aee002c6a2d0e174e08c6d23245c18dd828" - integrity sha512-kbHTZDcFmN8GHKzRpImUEl9AJfFWI/0Kl+DsYVT3kHzQWUuHiKm3uHXR1RCOqr7H8IgHFPdbxItmCSQ/mj7zgg== +"@mui/material@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.14.tgz#a40bd5eccfa9fc925535e1f4d70c6cef77fa3a75" + integrity sha512-kEbRw6fASdQ1SQ7LVdWR5OlWV3y7Y54ZxkLzd6LV5tmz+NpO3MJKZXSfgR0LHMP7meKsPiMm4AuzV0pXDpk/BQ== dependencies: "@babel/runtime" "^7.23.9" - "@mui/base" "5.0.0-beta.36" - "@mui/core-downloads-tracker" "^5.15.9" - "@mui/system" "^5.15.9" - "@mui/types" "^7.2.13" - "@mui/utils" "^5.15.9" + "@mui/base" "5.0.0-beta.40" + "@mui/core-downloads-tracker" "^5.15.14" + "@mui/system" "^5.15.14" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" "@types/react-transition-group" "^4.4.10" clsx "^2.1.0" csstype "^3.1.3" @@ -2007,35 +2007,35 @@ execa "^8.0.1" google-auth-library "^9.5.0" -"@mui/private-theming@^5.15.9": - version "5.15.9" - resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.15.9.tgz#3ea3514ed2f6bf68541dbe9206665a82cd89cb01" - integrity sha512-/aMJlDOxOTAXyp4F2rIukW1O0anodAMCkv1DfBh/z9vaKHY3bd5fFf42wmP+0GRmwMinC5aWPpNfHXOED1fEtg== +"@mui/private-theming@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.15.14.tgz#edd9a82948ed01586a01c842eb89f0e3f68970ee" + integrity sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw== dependencies: "@babel/runtime" "^7.23.9" - "@mui/utils" "^5.15.9" + "@mui/utils" "^5.15.14" prop-types "^15.8.1" -"@mui/styled-engine@^5.15.9": - version "5.15.9" - resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.15.9.tgz#444605039ec3fe456bdd5d5cb94330183be62b91" - integrity sha512-NRKtYkL5PZDH7dEmaLEIiipd3mxNnQSO+Yo8rFNBNptY8wzQnQ+VjayTq39qH7Sast5cwHKYFusUrQyD+SS4Og== +"@mui/styled-engine@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.15.14.tgz#168b154c4327fa4ccc1933a498331d53f61c0de2" + integrity sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw== dependencies: "@babel/runtime" "^7.23.9" "@emotion/cache" "^11.11.0" csstype "^3.1.3" prop-types "^15.8.1" -"@mui/styles@^5.15.9": - version "5.15.9" - resolved "https://registry.yarnpkg.com/@mui/styles/-/styles-5.15.9.tgz#b123e49c52578911354abd7adf9b63a9c360461d" - integrity sha512-xwQ1uI2rB6Ra9fZM0jPjmUWsr2Jbtf8+VMNs7DiallJDrzdmmvHzBkVe74bTWWmHq1HdsYIz2vIcH0wnFYGe4Q== +"@mui/styles@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/styles/-/styles-5.15.14.tgz#b0f157ccbb4270bbda68e518b035fb3b71cc7470" + integrity sha512-EspFoCqLf3BadSIRM5dBqrrbE0hioI6/YZXDGzvcPsedQ7j7wAdcIs9Ex6TVqrRUADNWI/Azg6/mhcqWiBDFOg== dependencies: "@babel/runtime" "^7.23.9" "@emotion/hash" "^0.9.1" - "@mui/private-theming" "^5.15.9" - "@mui/types" "^7.2.13" - "@mui/utils" "^5.15.9" + "@mui/private-theming" "^5.15.14" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" clsx "^2.1.0" csstype "^3.1.3" hoist-non-react-statics "^3.3.2" @@ -2049,29 +2049,29 @@ jss-plugin-vendor-prefixer "^10.10.0" prop-types "^15.8.1" -"@mui/system@^5.15.9": - version "5.15.9" - resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.9.tgz#8a34ac0ab133af2550cc7ab980a35174142fd265" - integrity sha512-SxkaaZ8jsnIJ77bBXttfG//LUf6nTfOcaOuIgItqfHv60ZCQy/Hu7moaob35kBb+guxVJnoSZ+7vQJrA/E7pKg== +"@mui/system@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.14.tgz#8a0c6571077eeb6b5f1ff7aa7ff6a3dc4a14200d" + integrity sha512-auXLXzUaCSSOLqJXmsAaq7P96VPRXg2Rrz6OHNV7lr+kB8lobUF+/N84Vd9C4G/wvCXYPs5TYuuGBRhcGbiBGg== dependencies: "@babel/runtime" "^7.23.9" - "@mui/private-theming" "^5.15.9" - "@mui/styled-engine" "^5.15.9" - "@mui/types" "^7.2.13" - "@mui/utils" "^5.15.9" + "@mui/private-theming" "^5.15.14" + "@mui/styled-engine" "^5.15.14" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" clsx "^2.1.0" csstype "^3.1.3" prop-types "^15.8.1" -"@mui/types@^7.2.13": - version "7.2.13" - resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.13.tgz#d1584912942f9dc042441ecc2d1452be39c666b8" - integrity sha512-qP9OgacN62s+l8rdDhSFRe05HWtLLJ5TGclC9I1+tQngbssu0m2dmFZs+Px53AcOs9fD7TbYd4gc9AXzVqO/+g== +"@mui/types@^7.2.14": + version "7.2.14" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.14.tgz#8a02ac129b70f3d82f2f9b76ded2c8d48e3fc8c9" + integrity sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ== -"@mui/utils@^5.15.9": - version "5.15.9" - resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.9.tgz#2bdf925e274d87cbe90c14eb52d0835318205e86" - integrity sha512-yDYfr61bCYUz1QtwvpqYy/3687Z8/nS4zv7lv/ih/6ZFGMl1iolEvxRmR84v2lOYxlds+kq1IVYbXxDKh8Z9sg== +"@mui/utils@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.14.tgz#e414d7efd5db00bfdc875273a40c0a89112ade3a" + integrity sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA== dependencies: "@babel/runtime" "^7.23.9" "@types/prop-types" "^15.7.11" From c9f1006752a321011f7ae4b6754a47f801f4d0d0 Mon Sep 17 00:00:00 2001 From: Andrew Cherniavskii Date: Tue, 19 Mar 2024 14:52:35 +0100 Subject: [PATCH 3/9] [docs] Update links to v6 (#12496) --- docs/pages/_app.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/pages/_app.js b/docs/pages/_app.js index b9ca17827e0a7..76826c3c75eff 100644 --- a/docs/pages/_app.js +++ b/docs/pages/_app.js @@ -215,7 +215,7 @@ function AppWrapper(props) { text: `v${process.env.LIB_VERSION}`, current: true, }, - { text: 'v6', href: `https://mui.com${languagePrefix}/x/introduction/` }, + { text: 'v6', href: `https://v6.mui.com${languagePrefix}/x/introduction/` }, { text: 'v5', href: `https://v5.mui.com${languagePrefix}/x/introduction/` }, { text: 'v4', href: `https://v4.mui.com${languagePrefix}/components/data-grid/` }, ], @@ -230,7 +230,7 @@ function AppWrapper(props) { text: `v${process.env.DATA_GRID_VERSION}`, current: true, }, - { text: 'v6', href: `https://mui.com${languagePrefix}/x/react-data-grid/` }, + { text: 'v6', href: `https://v6.mui.com${languagePrefix}/x/react-data-grid/` }, { text: 'v5', href: `https://v5.mui.com${languagePrefix}/x/react-data-grid/` }, { text: 'v4', href: `https://v4.mui.com${languagePrefix}/components/data-grid/` }, ], @@ -246,7 +246,7 @@ function AppWrapper(props) { }, { text: 'v6', - href: `https://mui.com${languagePrefix}/x/react-date-pickers/`, + href: `https://v6.mui.com${languagePrefix}/x/react-date-pickers/`, }, { text: 'v5', @@ -263,7 +263,7 @@ function AppWrapper(props) { text: `v${process.env.CHARTS_VERSION}`, current: true, }, - { text: 'v6', href: `https://mui.com${languagePrefix}/x/react-charts/` }, + { text: 'v6', href: `https://v6.mui.com${languagePrefix}/x/react-charts/` }, ], }; } else if (productId === 'x-tree-view') { @@ -277,7 +277,7 @@ function AppWrapper(props) { }, { text: 'v6', - href: `https://mui.com${languagePrefix}/x/react-tree-view/getting-started`, + href: `https://v6.mui.com${languagePrefix}/x/react-tree-view/getting-started`, }, ], }; From aea5ef70c47f3eece1cc8139b12746c86f0b5871 Mon Sep 17 00:00:00 2001 From: Nora <72460825+noraleonte@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:56:59 +0200 Subject: [PATCH 4/9] [docs] Update links to v7 docs (#12500) --- docs/translations/api-docs/tree-view/tree-view/tree-view.json | 2 +- .../src/internals/hooks/useField/useFieldV7TextField.ts | 2 +- packages/x-tree-view/src/RichTreeView/RichTreeView.tsx | 2 +- packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.tsx | 2 +- packages/x-tree-view/src/TreeView/TreeView.tsx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/translations/api-docs/tree-view/tree-view/tree-view.json b/docs/translations/api-docs/tree-view/tree-view/tree-view.json index a8a98ee8ae876..c6d01a54bd44e 100644 --- a/docs/translations/api-docs/tree-view/tree-view/tree-view.json +++ b/docs/translations/api-docs/tree-view/tree-view/tree-view.json @@ -1,5 +1,5 @@ { - "componentDescription": "This component has been deprecated in favor of the new `SimpleTreeView` component.\nYou can have a look at how to migrate to the new component in the v7 [migration guide](https://next.mui.com/x/migration/migration-tree-view-v6/#use-simpletreeview-instead-of-treeview)", + "componentDescription": "This component has been deprecated in favor of the new `SimpleTreeView` component.\nYou can have a look at how to migrate to the new component in the v7 [migration guide](https://mui.com/x/migration/migration-tree-view-v6/#use-simpletreeview-instead-of-treeview)", "propDescriptions": { "apiRef": { "description": "The ref object that allows Tree View manipulation. Can be instantiated with useTreeViewApiRef()." diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useFieldV7TextField.ts b/packages/x-date-pickers/src/internals/hooks/useField/useFieldV7TextField.ts index c0c707676fd12..ec2457a8d76b6 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useFieldV7TextField.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useFieldV7TextField.ts @@ -494,7 +494,7 @@ export const useFieldV7TextField: UseFieldTextField = (params) => { '', '', '', - 'Learn more about the field accessible DOM structure on the MUI documentation: https://next.mui.com/x/react-date-pickers/fields/#fields-to-edit-a-single-element', + 'Learn more about the field accessible DOM structure on the MUI documentation: https://mui.com/x/react-date-pickers/fields/#fields-to-edit-a-single-element', ].join('\n'), ); } diff --git a/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx b/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx index 27f3df1b64cfc..051e56a30c5d8 100644 --- a/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx +++ b/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx @@ -62,7 +62,7 @@ function WrappedTreeItem({ const childrenWarning = buildWarning([ 'MUI X: The `RichTreeView` component does not support JSX children.', 'If you want to add items, you need to use the `items` prop', - 'Check the documentation for more details: https://next.mui.com/x/react-tree-view/rich-tree-view/items/', + 'Check the documentation for more details: https://mui.com/x/react-tree-view/rich-tree-view/items/', ]); /** diff --git a/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.tsx b/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.tsx index 54c093ba1b5b6..c78cd8dcd2538 100644 --- a/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.tsx +++ b/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.tsx @@ -47,7 +47,7 @@ const EMPTY_ITEMS: any[] = []; const itemsPropWarning = buildWarning([ 'MUI X: The `SimpleTreeView` component does not support the `items` prop.', 'If you want to add items, you need to pass them as JSX children.', - 'Check the documentation for more details: https://next.mui.com/x/react-tree-view/simple-tree-view/items/', + 'Check the documentation for more details: https://mui.com/x/react-tree-view/simple-tree-view/items/', ]); /** diff --git a/packages/x-tree-view/src/TreeView/TreeView.tsx b/packages/x-tree-view/src/TreeView/TreeView.tsx index 6cc59d4ff1623..5d21d2a0d7aca 100644 --- a/packages/x-tree-view/src/TreeView/TreeView.tsx +++ b/packages/x-tree-view/src/TreeView/TreeView.tsx @@ -48,7 +48,7 @@ const warn = () => { /** * This component has been deprecated in favor of the new `SimpleTreeView` component. - * You can have a look at how to migrate to the new component in the v7 [migration guide](https://next.mui.com/x/migration/migration-tree-view-v6/#use-simpletreeview-instead-of-treeview) + * You can have a look at how to migrate to the new component in the v7 [migration guide](https://mui.com/x/migration/migration-tree-view-v6/#use-simpletreeview-instead-of-treeview) * * Demos: * From d968a6391227097623176041b03abeb8199f98dd Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Tue, 19 Mar 2024 19:21:17 +0500 Subject: [PATCH 5/9] [DataGrid] Allow to control the grid density (#12332) Signed-off-by: Bilal Shafi Co-authored-by: Flavien DELANGLE Co-authored-by: Andrew Cherniavskii --- .../accessibility/DensitySelectorSmallGrid.js | 8 +- .../DensitySelectorSmallGrid.tsx | 9 +- .../DensitySelectorSmallGrid.tsx.preview | 6 +- .../data-grid/accessibility/accessibility.md | 50 +++- docs/data/data-grid/events/events.json | 7 + .../migration-data-grid-v6.md | 26 ++ .../x/api/data-grid/data-grid-premium.json | 7 + docs/pages/x/api/data-grid/data-grid-pro.json | 7 + docs/pages/x/api/data-grid/data-grid.json | 7 + docs/pages/x/api/data-grid/selectors.json | 8 +- .../data-grid-premium/data-grid-premium.json | 4 + .../data-grid-pro/data-grid-pro.json | 4 + .../data-grid/data-grid/data-grid.json | 4 + .../src/DataGridPremium/DataGridPremium.tsx | 5 + .../src/DataGridPro/DataGridPro.tsx | 5 + .../x-data-grid/src/DataGrid/DataGrid.tsx | 5 + .../src/DataGrid/useDataGridProps.ts | 1 - .../src/components/containers/GridRoot.tsx | 6 +- .../toolbar/GridToolbarDensitySelector.tsx | 10 +- .../hooks/features/density/densitySelector.ts | 17 +- .../hooks/features/density/densityState.ts | 5 +- .../hooks/features/density/useGridDensity.tsx | 59 ++--- .../src/models/events/gridEventLookup.ts | 5 + .../src/models/gridStateCommunity.ts | 1 + .../src/models/props/DataGridProps.ts | 15 +- .../src/tests/density.DataGrid.test.tsx | 229 ++++++++++++++++++ .../src/tests/rows.DataGrid.test.tsx | 2 +- .../src/tests/toolbar.DataGrid.test.tsx | 114 +-------- scripts/x-data-grid-premium.exports.json | 5 +- scripts/x-data-grid-pro.exports.json | 5 +- scripts/x-data-grid.exports.json | 5 +- 31 files changed, 448 insertions(+), 193 deletions(-) create mode 100644 packages/x-data-grid/src/tests/density.DataGrid.test.tsx diff --git a/docs/data/data-grid/accessibility/DensitySelectorSmallGrid.js b/docs/data/data-grid/accessibility/DensitySelectorSmallGrid.js index d9d61b7c7433f..9fe1c320f95be 100644 --- a/docs/data/data-grid/accessibility/DensitySelectorSmallGrid.js +++ b/docs/data/data-grid/accessibility/DensitySelectorSmallGrid.js @@ -15,6 +15,8 @@ function CustomToolbar() { } export default function DensitySelectorSmallGrid() { + const [density, setDensity] = React.useState('compact'); + const { data } = useDemoData({ dataSet: 'Commodity', rowLength: 4, @@ -25,7 +27,11 @@ export default function DensitySelectorSmallGrid() {
{ + console.info(`Density updated to: ${newDensity}`); + setDensity(newDensity); + }} slots={{ toolbar: CustomToolbar, }} diff --git a/docs/data/data-grid/accessibility/DensitySelectorSmallGrid.tsx b/docs/data/data-grid/accessibility/DensitySelectorSmallGrid.tsx index d9d61b7c7433f..d7d04073e9425 100644 --- a/docs/data/data-grid/accessibility/DensitySelectorSmallGrid.tsx +++ b/docs/data/data-grid/accessibility/DensitySelectorSmallGrid.tsx @@ -3,6 +3,7 @@ import { DataGrid, GridToolbarContainer, GridToolbarDensitySelector, + GridDensity, } from '@mui/x-data-grid'; import { useDemoData } from '@mui/x-data-grid-generator'; @@ -15,6 +16,8 @@ function CustomToolbar() { } export default function DensitySelectorSmallGrid() { + const [density, setDensity] = React.useState('compact'); + const { data } = useDemoData({ dataSet: 'Commodity', rowLength: 4, @@ -25,7 +28,11 @@ export default function DensitySelectorSmallGrid() {
{ + console.info(`Density updated to: ${newDensity}`); + setDensity(newDensity); + }} slots={{ toolbar: CustomToolbar, }} diff --git a/docs/data/data-grid/accessibility/DensitySelectorSmallGrid.tsx.preview b/docs/data/data-grid/accessibility/DensitySelectorSmallGrid.tsx.preview index 9d198ee8add6a..cd87eee43b444 100644 --- a/docs/data/data-grid/accessibility/DensitySelectorSmallGrid.tsx.preview +++ b/docs/data/data-grid/accessibility/DensitySelectorSmallGrid.tsx.preview @@ -1,6 +1,10 @@ { + console.info(`Density updated to: ${newDensity}`); + setDensity(newDensity); + }} slots={{ toolbar: CustomToolbar, }} diff --git a/docs/data/data-grid/accessibility/accessibility.md b/docs/data/data-grid/accessibility/accessibility.md index 3a54c27b73b07..afb47c68a23a7 100644 --- a/docs/data/data-grid/accessibility/accessibility.md +++ b/docs/data/data-grid/accessibility/accessibility.md @@ -22,22 +22,56 @@ The [WAI-ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/patterns/grid You can change the density of the rows and the column header. -### Density selector +### Density selection from the toolbar -To enable the density selector, create a toolbar containing the `GridToolbarDensitySelector` component and apply it using the `toolbar` property in the Data Grid's `slots` prop. -The user can then change the density of the Data Grid by using the density selector from the toolbar, as the following demo illustrates: +To enable the density selection from the toolbar, you can do one of the following: + +1. Enable the default toolbar component by passing the `slots.toolbar` prop to the Data Grid. +2. Create a specific toolbar containing only the `GridToolbarDensitySelector` component and apply it using the `toolbar` property in the Data Grid's `slots` prop. + +The user can then change the density of the Data Grid by using the density selection menu from the toolbar, as the following demo illustrates: {{"demo": "DensitySelectorGrid.js", "bg": "inline"}} -To hide the density selector, add the `disableDensitySelector` prop to the Data Grid. +To disable the density selection menu, pass the `disableDensitySelector` prop to the Data Grid. + +### Set the density programmatically + +The Data Grid exposes the `density` prop which supports the following values: + +- `standard` (default) +- `compact` +- `comfortable` + +You can set the density programmatically in one of the following ways: + +1. Uncontrolled – initialize the density with the `initialState.density` prop. + + ```tsx + + ``` + +2. Controlled – pass the `density` and `onDensityChange` props. For more advanced use cases, you can also subscribe to the `densityChange` grid event. + + ```tsx + const [density, setDensity] = React.useState('compact'); -### Density prop + return ( + setDensity(newDensity)} + /> + ); + ``` -Set the vertical density of the Data Grid using the `density` prop. -This prop applies the values determined by the `rowHeight` and `columnHeaderHeight` props, if supplied. +The `density` prop applies the values determined by the `rowHeight` and `columnHeaderHeight` props, if supplied. The user can override this setting with the optional toolbar density selector. -The following demo shows a Data Grid with the default density set to `compact`: +The following demo shows a Data Grid with the controlled density set to `compact` and outputs the current density to the console when the user changes it using the density selector from the toolbar: {{"demo": "DensitySelectorSmallGrid.js", "bg": "inline"}} diff --git a/docs/data/data-grid/events/events.json b/docs/data/data-grid/events/events.json index 6bb6029718fa7..64c820e1c397d 100644 --- a/docs/data/data-grid/events/events.json +++ b/docs/data/data-grid/events/events.json @@ -202,6 +202,13 @@ "event": "MuiEvent<{}>", "componentProp": "onResize" }, + { + "projects": ["x-data-grid", "x-data-grid-pro", "x-data-grid-premium"], + "name": "densityChange", + "description": "Fired when the density changes.", + "params": "GridDensity", + "event": "MuiEvent<{}>" + }, { "projects": ["x-data-grid-premium"], "name": "excelExportStateChange", diff --git a/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md b/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md index 8e693b6e8e61c..fd89b82c9ecda 100644 --- a/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md +++ b/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md @@ -303,6 +303,32 @@ See the [Direct state access](/x/react-data-grid/state/#direct-selector-access) + groupingValueGetter: (value: { name: string }) => value.name, ``` +### Density + +- The `density` is a [controlled prop](/x/react-data-grid/accessibility/#set-the-density-programmatically) now, if you were previously passing the `density` prop to the Data Grid, you will need to do one of the following: + +1. Move it to the `initialState.density` to initialize it. + +```diff + +``` + +2. Move it to the state and use `onDensityChange` callback to update the `density` prop accordingly for it to work as expected. + +```diff + const [density, setDensity] = React.useState('compact'); + setDensity(newDensity)} + /> +``` + +- The selector `gridDensityValueSelector` was removed, use the `gridDensitySelector` instead. + diff --git a/docs/pages/x/api/data-grid/data-grid-premium.json b/docs/pages/x/api/data-grid/data-grid-premium.json index 559aa75b2e3f4..4c43103ed7e0b 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -361,6 +361,13 @@ "describedArgs": ["params", "event", "details"] } }, + "onDensityChange": { + "type": { "name": "func" }, + "signature": { + "type": "function(density: GridDensity) => void", + "describedArgs": ["density"] + } + }, "onDetailPanelExpandedRowIdsChange": { "type": { "name": "func" }, "signature": { diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json index 198c5f68c6da7..6c2382cfa2982 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -318,6 +318,13 @@ "describedArgs": ["params", "event", "details"] } }, + "onDensityChange": { + "type": { "name": "func" }, + "signature": { + "type": "function(density: GridDensity) => void", + "describedArgs": ["density"] + } + }, "onDetailPanelExpandedRowIdsChange": { "type": { "name": "func" }, "signature": { diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json index 468423143dd70..9fd2b885eb548 100644 --- a/docs/pages/x/api/data-grid/data-grid.json +++ b/docs/pages/x/api/data-grid/data-grid.json @@ -278,6 +278,13 @@ "describedArgs": ["params", "event", "details"] } }, + "onDensityChange": { + "type": { "name": "func" }, + "signature": { + "type": "function(density: GridDensity) => void", + "describedArgs": ["density"] + } + }, "onFilterModelChange": { "type": { "name": "func" }, "signature": { diff --git a/docs/pages/x/api/data-grid/selectors.json b/docs/pages/x/api/data-grid/selectors.json index fc2fa812d4817..1140b1bf1d946 100644 --- a/docs/pages/x/api/data-grid/selectors.json +++ b/docs/pages/x/api/data-grid/selectors.json @@ -124,15 +124,9 @@ }, { "name": "gridDensitySelector", - "returnType": "GridDensityState", - "description": "", - "supportsApiRef": false - }, - { - "name": "gridDensityValueSelector", "returnType": "GridDensity", "description": "", - "supportsApiRef": true + "supportsApiRef": false }, { "name": "gridDetailPanelExpandedRowIdsSelector", diff --git a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json index 96b941032c92d..55da4f984364f 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json @@ -394,6 +394,10 @@ "details": "Additional details for this callback." } }, + "onDensityChange": { + "description": "Callback fired when the density changes.", + "typeDescriptions": { "density": "New density value." } + }, "onDetailPanelExpandedRowIdsChange": { "description": "Callback fired when the detail panel of a row is opened or closed.", "typeDescriptions": { diff --git a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json index c4f04eb81c606..32c54712411fd 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json @@ -351,6 +351,10 @@ "details": "Additional details for this callback." } }, + "onDensityChange": { + "description": "Callback fired when the density changes.", + "typeDescriptions": { "density": "New density value." } + }, "onDetailPanelExpandedRowIdsChange": { "description": "Callback fired when the detail panel of a row is opened or closed.", "typeDescriptions": { diff --git a/docs/translations/api-docs/data-grid/data-grid/data-grid.json b/docs/translations/api-docs/data-grid/data-grid/data-grid.json index c6d25a3f35a80..a9dd4e497f260 100644 --- a/docs/translations/api-docs/data-grid/data-grid/data-grid.json +++ b/docs/translations/api-docs/data-grid/data-grid/data-grid.json @@ -295,6 +295,10 @@ "details": "Additional details for this callback." } }, + "onDensityChange": { + "description": "Callback fired when the density changes.", + "typeDescriptions": { "density": "New density value." } + }, "onFilterModelChange": { "description": "Callback fired when the Filter model changes before the filters are applied.", "typeDescriptions": { diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index a411583a776c3..8991449eda96b 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -678,6 +678,11 @@ DataGridPremiumRaw.propTypes = { * @param {GridCallbackDetails} details Additional details for this callback. */ onColumnWidthChange: PropTypes.func, + /** + * Callback fired when the density changes. + * @param {GridDensity} density New density value. + */ + onDensityChange: PropTypes.func, /** * Callback fired when the detail panel of a row is opened or closed. * @param {GridRowId[]} ids The ids of the rows which have the detail panel open. diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index 55bae26091799..df1e21319472d 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -591,6 +591,11 @@ DataGridProRaw.propTypes = { * @param {GridCallbackDetails} details Additional details for this callback. */ onColumnWidthChange: PropTypes.func, + /** + * Callback fired when the density changes. + * @param {GridDensity} density New density value. + */ + onDensityChange: PropTypes.func, /** * Callback fired when the detail panel of a row is opened or closed. * @param {GridRowId[]} ids The ids of the rows which have the detail panel open. diff --git a/packages/x-data-grid/src/DataGrid/DataGrid.tsx b/packages/x-data-grid/src/DataGrid/DataGrid.tsx index 475dc646e981f..fe0f14bbb9d6c 100644 --- a/packages/x-data-grid/src/DataGrid/DataGrid.tsx +++ b/packages/x-data-grid/src/DataGrid/DataGrid.tsx @@ -506,6 +506,11 @@ DataGridRaw.propTypes = { * @param {GridCallbackDetails} details Additional details for this callback. */ onColumnWidthChange: PropTypes.func, + /** + * Callback fired when the density changes. + * @param {GridDensity} density New density value. + */ + onDensityChange: PropTypes.func, /** * Callback fired when the Filter model changes before the filters are applied. * @param {GridFilterModel} model With all properties from [[GridFilterModel]]. diff --git a/packages/x-data-grid/src/DataGrid/useDataGridProps.ts b/packages/x-data-grid/src/DataGrid/useDataGridProps.ts index b0f91d49cf291..3759d21e04241 100644 --- a/packages/x-data-grid/src/DataGrid/useDataGridProps.ts +++ b/packages/x-data-grid/src/DataGrid/useDataGridProps.ts @@ -36,7 +36,6 @@ export const DATA_GRID_PROPS_DEFAULT_VALUES: DataGridPropsWithDefaultValues = { columnThreshold: 3, rowThreshold: 3, rowSelection: true, - density: 'standard', disableColumnFilter: false, disableColumnMenu: false, disableColumnSelector: false, diff --git a/packages/x-data-grid/src/components/containers/GridRoot.tsx b/packages/x-data-grid/src/components/containers/GridRoot.tsx index b42fcb4341ed4..a8e56cecfc30a 100644 --- a/packages/x-data-grid/src/components/containers/GridRoot.tsx +++ b/packages/x-data-grid/src/components/containers/GridRoot.tsx @@ -14,7 +14,7 @@ import { useGridSelector } from '../../hooks/utils/useGridSelector'; import { useGridPrivateApiContext } from '../../hooks/utils/useGridPrivateApiContext'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; -import { gridDensityValueSelector } from '../../hooks/features/density/densitySelector'; +import { gridDensitySelector } from '../../hooks/features/density/densitySelector'; import { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { GridDensity } from '../../models/gridDensity'; @@ -55,13 +55,13 @@ const GridRoot = React.forwardRef(function GridRo const rootProps = useGridRootProps(); const { children, className, ...other } = props; const apiRef = useGridPrivateApiContext(); - const densityValue = useGridSelector(apiRef, gridDensityValueSelector); + const density = useGridSelector(apiRef, gridDensitySelector); const rootElementRef = apiRef.current.rootElementRef; const handleRef = useForkRef(rootElementRef, ref); const ownerState = { ...rootProps, - density: densityValue, + density, }; const classes = useUtilityClasses(ownerState); diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx index 1c4b0b6a63511..4c790f2ee720b 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx @@ -6,7 +6,7 @@ import { ButtonProps } from '@mui/material/Button'; import { TooltipProps } from '@mui/material/Tooltip'; import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; -import { gridDensityValueSelector } from '../../hooks/features/density/densitySelector'; +import { gridDensitySelector } from '../../hooks/features/density/densitySelector'; import { GridDensity } from '../../models/gridDensity'; import { isHideMenuKey, isTabKey } from '../../utils/keyboardUtils'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; @@ -33,7 +33,7 @@ const GridToolbarDensitySelector = React.forwardRef< const tooltipProps = slotProps.tooltip || {}; const apiRef = useGridApiContext(); const rootProps = useGridRootProps(); - const densityValue = useGridSelector(apiRef, gridDensityValueSelector); + const density = useGridSelector(apiRef, gridDensitySelector); const densityButtonId = useId(); const densityMenuId = useId(); @@ -60,7 +60,7 @@ const GridToolbarDensitySelector = React.forwardRef< ]; const startIcon = React.useMemo(() => { - switch (densityValue) { + switch (density) { case 'compact': return ; case 'comfortable': @@ -68,7 +68,7 @@ const GridToolbarDensitySelector = React.forwardRef< default: return ; } - }, [densityValue, rootProps]); + }, [density, rootProps]); const handleDensitySelectorOpen = (event: React.MouseEvent) => { setOpen((prevOpen) => !prevOpen); @@ -100,7 +100,7 @@ const GridToolbarDensitySelector = React.forwardRef< handleDensityUpdate(option.value)} - selected={option.value === densityValue} + selected={option.value === density} > {option.icon} {option.label} diff --git a/packages/x-data-grid/src/hooks/features/density/densitySelector.ts b/packages/x-data-grid/src/hooks/features/density/densitySelector.ts index 277cc3cfbfc61..0fcbacc3ec7f4 100644 --- a/packages/x-data-grid/src/hooks/features/density/densitySelector.ts +++ b/packages/x-data-grid/src/hooks/features/density/densitySelector.ts @@ -1,14 +1,19 @@ import { createSelector } from '../../../utils/createSelector'; import { GridStateCommunity } from '../../../models/gridStateCommunity'; +import { GridDensity } from '../../../models/gridDensity'; -export const gridDensitySelector = (state: GridStateCommunity) => state.density; +export const COMPACT_DENSITY_FACTOR = 0.7; +export const COMFORTABLE_DENSITY_FACTOR = 1.3; -export const gridDensityValueSelector = createSelector( - gridDensitySelector, - (density) => density.value, -); +const DENSITY_FACTORS: Record = { + compact: COMPACT_DENSITY_FACTOR, + comfortable: COMFORTABLE_DENSITY_FACTOR, + standard: 1, +}; + +export const gridDensitySelector = (state: GridStateCommunity) => state.density; export const gridDensityFactorSelector = createSelector( gridDensitySelector, - (density) => density.factor, + (density) => DENSITY_FACTORS[density], ); diff --git a/packages/x-data-grid/src/hooks/features/density/densityState.ts b/packages/x-data-grid/src/hooks/features/density/densityState.ts index bebaa1844b514..fdc6aa28a9a10 100644 --- a/packages/x-data-grid/src/hooks/features/density/densityState.ts +++ b/packages/x-data-grid/src/hooks/features/density/densityState.ts @@ -1,6 +1,3 @@ import { GridDensity } from '../../../models/gridDensity'; -export interface GridDensityState { - value: GridDensity; - factor: number; -} +export type GridDensityState = GridDensity; diff --git a/packages/x-data-grid/src/hooks/features/density/useGridDensity.tsx b/packages/x-data-grid/src/hooks/features/density/useGridDensity.tsx index 3b3bf2d7dab45..172b161c2cdbf 100644 --- a/packages/x-data-grid/src/hooks/features/density/useGridDensity.tsx +++ b/packages/x-data-grid/src/hooks/features/density/useGridDensity.tsx @@ -1,59 +1,52 @@ import * as React from 'react'; -import { GridDensity } from '../../../models/gridDensity'; +import useEventCallback from '@mui/utils/useEventCallback'; import { useGridLogger } from '../../utils/useGridLogger'; import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; import { useGridApiMethod } from '../../utils/useGridApiMethod'; import { GridDensityApi } from '../../../models/api/gridDensityApi'; import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; import { gridDensitySelector } from './densitySelector'; -import { isDeepEqual } from '../../../utils/utils'; import { GridStateInitializer } from '../../utils/useGridInitializeState'; -export const COMPACT_DENSITY_FACTOR = 0.7; -export const COMFORTABLE_DENSITY_FACTOR = 1.3; - -const DENSITY_FACTORS: Record = { - compact: COMPACT_DENSITY_FACTOR, - comfortable: COMFORTABLE_DENSITY_FACTOR, - standard: 1, -}; - export const densityStateInitializer: GridStateInitializer< - Pick + Pick > = (state, props) => ({ ...state, - density: { value: props.density, factor: DENSITY_FACTORS[props.density] }, + density: props.initialState?.density ?? props.density ?? 'standard', }); export const useGridDensity = ( apiRef: React.MutableRefObject, - props: Pick, + props: Pick, ): void => { const logger = useGridLogger(apiRef, 'useDensity'); - const setDensity = React.useCallback( - (newDensity): void => { - logger.debug(`Set grid density to ${newDensity}`); - apiRef.current.setState((state) => { - const currentDensityState = gridDensitySelector(state); - const newDensityState = { value: newDensity, factor: DENSITY_FACTORS[newDensity] }; + apiRef.current.registerControlState({ + stateId: 'density', + propModel: props.density, + propOnChange: props.onDensityChange, + stateSelector: gridDensitySelector, + changeEvent: 'densityChange', + }); + + const setDensity = useEventCallback((newDensity): void => { + const currentDensity = gridDensitySelector(apiRef.current.state); + if (currentDensity === newDensity) { + return; + } - if (isDeepEqual(currentDensityState, newDensityState)) { - return state; - } + logger.debug(`Set grid density to ${newDensity}`); - return { - ...state, - density: newDensityState, - }; - }); - apiRef.current.forceUpdate(); - }, - [logger, apiRef], - ); + apiRef.current.setState((state) => ({ + ...state, + density: newDensity, + })); + }); React.useEffect(() => { - apiRef.current.setDensity(props.density); + if (props.density) { + apiRef.current.setDensity(props.density); + } }, [apiRef, props.density]); const densityApi: GridDensityApi = { diff --git a/packages/x-data-grid/src/models/events/gridEventLookup.ts b/packages/x-data-grid/src/models/events/gridEventLookup.ts index 451988f596a1d..5f29dceffb45d 100644 --- a/packages/x-data-grid/src/models/events/gridEventLookup.ts +++ b/packages/x-data-grid/src/models/events/gridEventLookup.ts @@ -25,6 +25,7 @@ import type { GridStrategyProcessorName } from '../../hooks/core/strategyProcess import { GridRowEditStartParams, GridRowEditStopParams } from '../params/gridRowParams'; import { GridCellModesModel, GridRowModesModel } from '../api/gridEditingApi'; import { GridPaginationModel } from '../gridPaginationProps'; +import { GridDensity } from '../gridDensity'; export interface GridRowEventLookup { /** @@ -362,6 +363,10 @@ export interface GridControlledStateEventLookup { * Fired when the row count change. */ rowCountChange: { params: number }; + /** + * Fired when the density changes. + */ + densityChange: { params: GridDensity }; } export interface GridControlledStateReasonLookup { diff --git a/packages/x-data-grid/src/models/gridStateCommunity.ts b/packages/x-data-grid/src/models/gridStateCommunity.ts index 95015838ddc1b..7e00992692bf8 100644 --- a/packages/x-data-grid/src/models/gridStateCommunity.ts +++ b/packages/x-data-grid/src/models/gridStateCommunity.ts @@ -63,4 +63,5 @@ export interface GridInitialStateCommunity { filter?: GridFilterInitialState; columns?: GridColumnsInitialState; preferencePanel?: GridPreferencePanelInitialState; + density?: GridDensityState; } diff --git a/packages/x-data-grid/src/models/props/DataGridProps.ts b/packages/x-data-grid/src/models/props/DataGridProps.ts index 90c2b031ccbea..13918cd9b5ab5 100644 --- a/packages/x-data-grid/src/models/props/DataGridProps.ts +++ b/packages/x-data-grid/src/models/props/DataGridProps.ts @@ -152,11 +152,6 @@ export interface DataGridPropsWithDefaultValues { * @default 3 */ columnThreshold: number; - /** - * Set the density of the Data Grid. - * @default "standard" - */ - density: GridDensity; /** * If `true`, column filters are disabled. * @default false @@ -408,6 +403,11 @@ export interface DataGridPropsWithoutDefaultValue; + /** + * Set the density of the Data Grid. + * @default "standard" + */ + density?: GridDensity; /** * Set the total number of rows, if it is different from the length of the value `rows` prop. * If some rows have children (for instance in the tree data), this number represents the amount of top level rows. @@ -561,6 +561,11 @@ export interface DataGridPropsWithoutDefaultValue; + /** + * Callback fired when the density changes. + * @param {GridDensity} density New density value. + */ + onDensityChange?: (density: GridDensity) => void; /** * Callback fired when a row is clicked. * Not called if the target clicked is an interactive element added by the built-in columns. diff --git a/packages/x-data-grid/src/tests/density.DataGrid.test.tsx b/packages/x-data-grid/src/tests/density.DataGrid.test.tsx new file mode 100644 index 0000000000000..6e71774e9651a --- /dev/null +++ b/packages/x-data-grid/src/tests/density.DataGrid.test.tsx @@ -0,0 +1,229 @@ +import * as React from 'react'; +import { spy } from 'sinon'; +import { createRenderer, fireEvent, screen } from '@mui-internal/test-utils'; +import { grid } from 'test/utils/helperFn'; +import { expect } from 'chai'; +import { DataGrid, DataGridProps, GridToolbar, gridClasses } from '@mui/x-data-grid'; +import { + COMFORTABLE_DENSITY_FACTOR, + COMPACT_DENSITY_FACTOR, +} from '../hooks/features/density/densitySelector'; + +const isJSDOM = /jsdom/.test(window.navigator.userAgent); + +describe(' - Density', () => { + const { render, clock } = createRenderer({ clock: 'fake' }); + + const baselineProps = { + autoHeight: isJSDOM, + rows: [ + { + id: 0, + brand: 'Nike', + }, + { + id: 1, + brand: 'Adidas', + }, + { + id: 2, + brand: 'Puma', + }, + ], + columns: [ + { + field: 'id', + }, + { + field: 'brand', + }, + ], + }; + + function expectHeight(value: number) { + expect(screen.getAllByRole('row')[1]).toHaveInlineStyle({ + maxHeight: `${Math.floor(value)}px`, + }); + + expect(getComputedStyle(screen.getAllByRole('gridcell')[1]).height).to.equal( + `${Math.floor(value)}px`, + ); + } + + before(function beforeHook() { + if (isJSDOM) { + // JSDOM seem to not support CSS variables properly and `height: var(--height)` ends up being `height: ''` + this.skip(); + } + }); + + describe('prop: `initialState.density`', () => { + it('should set the density to the value of initialState.density', () => { + const rowHeight = 30; + render( +
+ +
, + ); + + expectHeight(rowHeight * COMPACT_DENSITY_FACTOR); + }); + }); + + describe('prop: `density`', () => { + it('should set the density value using density prop', () => { + const rowHeight = 30; + render( +
+ +
, + ); + + expectHeight(rowHeight * COMPACT_DENSITY_FACTOR); + }); + + it('should allow to control the density from the prop using state', () => { + const rowHeight = 30; + + function Grid(props: Partial) { + return ( +
+ +
+ ); + } + + const { setProps, getByText } = render(); + + expectHeight(rowHeight); + + fireEvent.click(getByText('Density')); + clock.tick(100); + fireEvent.click(getByText('Compact')); + + // Not updated because of the controlled prop + expectHeight(rowHeight); + + // Explicitly update the prop + setProps({ density: 'compact' }); + clock.tick(200); + expectHeight(rowHeight * COMPACT_DENSITY_FACTOR); + }); + + it('should call `onDensityChange` prop when density gets updated', () => { + const onDensityChange = spy(); + function Test() { + return ( +
+ +
+ ); + } + const { getByText } = render(); + fireEvent.click(getByText('Density')); + fireEvent.click(getByText('Comfortable')); + expect(onDensityChange.callCount).to.equal(1); + expect(onDensityChange.firstCall.args[0]).to.equal('comfortable'); + }); + }); + + describe('density selection menu', () => { + it('should increase grid density when selecting compact density', () => { + const rowHeight = 30; + const { getByText } = render( +
+ +
, + ); + + fireEvent.click(getByText('Density')); + clock.tick(100); + fireEvent.click(getByText('Compact')); + + expectHeight(rowHeight * COMPACT_DENSITY_FACTOR); + }); + + it('should decrease grid density when selecting comfortable density', () => { + const rowHeight = 30; + const { getByText } = render( +
+ +
, + ); + + fireEvent.click(getByText('Density')); + fireEvent.click(getByText('Comfortable')); + + expectHeight(rowHeight * COMFORTABLE_DENSITY_FACTOR); + }); + + it('should increase grid density even if toolbar is not enabled', () => { + const rowHeight = 30; + render( +
+ +
, + ); + + expectHeight(rowHeight * COMPACT_DENSITY_FACTOR); + }); + + it('should decrease grid density even if toolbar is not enabled', () => { + const rowHeight = 30; + render( +
+ +
, + ); + + expectHeight(rowHeight * COMFORTABLE_DENSITY_FACTOR); + }); + + it('should apply to the root element a class corresponding to the current density', () => { + function Test(props: Partial) { + return ( +
+ +
+ ); + } + const { setProps } = render(); + expect(grid('root')).to.have.class(gridClasses['root--densityStandard']); + setProps({ density: 'compact' }); + expect(grid('root')).to.have.class(gridClasses['root--densityCompact']); + setProps({ density: 'comfortable' }); + expect(grid('root')).to.have.class(gridClasses['root--densityComfortable']); + }); + }); +}); diff --git a/packages/x-data-grid/src/tests/rows.DataGrid.test.tsx b/packages/x-data-grid/src/tests/rows.DataGrid.test.tsx index 0056600086a73..12a40be44b724 100644 --- a/packages/x-data-grid/src/tests/rows.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/rows.DataGrid.test.tsx @@ -35,7 +35,7 @@ import { } from 'test/utils/helperFn'; import Dialog from '@mui/material/Dialog'; -import { COMPACT_DENSITY_FACTOR } from '../hooks/features/density/useGridDensity'; +import { COMPACT_DENSITY_FACTOR } from '../hooks/features/density/densitySelector'; const isJSDOM = /jsdom/.test(window.navigator.userAgent); diff --git a/packages/x-data-grid/src/tests/toolbar.DataGrid.test.tsx b/packages/x-data-grid/src/tests/toolbar.DataGrid.test.tsx index 1839faa819dc0..cfba3fb2e0f15 100644 --- a/packages/x-data-grid/src/tests/toolbar.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/toolbar.DataGrid.test.tsx @@ -1,23 +1,13 @@ import * as React from 'react'; import { createRenderer, fireEvent, screen, act } from '@mui-internal/test-utils'; -import { getColumnHeadersTextContent, grid } from 'test/utils/helperFn'; +import { getColumnHeadersTextContent } from 'test/utils/helperFn'; import { expect } from 'chai'; -import { - DataGrid, - DataGridProps, - GridToolbar, - gridClasses, - GridColumnsManagementProps, -} from '@mui/x-data-grid'; -import { - COMFORTABLE_DENSITY_FACTOR, - COMPACT_DENSITY_FACTOR, -} from '../hooks/features/density/useGridDensity'; +import { DataGrid, GridToolbar, GridColumnsManagementProps } from '@mui/x-data-grid'; const isJSDOM = /jsdom/.test(window.navigator.userAgent); describe(' - Toolbar', () => { - const { render, clock } = createRenderer({ clock: 'fake' }); + const { render } = createRenderer({ clock: 'fake' }); const baselineProps = { autoHeight: isJSDOM, @@ -45,104 +35,6 @@ describe(' - Toolbar', () => { ], }; - describe('density selector', () => { - before(function beforeHook() { - if (isJSDOM) { - // JSDOM seem to not support CSS variables properly and `height: var(--height)` ends up being `height: ''` - this.skip(); - } - }); - - function expectHeight(value: number) { - expect(screen.getAllByRole('row')[1]).toHaveInlineStyle({ - maxHeight: `${Math.floor(value)}px`, - }); - - expect(getComputedStyle(screen.getAllByRole('gridcell')[1]).height).to.equal( - `${Math.floor(value)}px`, - ); - } - - it('should increase grid density when selecting compact density', () => { - const rowHeight = 30; - const { getByText } = render( -
- -
, - ); - - fireEvent.click(getByText('Density')); - clock.tick(100); - fireEvent.click(getByText('Compact')); - - expectHeight(rowHeight * COMPACT_DENSITY_FACTOR); - }); - - it('should decrease grid density when selecting comfortable density', () => { - const rowHeight = 30; - const { getByText } = render( -
- -
, - ); - - fireEvent.click(getByText('Density')); - fireEvent.click(getByText('Comfortable')); - - expectHeight(rowHeight * COMFORTABLE_DENSITY_FACTOR); - }); - - it('should increase grid density even if toolbar is not enabled', () => { - const rowHeight = 30; - render( -
- -
, - ); - - expectHeight(rowHeight * COMPACT_DENSITY_FACTOR); - }); - - it('should decrease grid density even if toolbar is not enabled', () => { - const rowHeight = 30; - render( -
- -
, - ); - - expectHeight(rowHeight * COMFORTABLE_DENSITY_FACTOR); - }); - - it('should apply to the root element a class corresponding to the current density', () => { - function Test(props: Partial) { - return ( -
- -
- ); - } - const { setProps } = render(); - expect(grid('root')).to.have.class(gridClasses['root--densityStandard']); - setProps({ density: 'compact' }); - expect(grid('root')).to.have.class(gridClasses['root--densityCompact']); - setProps({ density: 'comfortable' }); - expect(grid('root')).to.have.class(gridClasses['root--densityComfortable']); - }); - }); - describe('column selector', () => { it('should hide "id" column when hiding it from the column selector', () => { const { getByText } = render( diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index b0cddf8620c36..5641efdd4b4cf 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -20,6 +20,8 @@ { "name": "ColumnsManagementPropsOverrides", "kind": "Interface" }, { "name": "ColumnsPanelPropsOverrides", "kind": "Interface" }, { "name": "ColumnsStylesInterface", "kind": "Interface" }, + { "name": "COMFORTABLE_DENSITY_FACTOR", "kind": "Variable" }, + { "name": "COMPACT_DENSITY_FACTOR", "kind": "Variable" }, { "name": "createUseGridApiEventHandler", "kind": "Function" }, { "name": "CursorCoordinates", "kind": "Interface" }, { "name": "DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES", "kind": "Variable" }, @@ -251,8 +253,7 @@ { "name": "gridDensityFactorSelector", "kind": "Variable" }, { "name": "GridDensityOption", "kind": "Interface" }, { "name": "gridDensitySelector", "kind": "Variable" }, - { "name": "GridDensityState", "kind": "Interface" }, - { "name": "gridDensityValueSelector", "kind": "Variable" }, + { "name": "GridDensityState", "kind": "TypeAlias" }, { "name": "GridDetailPanelApi", "kind": "Interface" }, { "name": "gridDetailPanelExpandedRowIdsSelector", "kind": "Variable" }, { "name": "gridDetailPanelExpandedRowsContentCacheSelector", "kind": "Variable" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 1946f832da008..44386f2892c85 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -19,6 +19,8 @@ { "name": "ColumnMenuPropsOverrides", "kind": "Interface" }, { "name": "ColumnsManagementPropsOverrides", "kind": "Interface" }, { "name": "ColumnsPanelPropsOverrides", "kind": "Interface" }, + { "name": "COMFORTABLE_DENSITY_FACTOR", "kind": "Variable" }, + { "name": "COMPACT_DENSITY_FACTOR", "kind": "Variable" }, { "name": "createUseGridApiEventHandler", "kind": "Function" }, { "name": "CursorCoordinates", "kind": "Interface" }, { "name": "DATA_GRID_PRO_PROPS_DEFAULT_VALUES", "kind": "Variable" }, @@ -225,8 +227,7 @@ { "name": "gridDensityFactorSelector", "kind": "Variable" }, { "name": "GridDensityOption", "kind": "Interface" }, { "name": "gridDensitySelector", "kind": "Variable" }, - { "name": "GridDensityState", "kind": "Interface" }, - { "name": "gridDensityValueSelector", "kind": "Variable" }, + { "name": "GridDensityState", "kind": "TypeAlias" }, { "name": "GridDetailPanelApi", "kind": "Interface" }, { "name": "gridDetailPanelExpandedRowIdsSelector", "kind": "Variable" }, { "name": "gridDetailPanelExpandedRowsContentCacheSelector", "kind": "Variable" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index 484adb49bc66c..f2755ff699bee 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -19,6 +19,8 @@ { "name": "ColumnMenuPropsOverrides", "kind": "Interface" }, { "name": "ColumnsManagementPropsOverrides", "kind": "Interface" }, { "name": "ColumnsPanelPropsOverrides", "kind": "Interface" }, + { "name": "COMFORTABLE_DENSITY_FACTOR", "kind": "Variable" }, + { "name": "COMPACT_DENSITY_FACTOR", "kind": "Variable" }, { "name": "createUseGridApiEventHandler", "kind": "Function" }, { "name": "CursorCoordinates", "kind": "Interface" }, { "name": "DATA_GRID_PROPS_DEFAULT_VALUES", "kind": "Variable" }, @@ -211,8 +213,7 @@ { "name": "gridDensityFactorSelector", "kind": "Variable" }, { "name": "GridDensityOption", "kind": "Interface" }, { "name": "gridDensitySelector", "kind": "Variable" }, - { "name": "GridDensityState", "kind": "Interface" }, - { "name": "gridDensityValueSelector", "kind": "Variable" }, + { "name": "GridDensityState", "kind": "TypeAlias" }, { "name": "GridDimensions", "kind": "Interface" }, { "name": "GridDimensionsApi", "kind": "Interface" }, { "name": "gridDimensionsSelector", "kind": "Variable" }, From 51952b6c81495222e98f7ce416313f710fb5b82a Mon Sep 17 00:00:00 2001 From: Nora <72460825+noraleonte@users.noreply.github.com> Date: Tue, 19 Mar 2024 16:27:46 +0200 Subject: [PATCH 6/9] [docs] Finalize migration guide (#12501) --- .../migration/migration-charts-v6/migration-charts-v6.md | 2 +- .../migration-data-grid-v6/migration-data-grid-v6.md | 5 +++-- .../migration/migration-pickers-v6/migration-pickers-v6.md | 3 ++- .../migration-tree-view-v6/migration-tree-view-v6.md | 3 ++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/data/migration/migration-charts-v6/migration-charts-v6.md b/docs/data/migration/migration-charts-v6/migration-charts-v6.md index ca9b8da21fce2..01265fb8cb84a 100644 --- a/docs/data/migration/migration-charts-v6/migration-charts-v6.md +++ b/docs/data/migration/migration-charts-v6/migration-charts-v6.md @@ -10,7 +10,7 @@ productId: x-charts This is a reference guide for upgrading `@mui/x-charts` from v6 to v7. The change between v6 and v7 is mostly here to match the version with other MUI X packages. -Not big breaking changes are expected. +No big breaking changes are expected. ## Start using the new release diff --git a/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md b/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md index fd89b82c9ecda..28089d0600bb6 100644 --- a/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md +++ b/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md @@ -8,9 +8,10 @@ productId: x-data-grid

This guide describes the changes needed to migrate the Data Grid from v6 to v7.

- +This is a reference guide for upgrading `@mui/x-data-grid` from v6 to v7. +To read more about the changes from the new major, check out [the blog post about the release of MUI X v7](https://mui.com/blog/mui-x-v7-beta/). ## Start using the new release diff --git a/docs/data/migration/migration-pickers-v6/migration-pickers-v6.md b/docs/data/migration/migration-pickers-v6/migration-pickers-v6.md index d31db5d3b9299..92357cea938f3 100644 --- a/docs/data/migration/migration-pickers-v6/migration-pickers-v6.md +++ b/docs/data/migration/migration-pickers-v6/migration-pickers-v6.md @@ -10,7 +10,8 @@ productId: x-date-pickers ## Introduction -TBD +This is a reference guide for upgrading `@mui/x-date-pickers` from v6 to v7. +To read more about the changes from the new major, check out [the blog post about the release of MUI X v7](https://mui.com/blog/mui-x-v7-beta/). ## Start using the new release diff --git a/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md b/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md index bd4557049c723..9e34405bb7968 100644 --- a/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md +++ b/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md @@ -10,7 +10,8 @@ productId: x-tree-view ## Introduction -TBD +This is a reference guide for upgrading `@mui/x-tree-view` from v6 to v7. +To read more about the changes from the new major, check out [the blog post about the release of MUI X v7](https://mui.com/blog/mui-x-v7-beta/). ## Start using the beta release From 84b4e02c69e05921b73dd1cf2a17f0e949afc333 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Tue, 19 Mar 2024 19:50:29 +0500 Subject: [PATCH 7/9] [codemod] Add a codemod and update the grid migration guide (#12488) --- .../migration-data-grid-v6.md | 8 ++--- packages/x-codemod/README.md | 20 +++++++++++ .../data-grid/preset-safe/actual.spec.js | 6 ++++ .../data-grid/preset-safe/expected.spec.js | 3 +- .../src/v7.0.0/data-grid/preset-safe/index.ts | 2 ++ .../actual.spec.js | 34 +++++++++++++++++++ .../expected.spec.js | 17 ++++++++++ .../index.ts | 22 ++++++++++++ ...ve-stabilized-experimentalFeatures.test.ts | 27 +++++++++++++++ .../rename-cell-selection-props/index.ts | 2 ++ 10 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 packages/x-codemod/src/v7.0.0/data-grid/remove-stabilized-experimentalFeatures/actual.spec.js create mode 100644 packages/x-codemod/src/v7.0.0/data-grid/remove-stabilized-experimentalFeatures/expected.spec.js create mode 100644 packages/x-codemod/src/v7.0.0/data-grid/remove-stabilized-experimentalFeatures/index.ts create mode 100644 packages/x-codemod/src/v7.0.0/data-grid/remove-stabilized-experimentalFeatures/remove-stabilized-experimentalFeatures.test.ts diff --git a/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md b/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md index 28089d0600bb6..6e0e5494e6fe0 100644 --- a/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md +++ b/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md @@ -122,7 +122,7 @@ As a result, the following changes have been made: ### Removed props -- The deprecated props `components` and `componentsProps` have been removed. Use `slots` and `slotProps` instead. See [components section](/x/react-data-grid/components/) for more details. +- ✅ The deprecated props `components` and `componentsProps` have been removed. Use `slots` and `slotProps` instead. See [components section](/x/react-data-grid/components/) for more details. - The `slots.preferencesPanel` slot and the `slotProps.preferencesPanel` prop were removed. Use `slots.panel` and `slotProps.panel` instead. - The `getOptionValue` and `getOptionLabel` props were removed from the following components: @@ -145,7 +145,7 @@ As a result, the following changes have been made: }; ``` -- Some feature flags were removed from the `experimentalFeatures` prop. These features are now stable and enabled by default: +- ✅ Some feature flags were removed from the `experimentalFeatures` prop. These features are now stable and enabled by default: - [`columnGrouping`](/x/react-data-grid/column-groups/) - [`clipboardPaste`](/x/react-data-grid/clipboard/#clipboard-paste) - [`lazyLoading`](/x/react-data-grid/row-updates/#lazy-loading) @@ -340,7 +340,7 @@ See the [Direct state access](/x/react-data-grid/state/#direct-selector-access) ### Clipboard -- The clipboard related exports `ignoreValueFormatterDuringExport` and `splitClipboardPastedText` are not anymore prefixed with `unstable_`. +- ✅ The clipboard related exports `ignoreValueFormatterDuringExport` and `splitClipboardPastedText` are not anymore prefixed with `unstable_`. ### Print export @@ -430,7 +430,7 @@ See the [Direct state access](/x/react-data-grid/state/#direct-selector-access) ### Accessibility -- The `ariaV7` experimental flag has been removed and the Data Grid now uses the improved accessibility implementation by default. +- ✅ The `ariaV7` experimental flag has been removed and the Data Grid now uses the improved accessibility implementation by default. If you were using the `ariaV7` flag, you can remove it from the `experimentalFeatures` prop: ```diff diff --git a/packages/x-codemod/README.md b/packages/x-codemod/README.md index 5894102fcca66..9f9e6e967890a 100644 --- a/packages/x-codemod/README.md +++ b/packages/x-codemod/README.md @@ -166,6 +166,7 @@ The list includes these transformers - [`rename-components-to-slots-data-grid`](#rename-components-to-slots-data-grid) - [`rename-cell-selection-props`](#rename-cell-selection-props) +- [`remove-stabilized-v7-experimentalFeatures`](#remove-stabilized-v7-experimentalFeatures) #### `rename-components-to-slots-data-grid` @@ -205,6 +206,25 @@ Rename props related to `cellSelection` feature. npx @mui/x-codemod@next v7.0.0/data-grid/rename-cell-selection-props ``` +#### `remove-stabilized-v7-experimentalFeatures` + +Remove feature flags for stabilized `experimentalFeatures`. + +```diff + +``` + +```bash +npx @mui/x-codemod@next v7.0.0/data-grid/remove-stabilized-experimentalFeatures +``` + ### Tree View codemods #### `preset-safe` for tree view v7.0.0 diff --git a/packages/x-codemod/src/v7.0.0/data-grid/preset-safe/actual.spec.js b/packages/x-codemod/src/v7.0.0/data-grid/preset-safe/actual.spec.js index 223be3fca8717..ed4ea3a7781f8 100644 --- a/packages/x-codemod/src/v7.0.0/data-grid/preset-safe/actual.spec.js +++ b/packages/x-codemod/src/v7.0.0/data-grid/preset-safe/actual.spec.js @@ -26,6 +26,12 @@ export default function App() { checked: 'true', }, }} + experimentalFeatures={{ + lazyLoading: true, + ariaV7: true, + columnGrouping: true, + clipboardPaste: true, + }} /> + }} /> + + + + + + ); +} + +export default App; diff --git a/packages/x-codemod/src/v7.0.0/data-grid/remove-stabilized-experimentalFeatures/expected.spec.js b/packages/x-codemod/src/v7.0.0/data-grid/remove-stabilized-experimentalFeatures/expected.spec.js new file mode 100644 index 0000000000000..85baca51c531e --- /dev/null +++ b/packages/x-codemod/src/v7.0.0/data-grid/remove-stabilized-experimentalFeatures/expected.spec.js @@ -0,0 +1,17 @@ +import * as React from 'react'; +import { DataGrid } from '@mui/x-data-grid'; +import { DataGridPro } from '@mui/x-data-grid-pro'; +import { DataGridPremium } from '@mui/x-data-grid-premium'; + +function App() { + return ( + + + + + + + ); +} + +export default App; diff --git a/packages/x-codemod/src/v7.0.0/data-grid/remove-stabilized-experimentalFeatures/index.ts b/packages/x-codemod/src/v7.0.0/data-grid/remove-stabilized-experimentalFeatures/index.ts new file mode 100644 index 0000000000000..9b42c64500b14 --- /dev/null +++ b/packages/x-codemod/src/v7.0.0/data-grid/remove-stabilized-experimentalFeatures/index.ts @@ -0,0 +1,22 @@ +import removeObjectProperty from '../../../util/removeObjectProperty'; +import { JsCodeShiftAPI, JsCodeShiftFileInfo } from '../../../types'; + +const componentsNames = ['DataGrid', 'DataGridPro', 'DataGridPremium']; +const propName = 'experimentalFeatures'; +const propKeys = ['columnGrouping', 'clipboardPaste', 'lazyLoading', 'ariaV7']; + +export default function transformer(file: JsCodeShiftFileInfo, api: JsCodeShiftAPI, options: any) { + const j = api.jscodeshift; + const root = j(file.source); + + const printOptions = options.printOptions || { + quote: 'single', + trailingComma: true, + }; + + propKeys.forEach((propKey) => { + removeObjectProperty({ root, j, propName, componentsNames, propKey }); + }); + + return root.toSource(printOptions); +} diff --git a/packages/x-codemod/src/v7.0.0/data-grid/remove-stabilized-experimentalFeatures/remove-stabilized-experimentalFeatures.test.ts b/packages/x-codemod/src/v7.0.0/data-grid/remove-stabilized-experimentalFeatures/remove-stabilized-experimentalFeatures.test.ts new file mode 100644 index 0000000000000..d5610209aafd4 --- /dev/null +++ b/packages/x-codemod/src/v7.0.0/data-grid/remove-stabilized-experimentalFeatures/remove-stabilized-experimentalFeatures.test.ts @@ -0,0 +1,27 @@ +import path from 'path'; +import { expect } from 'chai'; +import jscodeshift from 'jscodeshift'; +import transform from '.'; +import readFile from '../../../util/readFile'; + +function read(fileName) { + return readFile(path.join(__dirname, fileName)); +} + +describe('v7.0.0/data-grid', () => { + describe('remove-stabilized-experimentalFeatures', () => { + it('transforms props as needed', () => { + const actual = transform({ source: read('./actual.spec.js') }, { jscodeshift }, {}); + + const expected = read('./expected.spec.js'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + + it('should be idempotent', () => { + const actual = transform({ source: read('./expected.spec.js') }, { jscodeshift }, {}); + + const expected = read('./expected.spec.js'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + }); +}); diff --git a/packages/x-codemod/src/v7.0.0/data-grid/rename-cell-selection-props/index.ts b/packages/x-codemod/src/v7.0.0/data-grid/rename-cell-selection-props/index.ts index 8d61dff5088b2..74c0d48003d94 100644 --- a/packages/x-codemod/src/v7.0.0/data-grid/rename-cell-selection-props/index.ts +++ b/packages/x-codemod/src/v7.0.0/data-grid/rename-cell-selection-props/index.ts @@ -6,6 +6,8 @@ const props = { unstable_cellSelection: 'cellSelection', unstable_cellSelectionModel: 'cellSelectionModel', unstable_onCellSelectionModelChange: 'onCellSelectionModelChange', + unstable_ignoreValueFormatterDuringExport: 'ignoreValueFormatterDuringExport', + unstable_splitClipboardPastedText: 'splitClipboardPastedText', }; export default function transformer(file: JsCodeShiftFileInfo, api: JsCodeShiftAPI, options: any) { From 1a54927f5c208eb24fb95767145136859d36f1d6 Mon Sep 17 00:00:00 2001 From: Andrew Cherniavskii Date: Tue, 19 Mar 2024 17:19:08 +0100 Subject: [PATCH 8/9] [docs] Update branch name and tags (#12498) --- docs/constants.js | 2 +- .../charts/getting-started/getting-started.md | 8 ++- .../getting-started/getting-started.md | 4 -- docs/data/data-grid/localization/data.json | 66 +++++++++---------- .../getting-started/getting-started.md | 4 -- docs/data/date-pickers/localization/data.json | 66 +++++++++---------- docs/data/introduction/support/support.md | 4 +- .../migration-charts-v6.md | 4 +- .../migration-data-grid-v6.md | 16 ++--- .../migration-pickers-v6.md | 14 ++-- .../migration-tree-view-v6.md | 6 +- .../getting-started/getting-started.md | 10 +-- docs/pages/_app.js | 2 +- .../DataGridInstallationInstructions.js | 6 +- .../PickersInstallationInstructions.js | 4 +- packages/x-codemod/README.md | 32 ++++----- scripts/README.md | 2 +- scripts/releaseChangelog.mjs | 2 +- 18 files changed, 119 insertions(+), 133 deletions(-) diff --git a/docs/constants.js b/docs/constants.js index 8b2dbe4c383c9..7a90120b71745 100644 --- a/docs/constants.js +++ b/docs/constants.js @@ -2,5 +2,5 @@ module.exports = { SOURCE_CODE_REPO: 'https://github.com/mui/mui-x', - SOURCE_GITHUB_BRANCH: 'next', // #default-branch-switch + SOURCE_GITHUB_BRANCH: 'master', // #default-branch-switch }; diff --git a/docs/data/charts/getting-started/getting-started.md b/docs/data/charts/getting-started/getting-started.md index 45880b4fe07b2..933206fe3af01 100644 --- a/docs/data/charts/getting-started/getting-started.md +++ b/docs/data/charts/getting-started/getting-started.md @@ -13,17 +13,19 @@ packageName: '@mui/x-charts' Run one of the following commands to add the MUI X Charts to your project: + + ```bash npm -npm install @mui/x-charts@next +npm install @mui/x-charts ``` ```bash yarn -yarn add @mui/x-charts@next +yarn add @mui/x-charts ``` ```bash pnpm -pnpm add @mui/x-charts@next +pnpm add @mui/x-charts ``` diff --git a/docs/data/data-grid/getting-started/getting-started.md b/docs/data/data-grid/getting-started/getting-started.md index 4d9fbcef475ba..466ff5502429b 100644 --- a/docs/data/data-grid/getting-started/getting-started.md +++ b/docs/data/data-grid/getting-started/getting-started.md @@ -8,10 +8,6 @@ Using your favorite package manager, install `@mui/x-data-grid-pro` or `@mui/x-d -:::warning -The `next` tag is used to download the latest v7 **pre-release** version. -::: - {{"component": "modules/components/DataGridInstallationInstructions.js"}} The Data Grid package has a peer dependency on `@mui/material`. diff --git a/docs/data/data-grid/localization/data.json b/docs/data/data-grid/localization/data.json index 2d51946c58760..3ee05aff82c5c 100644 --- a/docs/data/data-grid/localization/data.json +++ b/docs/data/data-grid/localization/data.json @@ -5,7 +5,7 @@ "localeName": "Arabic (Sudan)", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/arSD.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/arSD.ts" }, { "languageTag": "be-BY", @@ -13,7 +13,7 @@ "localeName": "Belarusian", "missingKeysCount": 29, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/beBY.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/beBY.ts" }, { "languageTag": "bg-BG", @@ -21,7 +21,7 @@ "localeName": "Bulgarian", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/bgBG.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/bgBG.ts" }, { "languageTag": "zh-HK", @@ -29,7 +29,7 @@ "localeName": "Chinese (Hong Kong)", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/zhHK.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/zhHK.ts" }, { "languageTag": "zh-CN", @@ -37,7 +37,7 @@ "localeName": "Chinese (Simplified)", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/zhCN.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/zhCN.ts" }, { "languageTag": "zh-TW", @@ -45,7 +45,7 @@ "localeName": "Chinese (Taiwan)", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/zhTW.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/zhTW.ts" }, { "languageTag": "hr-HR", @@ -53,7 +53,7 @@ "localeName": "Croatian", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/hrHR.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/hrHR.ts" }, { "languageTag": "cs-CZ", @@ -61,7 +61,7 @@ "localeName": "Czech", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/csCZ.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/csCZ.ts" }, { "languageTag": "da-DK", @@ -69,7 +69,7 @@ "localeName": "Danish", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/daDK.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/daDK.ts" }, { "languageTag": "nl-NL", @@ -77,7 +77,7 @@ "localeName": "Dutch", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/nlNL.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/nlNL.ts" }, { "languageTag": "fi-FI", @@ -85,7 +85,7 @@ "localeName": "Finnish", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/fiFI.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/fiFI.ts" }, { "languageTag": "fr-FR", @@ -93,7 +93,7 @@ "localeName": "French", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/frFR.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/frFR.ts" }, { "languageTag": "de-DE", @@ -101,7 +101,7 @@ "localeName": "German", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/deDE.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/deDE.ts" }, { "languageTag": "el-GR", @@ -109,7 +109,7 @@ "localeName": "Greek", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/elGR.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/elGR.ts" }, { "languageTag": "he-IL", @@ -117,7 +117,7 @@ "localeName": "Hebrew", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/heIL.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/heIL.ts" }, { "languageTag": "hu-HU", @@ -125,7 +125,7 @@ "localeName": "Hungarian", "missingKeysCount": 5, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/huHU.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/huHU.ts" }, { "languageTag": "it-IT", @@ -133,7 +133,7 @@ "localeName": "Italian", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/itIT.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/itIT.ts" }, { "languageTag": "ja-JP", @@ -141,7 +141,7 @@ "localeName": "Japanese", "missingKeysCount": 0, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/jaJP.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/jaJP.ts" }, { "languageTag": "ko-KR", @@ -149,7 +149,7 @@ "localeName": "Korean", "missingKeysCount": 30, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/koKR.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/koKR.ts" }, { "languageTag": "nb-NO", @@ -157,7 +157,7 @@ "localeName": "Norwegian (Bokmål)", "missingKeysCount": 28, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/nbNO.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/nbNO.ts" }, { "languageTag": "fa-IR", @@ -165,7 +165,7 @@ "localeName": "Persian", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/faIR.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/faIR.ts" }, { "languageTag": "pl-PL", @@ -173,7 +173,7 @@ "localeName": "Polish", "missingKeysCount": 30, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/plPL.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/plPL.ts" }, { "languageTag": "pt-PT", @@ -181,7 +181,7 @@ "localeName": "Portuguese", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/ptPT.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/ptPT.ts" }, { "languageTag": "pt-BR", @@ -189,7 +189,7 @@ "localeName": "Portuguese (Brazil)", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/ptBR.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/ptBR.ts" }, { "languageTag": "ro-RO", @@ -197,7 +197,7 @@ "localeName": "Romanian", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/roRO.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/roRO.ts" }, { "languageTag": "ru-RU", @@ -205,7 +205,7 @@ "localeName": "Russian", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/ruRU.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/ruRU.ts" }, { "languageTag": "sk-SK", @@ -213,7 +213,7 @@ "localeName": "Slovak", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/skSK.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/skSK.ts" }, { "languageTag": "es-ES", @@ -221,7 +221,7 @@ "localeName": "Spanish", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/esES.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/esES.ts" }, { "languageTag": "sv-SE", @@ -229,7 +229,7 @@ "localeName": "Swedish", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/svSE.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/svSE.ts" }, { "languageTag": "tr-TR", @@ -237,7 +237,7 @@ "localeName": "Turkish", "missingKeysCount": 18, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/trTR.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/trTR.ts" }, { "languageTag": "uk-UA", @@ -245,7 +245,7 @@ "localeName": "Ukrainian", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/ukUA.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/ukUA.ts" }, { "languageTag": "ur-PK", @@ -253,7 +253,7 @@ "localeName": "Urdu (Pakistan)", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/urPK.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/urPK.ts" }, { "languageTag": "vi-VN", @@ -261,6 +261,6 @@ "localeName": "Vietnamese", "missingKeysCount": 3, "totalKeysCount": 117, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-data-grid/src/locales/viVN.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/viVN.ts" } ] diff --git a/docs/data/date-pickers/getting-started/getting-started.md b/docs/data/date-pickers/getting-started/getting-started.md index ce67d380a66f1..ac7925ea7c394 100644 --- a/docs/data/date-pickers/getting-started/getting-started.md +++ b/docs/data/date-pickers/getting-started/getting-started.md @@ -20,10 +20,6 @@ Using your favorite package manager, install: -:::warning -The `next` tag is used to download the latest v7 **pre-release** version. -::: - {{"component": "modules/components/PickersInstallationInstructions.js"}} :::info diff --git a/docs/data/date-pickers/localization/data.json b/docs/data/date-pickers/localization/data.json index 1ba2f8bb68bbe..52aa6c3832b94 100644 --- a/docs/data/date-pickers/localization/data.json +++ b/docs/data/date-pickers/localization/data.json @@ -5,7 +5,7 @@ "localeName": "Basque", "missingKeysCount": 13, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/eu.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/eu.ts" }, { "languageTag": "be-BY", @@ -13,7 +13,7 @@ "localeName": "Belarusian", "missingKeysCount": 15, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/beBY.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/beBY.ts" }, { "languageTag": "ca-ES", @@ -21,7 +21,7 @@ "localeName": "Catalan", "missingKeysCount": 14, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/caES.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/caES.ts" }, { "languageTag": "zh-HK", @@ -29,7 +29,7 @@ "localeName": "Chinese (Hong Kong)", "missingKeysCount": 14, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/zhHK.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/zhHK.ts" }, { "languageTag": "zh-CN", @@ -37,7 +37,7 @@ "localeName": "Chinese (Simplified)", "missingKeysCount": 0, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/zhCN.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/zhCN.ts" }, { "languageTag": "cs-CZ", @@ -45,7 +45,7 @@ "localeName": "Czech", "missingKeysCount": 15, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/csCZ.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/csCZ.ts" }, { "languageTag": "da-DK", @@ -53,7 +53,7 @@ "localeName": "Danish", "missingKeysCount": 14, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/daDK.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/daDK.ts" }, { "languageTag": "nl-NL", @@ -61,7 +61,7 @@ "localeName": "Dutch", "missingKeysCount": 14, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/nlNL.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/nlNL.ts" }, { "languageTag": "fi-FI", @@ -69,7 +69,7 @@ "localeName": "Finnish", "missingKeysCount": 14, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/fiFI.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/fiFI.ts" }, { "languageTag": "fr-FR", @@ -77,7 +77,7 @@ "localeName": "French", "missingKeysCount": 15, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/frFR.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/frFR.ts" }, { "languageTag": "de-DE", @@ -85,7 +85,7 @@ "localeName": "German", "missingKeysCount": 13, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/deDE.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/deDE.ts" }, { "languageTag": "el-GR", @@ -93,7 +93,7 @@ "localeName": "Greek", "missingKeysCount": 14, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/elGR.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/elGR.ts" }, { "languageTag": "he-IL", @@ -101,7 +101,7 @@ "localeName": "Hebrew", "missingKeysCount": 14, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/heIL.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/heIL.ts" }, { "languageTag": "hu-HU", @@ -109,7 +109,7 @@ "localeName": "Hungarian", "missingKeysCount": 14, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/huHU.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/huHU.ts" }, { "languageTag": "is-IS", @@ -117,7 +117,7 @@ "localeName": "Icelandic", "missingKeysCount": 14, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/isIS.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/isIS.ts" }, { "languageTag": "it-IT", @@ -125,7 +125,7 @@ "localeName": "Italian", "missingKeysCount": 15, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/itIT.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/itIT.ts" }, { "languageTag": "ja-JP", @@ -133,7 +133,7 @@ "localeName": "Japanese", "missingKeysCount": 0, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/jaJP.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/jaJP.ts" }, { "languageTag": "kz-KZ", @@ -141,7 +141,7 @@ "localeName": "Kazakh", "missingKeysCount": 15, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/kzKZ.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/kzKZ.ts" }, { "languageTag": "ko-KR", @@ -149,7 +149,7 @@ "localeName": "Korean", "missingKeysCount": 14, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/koKR.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/koKR.ts" }, { "languageTag": "mk", @@ -157,7 +157,7 @@ "localeName": "Macedonian", "missingKeysCount": 13, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/mk.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/mk.ts" }, { "languageTag": "nb-NO", @@ -165,7 +165,7 @@ "localeName": "Norwegian (Bokmål)", "missingKeysCount": 14, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/nbNO.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/nbNO.ts" }, { "languageTag": "fa-IR", @@ -173,7 +173,7 @@ "localeName": "Persian", "missingKeysCount": 14, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/faIR.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/faIR.ts" }, { "languageTag": "pl-PL", @@ -181,7 +181,7 @@ "localeName": "Polish", "missingKeysCount": 22, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/plPL.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/plPL.ts" }, { "languageTag": "pt-BR", @@ -189,7 +189,7 @@ "localeName": "Portuguese (Brazil)", "missingKeysCount": 14, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/ptBR.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/ptBR.ts" }, { "languageTag": "ro-RO", @@ -197,7 +197,7 @@ "localeName": "Romanian", "missingKeysCount": 14, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/roRO.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/roRO.ts" }, { "languageTag": "ru-RU", @@ -205,7 +205,7 @@ "localeName": "Russian", "missingKeysCount": 14, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/ruRU.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/ruRU.ts" }, { "languageTag": "sk-SK", @@ -213,7 +213,7 @@ "localeName": "Slovak", "missingKeysCount": 15, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/skSK.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/skSK.ts" }, { "languageTag": "es-ES", @@ -221,7 +221,7 @@ "localeName": "Spanish", "missingKeysCount": 13, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/esES.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/esES.ts" }, { "languageTag": "sv-SE", @@ -229,7 +229,7 @@ "localeName": "Swedish", "missingKeysCount": 22, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/svSE.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/svSE.ts" }, { "languageTag": "tr-TR", @@ -237,7 +237,7 @@ "localeName": "Turkish", "missingKeysCount": 14, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/trTR.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/trTR.ts" }, { "languageTag": "uk-UA", @@ -245,7 +245,7 @@ "localeName": "Ukrainian", "missingKeysCount": 14, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/ukUA.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/ukUA.ts" }, { "languageTag": "ur-PK", @@ -253,7 +253,7 @@ "localeName": "Urdu (Pakistan)", "missingKeysCount": 22, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/urPK.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/urPK.ts" }, { "languageTag": "vi-VN", @@ -261,6 +261,6 @@ "localeName": "Vietnamese", "missingKeysCount": 14, "totalKeysCount": 50, - "githubLink": "https://github.com/mui/mui-x/blob/next/packages/x-date-pickers/src/locales/viVN.ts" + "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/viVN.ts" } ] diff --git a/docs/data/introduction/support/support.md b/docs/data/introduction/support/support.md index ce015dcceb0d8..b13f6488b9f61 100644 --- a/docs/data/introduction/support/support.md +++ b/docs/data/introduction/support/support.md @@ -48,8 +48,10 @@ You can browse the documentation, find an example close to your use case, and th #### Use starter templates You can use a starter template to build a reproduction case with: + -- A minimal Data Grid [TypeScript template](https://stackblitz.com/github/mui/mui-x/tree/next/bug-reproductions/x-data-grid?file=src/index.tsx) + +- A minimal Data Grid [TypeScript template](https://stackblitz.com/github/mui/mui-x/tree/master/bug-reproductions/x-data-grid?file=src/index.tsx) - A plain React [JavaScript](https://stackblitz.com/github/stackblitz/starters/tree/main/react) or [TypeScript](https://stackblitz.com/github/stackblitz/starters/tree/main/react-ts) template ## Stack Overflow diff --git a/docs/data/migration/migration-charts-v6/migration-charts-v6.md b/docs/data/migration/migration-charts-v6/migration-charts-v6.md index 01265fb8cb84a..f5fb3d31b08d0 100644 --- a/docs/data/migration/migration-charts-v6/migration-charts-v6.md +++ b/docs/data/migration/migration-charts-v6/migration-charts-v6.md @@ -14,11 +14,11 @@ No big breaking changes are expected. ## Start using the new release -In `package.json`, change the version of the charts package to `next`. +In `package.json`, change the version of the charts package to `^7.0.0`. ```diff -"@mui/x-charts": "6.x.x", -+"@mui/x-charts": "next", ++"@mui/x-charts": "^7.0.0", ``` ## Update `@mui/material` package diff --git a/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md b/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md index 6e0e5494e6fe0..0c939947a016d 100644 --- a/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md +++ b/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md @@ -4,8 +4,6 @@ productId: x-data-grid # Migration from v6 to v7 - -

This guide describes the changes needed to migrate the Data Grid from v6 to v7.

## Introduction @@ -15,15 +13,15 @@ To read more about the changes from the new major, check out [the blog post abou ## Start using the new release -In `package.json`, change the version of the data grid package to `next`. +In `package.json`, change the version of the data grid package to `^7.0.0`. ```diff -"@mui/x-data-grid": "6.x.x", -+"@mui/x-data-grid": "next", ++"@mui/x-data-grid": "^7.0.0", -"@mui/x-data-grid-pro": "6.x.x", -+"@mui/x-data-grid-pro": "next", ++"@mui/x-data-grid-pro": "^7.0.0", -"@mui/x-data-grid-premium": "6.x.x", -+"@mui/x-data-grid-premium": "next", ++"@mui/x-data-grid-premium": "^7.0.0", ``` Since v7 is a major release, it contains changes that affect the public API. @@ -49,7 +47,7 @@ If you have `@mui/x-license-pro` in the `dependencies` section of your `package. ```diff -"@mui/x-license-pro": "6.x.x", -+"@mui/x-license": "next", ++"@mui/x-license": "^7.0.0", ``` ## Run codemods @@ -61,10 +59,10 @@ You can either run it on a specific file, folder, or your entire codebase when c ```bash // Data Grid specific -npx @mui/x-codemod@next v7.0.0/data-grid/preset-safe +npx @mui/x-codemod v7.0.0/data-grid/preset-safe // Target other MUI X components as well -npx @mui/x-codemod@next v7.0.0/preset-safe +npx @mui/x-codemod v7.0.0/preset-safe ``` :::info diff --git a/docs/data/migration/migration-pickers-v6/migration-pickers-v6.md b/docs/data/migration/migration-pickers-v6/migration-pickers-v6.md index 92357cea938f3..07220a0af7850 100644 --- a/docs/data/migration/migration-pickers-v6/migration-pickers-v6.md +++ b/docs/data/migration/migration-pickers-v6/migration-pickers-v6.md @@ -4,8 +4,6 @@ productId: x-date-pickers # Migration from v6 to v7 - -

This guide describes the changes needed to migrate the Date and Time Pickers from v6 to v7.

## Introduction @@ -15,11 +13,11 @@ To read more about the changes from the new major, check out [the blog post abou ## Start using the new release -In `package.json`, change the version of the date pickers package to `next`. +In `package.json`, change the version of the date pickers package to `^7.0.0`. ```diff -"@mui/x-date-pickers": "6.x.x", -+"@mui/x-date-pickers": "next", ++"@mui/x-date-pickers": "^7.0.0", ``` Since `v7` is a major release, it contains changes that affect the public API. @@ -45,7 +43,7 @@ If you have `@mui/x-license-pro` in the `dependencies` section of your `package. ```diff -"@mui/x-license-pro": "6.x.x", -+"@mui/x-license": "next", ++"@mui/x-license": "^7.0.0", ``` ## Run codemods @@ -56,10 +54,10 @@ You can either run it on a specific file, folder, or your entire codebase when c ```bash // Date and Time Pickers specific -npx @mui/x-codemod@next v7.0.0/pickers/preset-safe +npx @mui/x-codemod v7.0.0/pickers/preset-safe // Target Data Grid as well -npx @mui/x-codemod@next v7.0.0/preset-safe +npx @mui/x-codemod v7.0.0/preset-safe ``` :::info @@ -107,7 +105,7 @@ And are removed from the v7. If not already done, this modification can be handled by the codemod ```bash -npx @mui/x-codemod@next v7.0.0/pickers/ +npx @mui/x-codemod v7.0.0/pickers/ ``` Take a look at [the RFC](https://github.com/mui/material-ui/issues/33416) for more information. diff --git a/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md b/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md index 9e34405bb7968..55ff88eac6b3a 100644 --- a/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md +++ b/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md @@ -4,8 +4,6 @@ productId: x-tree-view # Migration from v6 to v7 - -

This guide describes the changes needed to migrate the Tree View from v6 to v7.

## Introduction @@ -15,11 +13,11 @@ To read more about the changes from the new major, check out [the blog post abou ## Start using the beta release -In `package.json`, change the version of the tree view package to `next`. +In `package.json`, change the version of the tree view package to `^7.0.0`. ```diff -"@mui/x-tree-view": "6.x.x", -+"@mui/x-tree-view": "next", ++"@mui/x-tree-view": "^7.0.0", ``` ## Update `@mui/material` package diff --git a/docs/data/tree-view/getting-started/getting-started.md b/docs/data/tree-view/getting-started/getting-started.md index 9c1f3403a46ce..3ae405a02a8d7 100644 --- a/docs/data/tree-view/getting-started/getting-started.md +++ b/docs/data/tree-view/getting-started/getting-started.md @@ -17,21 +17,17 @@ Using your favorite package manager, install `@mui/x-tree-view`: -:::warning -The `next` tag is used to download the latest v7 **pre-release** version. -::: - ```bash npm -npm install @mui/x-tree-view@next +npm install @mui/x-tree-view ``` ```bash yarn -yarn add @mui/x-tree-view@next +yarn add @mui/x-tree-view ``` ```bash pnpm -pnpm add @mui/x-tree-view@next +pnpm add @mui/x-tree-view ``` diff --git a/docs/pages/_app.js b/docs/pages/_app.js index 76826c3c75eff..06e3770e405c7 100644 --- a/docs/pages/_app.js +++ b/docs/pages/_app.js @@ -30,7 +30,7 @@ function getMuiPackageVersion(packageName, commitRef) { // #default-branch-switch // Use the "latest" npm tag for the master git branch // Use the "next" npm tag for the next git branch - return 'next'; + return 'latest'; } const shortSha = commitRef.slice(0, 8); return `https://pkg.csb.dev/mui/mui-x/commit/${shortSha}/@mui/${packageName}`; diff --git a/docs/src/modules/components/DataGridInstallationInstructions.js b/docs/src/modules/components/DataGridInstallationInstructions.js index 030004edd155f..fd41a7bddc3e2 100644 --- a/docs/src/modules/components/DataGridInstallationInstructions.js +++ b/docs/src/modules/components/DataGridInstallationInstructions.js @@ -4,9 +4,9 @@ import InstallationInstructions from './InstallationInstructions'; // #default-branch-switch const packages = { - Community: '@mui/x-data-grid@next', - Pro: '@mui/x-data-grid-pro@next', - Premium: '@mui/x-data-grid-premium@next', + Community: '@mui/x-data-grid', + Pro: '@mui/x-data-grid-pro', + Premium: '@mui/x-data-grid-premium', }; export default function DataGridInstallationInstructions() { diff --git a/docs/src/modules/components/PickersInstallationInstructions.js b/docs/src/modules/components/PickersInstallationInstructions.js index 81a2cf652e9ef..2651d0ca1158c 100644 --- a/docs/src/modules/components/PickersInstallationInstructions.js +++ b/docs/src/modules/components/PickersInstallationInstructions.js @@ -4,8 +4,8 @@ import InstallationInstructions from './InstallationInstructions'; // #default-branch-switch const packages = { - Community: '@mui/x-date-pickers@next', - Pro: '@mui/x-date-pickers-pro@next', + Community: '@mui/x-date-pickers', + Pro: '@mui/x-date-pickers-pro', }; const peerDependency = { diff --git a/packages/x-codemod/README.md b/packages/x-codemod/README.md index 9f9e6e967890a..3a9ac6e08276c 100644 --- a/packages/x-codemod/README.md +++ b/packages/x-codemod/README.md @@ -13,7 +13,7 @@ This repository contains a collection of codemod scripts based for use with ```bash -npx @mui/x-codemod@next +npx @mui/x-codemod Applies a `@mui/x-codemod` to the specified paths @@ -29,8 +29,8 @@ Options: --jscodeshift Pass options directly to jscodeshift [array] Examples: - npx @mui/x-codemod@latest v6.0.0/preset-safe src - npx @mui/x-codemod@latest v6.0.0/component-rename-prop src -- + npx @mui/x-codemod v6.0.0/preset-safe src + npx @mui/x-codemod v6.0.0/component-rename-prop src -- --component=DataGrid --from=prop --to=newProp ``` @@ -40,9 +40,9 @@ To pass more options directly to jscodeshift, use `--jscodeshift=...`. For examp ```bash // single option -npx @mui/x-codemod@next --jscodeshift=--run-in-band +npx @mui/x-codemod --jscodeshift=--run-in-band // multiple options -npx @mui/x-codemod@next --jscodeshift=--cpus=1 --jscodeshift=--print --jscodeshift=--dry --jscodeshift=--verbose=2 +npx @mui/x-codemod --jscodeshift=--cpus=1 --jscodeshift=--print --jscodeshift=--dry --jscodeshift=--verbose=2 ``` See all available options [here](https://github.com/facebook/jscodeshift#usage-cli). @@ -53,7 +53,7 @@ Options to [recast](https://github.com/benjamn/recast)'s printer can be provided through jscodeshift's `printOptions` command line argument ```bash -npx @mui/x-codemod@next --jscodeshift="--printOptions='{\"quote\":\"double\"}'" +npx @mui/x-codemod --jscodeshift="--printOptions='{\"quote\":\"double\"}'" ``` ## v7.0.0 @@ -66,7 +66,7 @@ It runs codemods for both Data Grid and Date and Time Pickers packages. To run codemods for a specific package, refer to the respective section. ```bash -npx @mui/x-codemod@next v7.0.0/preset-safe +npx @mui/x-codemod v7.0.0/preset-safe ``` The corresponding sub-sections are listed below @@ -82,7 +82,7 @@ The corresponding sub-sections are listed below The `preset-safe` codemods for pickers. ```bash -npx @mui/x-codemod@next v7.0.0/pickers/preset-safe +npx @mui/x-codemod v7.0.0/pickers/preset-safe ``` The list includes these transformers @@ -108,7 +108,7 @@ This change only affects Date and Time Picker components. ``` ```bash -npx @mui/x-codemod@next v7.0.0/pickers/rename-components-to-slots +npx @mui/x-codemod v7.0.0/pickers/rename-components-to-slots ``` #### `rename-default-calendar-month-to-reference-date` @@ -121,7 +121,7 @@ Replace the `defaultCalendarMonth` prop with the `referenceDate` prop. ``` ```bash -npx @mui/x-codemod@next v7.0.0/pickers/rename-default-calendar-month-to-reference-date +npx @mui/x-codemod v7.0.0/pickers/rename-default-calendar-month-to-reference-date ``` #### `rename-day-picker-classes` @@ -134,7 +134,7 @@ Rename the `dayPickerClasses` variable to `dayCalendarClasses`. ``` ```bash -npx @mui/x-codemod@next v7.0.0/pickers/rename-day-picker-classes +npx @mui/x-codemod v7.0.0/pickers/rename-day-picker-classes ``` #### `rename-slots-types` @@ -149,7 +149,7 @@ Replace types suffix `SlotsComponent` by `Slots` and `SlotsComponentsProps` by ` ``` ```bash -npx @mui/x-codemod@next v7.0.0/pickers/rename-slots-types +npx @mui/x-codemod v7.0.0/pickers/rename-slots-types ``` ### Data Grid codemods @@ -159,7 +159,7 @@ npx @mui/x-codemod@next v7.0.0/pickers/rename-slots-types The `preset-safe` codemods for data grid. ```bash -npx @mui/x-codemod@next v7.0.0/data-grid/preset-safe +npx @mui/x-codemod v7.0.0/data-grid/preset-safe ``` The list includes these transformers @@ -184,7 +184,7 @@ This change only affects Data Grid components. ``` ```bash -npx @mui/x-codemod@next v7.0.0/data-grid/rename-components-to-slots +npx @mui/x-codemod v7.0.0/data-grid/rename-components-to-slots ``` #### `rename-cell-selection-props` @@ -203,7 +203,7 @@ Rename props related to `cellSelection` feature. ``` ```bash -npx @mui/x-codemod@next v7.0.0/data-grid/rename-cell-selection-props +npx @mui/x-codemod v7.0.0/data-grid/rename-cell-selection-props ``` #### `remove-stabilized-v7-experimentalFeatures` @@ -232,7 +232,7 @@ npx @mui/x-codemod@next v7.0.0/data-grid/remove-stabilized-experimentalFeatures The `preset-safe` codemods for tree view. ```bash -npx @mui/x-codemod@next v7.0.0/tree-view/preset-safe +npx @mui/x-codemod v7.0.0/tree-view/preset-safe ``` The list includes these transformers diff --git a/scripts/README.md b/scripts/README.md index e810994e0d2bd..677fec421dd6e 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -49,7 +49,7 @@ Push the working branch on the documentation release branch to deploy the docume ```bash -git push -f upstream next:docs-next +git push -f upstream master:docs-v7 ``` You can follow the deployment process [on the Netlify Dashboard](https://app.netlify.com/sites/material-ui-x/deploys?filter=docs-next) diff --git a/scripts/releaseChangelog.mjs b/scripts/releaseChangelog.mjs index f05642243277f..01152512c8b63 100644 --- a/scripts/releaseChangelog.mjs +++ b/scripts/releaseChangelog.mjs @@ -300,7 +300,7 @@ yargs(process.argv.slice(2)) }) .option('release', { // #default-branch-switch - default: 'next', + default: 'master', describe: 'Ref which we want to release', type: 'string', }); From 6fc1da5a1de101bbeb65435f955f5752421eabfa Mon Sep 17 00:00:00 2001 From: Andrew Cherniavskii Date: Tue, 19 Mar 2024 17:25:52 +0100 Subject: [PATCH 9/9] [DataGrid] Fix `ElementType` usage (#12479) --- .../src/components/GridPagination.tsx | 180 ++++++++++-------- .../components/cell/GridActionsCellItem.tsx | 33 ++-- 2 files changed, 120 insertions(+), 93 deletions(-) diff --git a/packages/x-data-grid/src/components/GridPagination.tsx b/packages/x-data-grid/src/components/GridPagination.tsx index 03be488965cad..9fe8e5cf2f931 100644 --- a/packages/x-data-grid/src/components/GridPagination.tsx +++ b/packages/x-data-grid/src/components/GridPagination.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import PropTypes from 'prop-types'; import TablePagination, { tablePaginationClasses, TablePaginationProps, @@ -31,88 +32,107 @@ const GridPaginationRoot = styled(TablePagination)(({ theme }) => ({ type MutableArray = A extends readonly (infer T)[] ? T[] : never; -export const GridPagination = React.forwardRef>( - function GridPagination(props, ref) { - const apiRef = useGridApiContext(); - const rootProps = useGridRootProps(); - const paginationModel = useGridSelector(apiRef, gridPaginationModelSelector); - const rowCount = useGridSelector(apiRef, gridPaginationRowCountSelector); - - const lastPage = React.useMemo(() => { - const calculatedValue = Math.ceil(rowCount / (paginationModel.pageSize || 1)) - 1; - return Math.max(0, calculatedValue); - }, [rowCount, paginationModel.pageSize]); - - const handlePageSizeChange = React.useCallback( - (event: React.ChangeEvent) => { - const pageSize = Number(event.target.value); - apiRef.current.setPageSize(pageSize); - }, - [apiRef], - ); - - const handlePageChange = React.useCallback( - (_, page) => { - apiRef.current.setPage(page); - }, - [apiRef], - ); - - const isPageSizeIncludedInPageSizeOptions = (pageSize: number) => { - for (let i = 0; i < rootProps.pageSizeOptions.length; i += 1) { - const option = rootProps.pageSizeOptions[i]; - if (typeof option === 'number') { - if (option === pageSize) { - return true; - } - } else if (option.value === pageSize) { +interface GridPaginationOwnProps { + component?: React.ElementType; +} + +const GridPagination = React.forwardRef< + unknown, + Partial< + // See https://github.com/mui/material-ui/issues/40427 + Omit + > & + GridPaginationOwnProps +>(function GridPagination(props, ref) { + const apiRef = useGridApiContext(); + const rootProps = useGridRootProps(); + const paginationModel = useGridSelector(apiRef, gridPaginationModelSelector); + const rowCount = useGridSelector(apiRef, gridPaginationRowCountSelector); + + const lastPage = React.useMemo(() => { + const calculatedValue = Math.ceil(rowCount / (paginationModel.pageSize || 1)) - 1; + return Math.max(0, calculatedValue); + }, [rowCount, paginationModel.pageSize]); + + const handlePageSizeChange = React.useCallback( + (event: React.ChangeEvent) => { + const pageSize = Number(event.target.value); + apiRef.current.setPageSize(pageSize); + }, + [apiRef], + ); + + const handlePageChange = React.useCallback( + (_, page) => { + apiRef.current.setPage(page); + }, + [apiRef], + ); + + const isPageSizeIncludedInPageSizeOptions = (pageSize: number) => { + for (let i = 0; i < rootProps.pageSizeOptions.length; i += 1) { + const option = rootProps.pageSizeOptions[i]; + if (typeof option === 'number') { + if (option === pageSize) { return true; } - } - return false; - }; - - if (process.env.NODE_ENV !== 'production') { - // eslint-disable-next-line react-hooks/rules-of-hooks - const warnedOnceMissingInPageSizeOptions = React.useRef(false); - - const pageSize = rootProps.paginationModel?.pageSize ?? paginationModel.pageSize; - if ( - !warnedOnceMissingInPageSizeOptions.current && - !rootProps.autoPageSize && - !isPageSizeIncludedInPageSizeOptions(pageSize) - ) { - console.warn( - [ - `MUI X: The page size \`${paginationModel.pageSize}\` is not preset in the \`pageSizeOptions\`.`, - `Add it to show the pagination select.`, - ].join('\n'), - ); - - warnedOnceMissingInPageSizeOptions.current = true; + } else if (option.value === pageSize) { + return true; } } + return false; + }; - const pageSizeOptions = isPageSizeIncludedInPageSizeOptions(paginationModel.pageSize) - ? rootProps.pageSizeOptions - : []; - - return ( - } - rowsPerPage={paginationModel.pageSize} - onPageChange={handlePageChange} - onRowsPerPageChange={handlePageSizeChange} - {...apiRef.current.getLocaleText('MuiTablePagination')} - {...props} - /> - ); - }, -); + if (process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line react-hooks/rules-of-hooks + const warnedOnceMissingInPageSizeOptions = React.useRef(false); + + const pageSize = rootProps.paginationModel?.pageSize ?? paginationModel.pageSize; + if ( + !warnedOnceMissingInPageSizeOptions.current && + !rootProps.autoPageSize && + !isPageSizeIncludedInPageSizeOptions(pageSize) + ) { + console.warn( + [ + `MUI X: The page size \`${paginationModel.pageSize}\` is not preset in the \`pageSizeOptions\`.`, + `Add it to show the pagination select.`, + ].join('\n'), + ); + + warnedOnceMissingInPageSizeOptions.current = true; + } + } + + const pageSizeOptions = isPageSizeIncludedInPageSizeOptions(paginationModel.pageSize) + ? rootProps.pageSizeOptions + : []; + + return ( + } + rowsPerPage={paginationModel.pageSize} + onPageChange={handlePageChange} + onRowsPerPageChange={handlePageSizeChange} + {...apiRef.current.getLocaleText('MuiTablePagination')} + {...props} + /> + ); +}); + +GridPagination.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + component: PropTypes.elementType, +} as any; + +export { GridPagination }; diff --git a/packages/x-data-grid/src/components/cell/GridActionsCellItem.tsx b/packages/x-data-grid/src/components/cell/GridActionsCellItem.tsx index 642d410d54ed9..d11bd15188a02 100644 --- a/packages/x-data-grid/src/components/cell/GridActionsCellItem.tsx +++ b/packages/x-data-grid/src/components/cell/GridActionsCellItem.tsx @@ -5,23 +5,26 @@ import MenuItem, { MenuItemProps } from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; -export type GridActionsCellItemProps = { +interface GridActionsCellItemCommonProps { label: string; icon?: React.ReactElement; /** from https://mui.com/material-ui/api/button-base/#ButtonBase-prop-component */ component?: React.ElementType; -} & ( - | ({ showInMenu?: false; icon: React.ReactElement } & IconButtonProps) - | ({ - showInMenu: true; - /** - * If false, the menu will not close when this item is clicked. - * @default true - */ - closeMenuOnClick?: boolean; - closeMenu?: () => void; - } & MenuItemProps) -); +} + +export type GridActionsCellItemProps = GridActionsCellItemCommonProps & + ( + | ({ showInMenu?: false; icon: React.ReactElement } & Omit) + | ({ + showInMenu: true; + /** + * If false, the menu will not close when this item is clicked. + * @default true + */ + closeMenuOnClick?: boolean; + closeMenu?: () => void; + } & Omit) + ); const GridActionsCellItem = React.forwardRef( (props, ref) => { @@ -80,6 +83,10 @@ GridActionsCellItem.propTypes = { // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the TypeScript types and run "yarn proptypes" | // ---------------------------------------------------------------------- + /** + * from https://mui.com/material-ui/api/button-base/#ButtonBase-prop-component + */ + component: PropTypes.elementType, icon: PropTypes.element, label: PropTypes.string.isRequired, showInMenu: PropTypes.bool,