From 4f3b73eceaf27a115b752244ffc2fd0e0a3badcc Mon Sep 17 00:00:00 2001 From: Lukas Bach Date: Wed, 12 Mar 2025 22:09:59 +0100 Subject: [PATCH 1/4] feat: call onFocusItem with first item if focused item is missing (#363) --- next-release-notes.md | 11 +--- .../ControlledTreeEnvironment.tsx | 34 +++++------ packages/core/src/stories/363.stories.tsx | 59 +++++++++++++++++++ packages/core/test/dnd-restrictions.spec.tsx | 4 +- 4 files changed, 80 insertions(+), 28 deletions(-) create mode 100644 packages/core/src/stories/363.stories.tsx diff --git a/next-release-notes.md b/next-release-notes.md index f77d5bd6a..bda0d473a 100644 --- a/next-release-notes.md +++ b/next-release-notes.md @@ -1,9 +1,4 @@ - \ No newline at end of file +- If a tree environment renders without an item defined as focused in its `viewState` parameter, it will invoke the `onFocusItem` + prop with the first item in the tree during its render. In the past, this was implicitly and silently set in the `viewState` prop, + now this assignment is triggered explicitly with the handler call (#363) \ No newline at end of file diff --git a/packages/core/src/controlledEnvironment/ControlledTreeEnvironment.tsx b/packages/core/src/controlledEnvironment/ControlledTreeEnvironment.tsx index 1c65b0147..656790365 100644 --- a/packages/core/src/controlledEnvironment/ControlledTreeEnvironment.tsx +++ b/packages/core/src/controlledEnvironment/ControlledTreeEnvironment.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useContext } from 'react'; +import { useContext, useEffect } from 'react'; import { ControlledTreeEnvironmentProps, TreeEnvironmentContextProps, @@ -21,26 +21,24 @@ export const ControlledTreeEnvironment = React.forwardRef< >((props, ref) => { const environmentContextProps = useControlledTreeEnvironmentProps(props); - const { viewState } = props; + const { viewState, onFocusItem } = props; // Make sure that every tree view state has a focused item - for (const treeId of Object.keys(environmentContextProps.trees)) { - // TODO if the focus item is dragged out of the tree and is not within the expanded items - // TODO of that tree, the tree does not show any focus item anymore. - // Fix: use linear items to see if focus item is visible, and reset if not. Only refresh that - // information when the viewstate changes - if ( - !viewState[treeId]?.focusedItem && - environmentContextProps.trees[treeId] - ) { - viewState[treeId] = { - ...viewState[treeId], - focusedItem: - props.items[environmentContextProps.trees[treeId].rootItem] - ?.children?.[0], - }; + useEffect(() => { + for (const treeId of Object.keys(environmentContextProps.trees)) { + const firstItemIndex = + props.items[environmentContextProps.trees[treeId].rootItem] + ?.children?.[0]; + const firstItem = firstItemIndex && props.items[firstItemIndex]; + if ( + !viewState[treeId]?.focusedItem && + environmentContextProps.trees[treeId] && + firstItem + ) { + onFocusItem?.(firstItem, treeId, false); + } } - } + }, [environmentContextProps.trees, onFocusItem, props.items, viewState]); return ( diff --git a/packages/core/src/stories/363.stories.tsx b/packages/core/src/stories/363.stories.tsx new file mode 100644 index 000000000..3315f9861 --- /dev/null +++ b/packages/core/src/stories/363.stories.tsx @@ -0,0 +1,59 @@ +import { Meta } from '@storybook/react'; +import React, { useState } from 'react'; +import { longTree } from 'demodata'; +import { Tree } from '../tree/Tree'; +import { ControlledTreeEnvironment } from '../controlledEnvironment/ControlledTreeEnvironment'; +import { TreeItemIndex } from '../types'; + +export default { + title: 'Core/Issue Report Reproduction', +} as Meta; + +export const Issue363 = () => { + const [focusedItem, setFocusedItem] = useState(); + const [expandedItems, setExpandedItems] = useState([]); + const [selectedItems, setSelectedItems] = useState([]); + return ( + + canDragAndDrop + canDropOnFolder + canReorderItems + items={longTree.items} + getItemTitle={item => item.data} + onFocusItem={item => { + setFocusedItem(item.index); + console.log(`Focused item: ${item.index}`); + }} + onSelectItems={items => { + setSelectedItems(items); + }} + onExpandItem={item => { + setExpandedItems([...expandedItems, item.index]); + }} + onCollapseItem={item => { + setExpandedItems(expandedItems.filter(i => i !== item.index)); + }} + viewState={{ + 'tree-1': { + focusedItem, + expandedItems, + selectedItems, + }, + }} + > + + +
+        {JSON.stringify(
+          {
+            focusedItem,
+            expandedItems,
+            selectedItems,
+          },
+          null,
+          2
+        )}
+      
+ + ); +}; diff --git a/packages/core/test/dnd-restrictions.spec.tsx b/packages/core/test/dnd-restrictions.spec.tsx index d2f6ba4c5..1ae92642f 100644 --- a/packages/core/test/dnd-restrictions.spec.tsx +++ b/packages/core/test/dnd-restrictions.spec.tsx @@ -118,7 +118,7 @@ describe('dnd restrictions', () => { describe('canDrag', () => { it('respects disabled value', async () => { - const canDrag = jest.fn(items => items[0].data !== 'aab'); + const canDrag = jest.fn(items => items[0]?.data !== 'aab'); const test = await new TestUtil().renderOpenTree({ canDrag, }); @@ -131,7 +131,7 @@ describe('dnd restrictions', () => { }); it('works for other item', async () => { - const canDrag = jest.fn(items => items[0].data !== 'aab'); + const canDrag = jest.fn(items => items[0]?.data !== 'aab'); const test = await new TestUtil().renderOpenTree({ canDrag, }); From 5dd232971e64a48716ac07918d51a420ff792ff9 Mon Sep 17 00:00:00 2001 From: lukasbachbot Date: Wed, 12 Mar 2025 21:17:34 +0000 Subject: [PATCH 2/4] v2.5.1-alpha.0 --- lerna.json | 2 +- packages/autodemo/package.json | 6 +++--- packages/blueprintjs-renderers/package.json | 6 +++--- packages/core/package.json | 4 ++-- packages/demodata/package.json | 2 +- packages/docs/package.json | 10 +++++----- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lerna.json b/lerna.json index 9c10fb7bb..5fdbdb856 100644 --- a/lerna.json +++ b/lerna.json @@ -2,5 +2,5 @@ "packages": ["packages/*"], "npmClient": "yarn", "useWorkspaces": true, - "version": "2.5.0" + "version": "2.5.1-alpha.0" } diff --git a/packages/autodemo/package.json b/packages/autodemo/package.json index c5df087d8..7a4efbd93 100644 --- a/packages/autodemo/package.json +++ b/packages/autodemo/package.json @@ -1,6 +1,6 @@ { "name": "react-complex-tree-autodemo", - "version": "2.5.0", + "version": "2.5.1-alpha.0", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { @@ -23,10 +23,10 @@ "@types/react-dom": "^18.0.7", "babel-jest": "^27.5.1", "babel-loader": "^9.1.0", - "demodata": "^2.5.0", + "demodata": "^2.5.1-alpha.0", "jest": "^26.6.3", "react": "^18.2.0", - "react-complex-tree": "^2.5.0", + "react-complex-tree": "^2.5.1-alpha.0", "react-dom": "^18.2.0", "react-test-renderer": "^18.2.0", "ts-node": "^10.7.0", diff --git a/packages/blueprintjs-renderers/package.json b/packages/blueprintjs-renderers/package.json index 322a4a0b9..95e969d84 100644 --- a/packages/blueprintjs-renderers/package.json +++ b/packages/blueprintjs-renderers/package.json @@ -1,6 +1,6 @@ { "name": "react-complex-tree-blueprintjs-renderers", - "version": "2.5.0", + "version": "2.5.1-alpha.0", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { @@ -26,10 +26,10 @@ "@types/react-dom": "^18.0.7", "babel-jest": "^27.5.1", "babel-loader": "^9.1.0", - "demodata": "^2.5.0", + "demodata": "^2.5.1-alpha.0", "jest": "^26.6.3", "react": "^18.2.0", - "react-complex-tree": "^2.5.0", + "react-complex-tree": "^2.5.1-alpha.0", "react-dom": "^18.2.0", "react-test-renderer": "^18.2.0", "ts-node": "^10.7.0", diff --git a/packages/core/package.json b/packages/core/package.json index 6b7821721..b95d8cf23 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "react-complex-tree", - "version": "2.5.0", + "version": "2.5.1-alpha.0", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", "esnext": "lib/esnext/index.js", @@ -35,7 +35,7 @@ "babel-jest": "^27.5.1", "babel-loader": "^9.1.0", "cpy-cli": "^3.1.1", - "demodata": "^2.5.0", + "demodata": "^2.5.1-alpha.0", "jest": "^29.2.2", "jest-dom": "^4.0.0", "jest-environment-jsdom": "^29.2.2", diff --git a/packages/demodata/package.json b/packages/demodata/package.json index 26e1e9d77..729533493 100644 --- a/packages/demodata/package.json +++ b/packages/demodata/package.json @@ -1,6 +1,6 @@ { "name": "demodata", - "version": "2.5.0", + "version": "2.5.1-alpha.0", "main": "lib/index.js", "repository": { "type": "git", diff --git a/packages/docs/package.json b/packages/docs/package.json index ed034b279..e1cc4b949 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "docs", - "version": "2.5.0", + "version": "2.5.1-alpha.0", "private": true, "scripts": { "docusaurus": "docusaurus", @@ -21,7 +21,7 @@ "@mdx-js/react": "^1.6.21", "@svgr/webpack": "^6.5.1", "clsx": "^1.1.1", - "demodata": "^2.5.0", + "demodata": "^2.5.1-alpha.0", "docusaurus-plugin-react-docgen-typescript": "^1.0.2", "docusaurus-plugin-typedoc": "^0.18.0", "file-loader": "^6.2.0", @@ -29,9 +29,9 @@ "iframe-resizer-react": "^1.1.0", "prism-react-renderer": "^1.2.1", "react": "^18.2.0", - "react-complex-tree": "^2.5.0", - "react-complex-tree-autodemo": "^2.5.0", - "react-complex-tree-blueprintjs-renderers": "^2.5.0", + "react-complex-tree": "^2.5.1-alpha.0", + "react-complex-tree-autodemo": "^2.5.1-alpha.0", + "react-complex-tree-blueprintjs-renderers": "^2.5.1-alpha.0", "react-docgen-typescript": "^2.2.2", "react-dom": "^18.2.0", "typedoc": "^0.23.18", From 212081768fdee69cab5c494750897a3d03d7a079 Mon Sep 17 00:00:00 2001 From: lukasbachbot Date: Wed, 12 Mar 2025 21:19:24 +0000 Subject: [PATCH 3/4] chore: tidy up after prerelease --- lerna-publish-summary.json | 2 +- yarn.lock | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lerna-publish-summary.json b/lerna-publish-summary.json index 6245ee2d4..cc3c2bd93 100644 --- a/lerna-publish-summary.json +++ b/lerna-publish-summary.json @@ -1 +1 @@ -[{"packageName":"react-complex-tree-autodemo","version":"2.5.0"},{"packageName":"react-complex-tree-blueprintjs-renderers","version":"2.5.0"},{"packageName":"react-complex-tree","version":"2.5.0"}] \ No newline at end of file +[{"packageName":"react-complex-tree-autodemo","version":"2.5.1-alpha.0"},{"packageName":"react-complex-tree-blueprintjs-renderers","version":"2.5.1-alpha.0"},{"packageName":"react-complex-tree","version":"2.5.1-alpha.0"}] \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 319b66d66..20c823c20 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11803,7 +11803,7 @@ __metadata: languageName: node linkType: hard -"demodata@^2.5.0, demodata@workspace:packages/demodata": +"demodata@^2.5.1-alpha.0, demodata@workspace:packages/demodata": version: 0.0.0-use.local resolution: "demodata@workspace:packages/demodata" dependencies: @@ -12029,7 +12029,7 @@ __metadata: "@mdx-js/react": ^1.6.21 "@svgr/webpack": ^6.5.1 clsx: ^1.1.1 - demodata: ^2.5.0 + demodata: ^2.5.1-alpha.0 docusaurus-plugin-react-docgen-typescript: ^1.0.2 docusaurus-plugin-typedoc: ^0.18.0 file-loader: ^6.2.0 @@ -12037,9 +12037,9 @@ __metadata: iframe-resizer-react: ^1.1.0 prism-react-renderer: ^1.2.1 react: ^18.2.0 - react-complex-tree: ^2.5.0 - react-complex-tree-autodemo: ^2.5.0 - react-complex-tree-blueprintjs-renderers: ^2.5.0 + react-complex-tree: ^2.5.1-alpha.0 + react-complex-tree-autodemo: ^2.5.1-alpha.0 + react-complex-tree-blueprintjs-renderers: ^2.5.1-alpha.0 react-docgen-typescript: ^2.2.2 react-dom: ^18.2.0 typedoc: ^0.23.18 @@ -21548,7 +21548,7 @@ __metadata: languageName: node linkType: hard -"react-complex-tree-autodemo@^2.5.0, react-complex-tree-autodemo@workspace:packages/autodemo": +"react-complex-tree-autodemo@^2.5.1-alpha.0, react-complex-tree-autodemo@workspace:packages/autodemo": version: 0.0.0-use.local resolution: "react-complex-tree-autodemo@workspace:packages/autodemo" dependencies: @@ -21562,10 +21562,10 @@ __metadata: "@types/react-dom": ^18.0.7 babel-jest: ^27.5.1 babel-loader: ^9.1.0 - demodata: ^2.5.0 + demodata: ^2.5.1-alpha.0 jest: ^26.6.3 react: ^18.2.0 - react-complex-tree: ^2.5.0 + react-complex-tree: ^2.5.1-alpha.0 react-dom: ^18.2.0 react-test-renderer: ^18.2.0 ts-node: ^10.7.0 @@ -21573,7 +21573,7 @@ __metadata: languageName: unknown linkType: soft -"react-complex-tree-blueprintjs-renderers@^2.5.0, react-complex-tree-blueprintjs-renderers@workspace:packages/blueprintjs-renderers": +"react-complex-tree-blueprintjs-renderers@^2.5.1-alpha.0, react-complex-tree-blueprintjs-renderers@workspace:packages/blueprintjs-renderers": version: 0.0.0-use.local resolution: "react-complex-tree-blueprintjs-renderers@workspace:packages/blueprintjs-renderers" dependencies: @@ -21589,10 +21589,10 @@ __metadata: "@types/react-dom": ^18.0.7 babel-jest: ^27.5.1 babel-loader: ^9.1.0 - demodata: ^2.5.0 + demodata: ^2.5.1-alpha.0 jest: ^26.6.3 react: ^18.2.0 - react-complex-tree: ^2.5.0 + react-complex-tree: ^2.5.1-alpha.0 react-dom: ^18.2.0 react-test-renderer: ^18.2.0 ts-node: ^10.7.0 @@ -21640,7 +21640,7 @@ __metadata: languageName: unknown linkType: soft -"react-complex-tree@^2.5.0, react-complex-tree@workspace:packages/core": +"react-complex-tree@^2.5.1-alpha.0, react-complex-tree@workspace:packages/core": version: 0.0.0-use.local resolution: "react-complex-tree@workspace:packages/core" dependencies: @@ -21658,7 +21658,7 @@ __metadata: babel-jest: ^27.5.1 babel-loader: ^9.1.0 cpy-cli: ^3.1.1 - demodata: ^2.5.0 + demodata: ^2.5.1-alpha.0 jest: ^29.2.2 jest-dom: ^4.0.0 jest-environment-jsdom: ^29.2.2 From c97b72f3cff203caccb21bcc4fd262c11148fb81 Mon Sep 17 00:00:00 2001 From: Lukas Bach Date: Fri, 14 Mar 2025 15:52:56 +0100 Subject: [PATCH 4/4] feat: fix invalid droptarget at bottom of tree (#363) --- next-release-notes.md | 3 ++- packages/core/src/drag/DragAndDropProvider.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/next-release-notes.md b/next-release-notes.md index bda0d473a..4cb64522e 100644 --- a/next-release-notes.md +++ b/next-release-notes.md @@ -1,4 +1,5 @@ ### Bug Fixes and Improvements - If a tree environment renders without an item defined as focused in its `viewState` parameter, it will invoke the `onFocusItem` prop with the first item in the tree during its render. In the past, this was implicitly and silently set in the `viewState` prop, - now this assignment is triggered explicitly with the handler call (#363) \ No newline at end of file + now this assignment is triggered explicitly with the handler call (#363) +- Fixed a bug where an additional invalid drop target would be available at the bottom-most location when dragging via keyboard interactions (#363) \ No newline at end of file diff --git a/packages/core/src/drag/DragAndDropProvider.tsx b/packages/core/src/drag/DragAndDropProvider.tsx index f55174784..642a0317d 100644 --- a/packages/core/src/drag/DragAndDropProvider.tsx +++ b/packages/core/src/drag/DragAndDropProvider.tsx @@ -282,7 +282,7 @@ export const DragAndDropProvider: React.FC = ({ if (environment.activeTreeId) { setProgrammaticDragIndex(oldIndex => Math.min( - viableDragPositions[environment.activeTreeId!].length, + viableDragPositions[environment.activeTreeId!].length - 1, oldIndex + 1 ) );