Skip to content

Commit

Permalink
Merge pull request #147 from kpustakhod/onSelect-halfSelected-fix
Browse files Browse the repository at this point in the history
Fix onSelect shows isHalfSelected = true for Branch Nodes
  • Loading branch information
mellis481 authored Aug 24, 2023
2 parents fe97c08 + 8f392fa commit dddeef6
Show file tree
Hide file tree
Showing 8 changed files with 344 additions and 76 deletions.
21 changes: 21 additions & 0 deletions src/TreeView/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,24 @@ export const nodeActions = {
} as const;

export const NODE_ACTIONS = Object.freeze(Object.values(nodeActions));

export const treeTypes = {
collapse: "COLLAPSE",
collapseMany: "COLLAPSE_MANY",
expand: "EXPAND",
expandMany: "EXPAND_MANY",
halfSelect: "HALF_SELECT",
select: "SELECT",
deselect: "DESELECT",
toggle: "TOGGLE",
toggleSelect: "TOGGLE_SELECT",
changeSelectMany: "SELECT_MANY",
exclusiveChangeSelectMany: "EXCLUSIVE_CHANGE_SELECT_MANY",
focus: "FOCUS",
blur: "BLUR",
disable: "DISABLE",
enable: "ENABLE",
clearLastManuallyToggled: "CLEAR_MANUALLY_TOGGLED",
controlledSelectMany: "CONTROLLED_SELECT_MANY",
updateTreeStateWhenDataChanged: "UPDATE_TREE_STATE_WHEN_DATA_CHANGED",
} as const;
31 changes: 5 additions & 26 deletions src/TreeView/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import cx from "classnames";
import PropTypes from "prop-types";
import React, { useEffect, useReducer, useRef } from "react";
import {
ITreeViewState,
treeReducer,
treeTypes,
TreeViewAction,
} from "./reducer";
import { ITreeViewState, treeReducer, TreeViewAction } from "./reducer";
import {
ClickActions,
INode,
Expand All @@ -32,20 +27,20 @@ import {
symmetricDifference,
usePrevious,
usePreviousData,
isBranchSelectedAndHasSelectedDescendants,
getTreeParent,
getTreeNode,
validateTreeViewData,
noop,
isBranchNotSelectedAndHasOnlySelectedChild,
isBranchSelectedAndHasOnlySelectedChild,
getOnSelectTreeAction,
} from "./utils";
import { Node } from "./node";
import {
baseClassNames,
clickActions,
CLICK_ACTIONS,
NODE_ACTIONS,
treeTypes,
} from "./constants";

interface IUseTreeProps {
Expand Down Expand Up @@ -102,7 +97,6 @@ const useTree = ({

const {
selectedIds,
controlledIds,
expandedIds,
disabledIds,
tabbableId,
Expand Down Expand Up @@ -331,7 +325,7 @@ const useTree = ({
//Update parent if a child changes
useEffect(() => {
if (propagateSelectUpwards) {
const idsToUpdate = new Set<NodeId>([...toggledIds, ...controlledIds]);
const idsToUpdate = new Set<NodeId>([...toggledIds]);
if (
lastInteractedWith &&
lastAction !== treeTypes.focus &&
Expand Down Expand Up @@ -885,24 +879,9 @@ const handleKeyDown = ({
return;
}

const isSelectedAndHasSelectedDescendants = isBranchSelectedAndHasSelectedDescendants(
data,
element.id,
selectedIds
);

const isSelectedAndHasOnlySelectedChild = isBranchSelectedAndHasOnlySelectedChild(
data,
element.id,
selectedIds
);

dispatch({
type: togglableSelect
? isSelectedAndHasSelectedDescendants &&
!isSelectedAndHasOnlySelectedChild
? treeTypes.halfSelect
: treeTypes.toggleSelect
? getOnSelectTreeAction(data, id, selectedIds, disabledIds)
: treeTypes.select,
id: id,
multiSelect,
Expand Down
24 changes: 4 additions & 20 deletions src/TreeView/node.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import cx from "classnames";
import { ITreeViewState, treeTypes, TreeViewAction } from "./reducer";
import { ITreeViewState, TreeViewAction } from "./reducer";
import {
ClickActions,
EventCallback,
Expand All @@ -19,12 +19,11 @@ import {
getDescendants,
getTreeNode,
isBranchNode,
isBranchSelectedAndHasOnlySelectedChild,
isBranchSelectedAndHasSelectedDescendants,
noop,
propagatedIds,
getOnSelectTreeAction,
} from "./utils";
import { baseClassNames, clickActions } from "./constants";
import { baseClassNames, clickActions, treeTypes } from "./constants";

export interface INodeProps {
element: INode;
Expand Down Expand Up @@ -148,25 +147,10 @@ export const Node = (props: INodeProps) => {
lastManuallyToggled: element.id,
});
} else if (event.ctrlKey || clickAction === clickActions.select) {
const isSelectedAndHasSelectedDescendants = isBranchSelectedAndHasSelectedDescendants(
data,
element.id,
selectedIds
);

const isSelectedAndHasOnlySelectedChild = isBranchSelectedAndHasOnlySelectedChild(
data,
element.id,
selectedIds
);

//Select
dispatch({
type: togglableSelect
? isSelectedAndHasSelectedDescendants &&
!isSelectedAndHasOnlySelectedChild
? treeTypes.halfSelect
: treeTypes.toggleSelect
? getOnSelectTreeAction(data, element.id, selectedIds, disabledIds)
: treeTypes.select,
id: element.id,
multiSelect,
Expand Down
22 changes: 1 addition & 21 deletions src/TreeView/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,7 @@
import { treeTypes } from "./constants";
import { NodeId } from "./types";
import { difference } from "./utils";

export const treeTypes = {
collapse: "COLLAPSE",
collapseMany: "COLLAPSE_MANY",
expand: "EXPAND",
expandMany: "EXPAND_MANY",
halfSelect: "HALF_SELECT",
select: "SELECT",
deselect: "DESELECT",
toggle: "TOGGLE",
toggleSelect: "TOGGLE_SELECT",
changeSelectMany: "SELECT_MANY",
exclusiveChangeSelectMany: "EXCLUSIVE_CHANGE_SELECT_MANY",
focus: "FOCUS",
blur: "BLUR",
disable: "DISABLE",
enable: "ENABLE",
clearLastManuallyToggled: "CLEAR_MANUALLY_TOGGLED",
controlledSelectMany: "CONTROLLED_SELECT_MANY",
updateTreeStateWhenDataChanged: "UPDATE_TREE_STATE_WHEN_DATA_CHANGED",
} as const;

export type TreeViewAction =
| { type: "COLLAPSE"; id: NodeId; lastInteractedWith?: NodeId | null }
| { type: "COLLAPSE_MANY"; ids: NodeId[]; lastInteractedWith?: NodeId | null }
Expand Down
55 changes: 49 additions & 6 deletions src/TreeView/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useRef } from "react";
import { EventCallback, INode, INodeRef, NodeId } from "./types";
import { treeTypes } from "./constants";

// eslint-disable-next-line @typescript-eslint/no-empty-function
export const noop = () => {};
Expand Down Expand Up @@ -413,17 +414,18 @@ export const isBranchSelectedAndHasSelectedDescendants = (
);
};

export const isBranchNotSelectedAndHasAllSelectedDescendants = (
export const isBranchSelectedAndHasAllSelectedEnabledDescendants = (
data: INode[],
elementId: NodeId,
selectedIds: Set<NodeId>
selectedIds: Set<NodeId>,
disabledIds: Set<NodeId>
) => {
const children = getDescendants(data, elementId, new Set<number>());
return (
isBranchNode(data, elementId) &&
!selectedIds.has(elementId) &&
getDescendants(data, elementId, new Set<number>()).every((item) =>
selectedIds.has(item)
)
selectedIds.has(elementId) &&
children.every((item) => selectedIds.has(item)) &&
children.every((item) => !disabledIds.has(item))
);
};

Expand Down Expand Up @@ -455,6 +457,47 @@ export const isBranchSelectedAndHasOnlySelectedChild = (
);
};

export const getOnSelectTreeAction = (
data: INode[],
elementId: NodeId,
selectedIds: Set<NodeId>,
disabledIds: Set<NodeId>
) => {
const isSelectedAndHasSelectedDescendants = isBranchSelectedAndHasSelectedDescendants(
data,
elementId,
selectedIds
);

const isSelectedAndHasOnlySelectedChild = isBranchSelectedAndHasOnlySelectedChild(
data,
elementId,
selectedIds
);

const isSelectedAndHasAllSelectedEnabledDescendants = isBranchSelectedAndHasAllSelectedEnabledDescendants(
data,
elementId,
selectedIds,
disabledIds
);

if (isSelectedAndHasAllSelectedEnabledDescendants) {
// current element is branch and has no disabled and all selected descendants
return treeTypes.toggleSelect;
} else if (
// current element is branch and has any number of selected descendants
// OR
// current element is branch and has only one selected child
isSelectedAndHasSelectedDescendants &&
!isSelectedAndHasOnlySelectedChild
) {
return treeTypes.halfSelect;
}

return treeTypes.toggleSelect;
};

export const getTreeParent = (data: INode[]): INode => {
const parentNode: INode | undefined = data.find(
(node) => node.parent === null
Expand Down
Loading

0 comments on commit dddeef6

Please sign in to comment.