Skip to content

Commit 1aaa985

Browse files
committed
fix: methods for 'checked'
1 parent 1afbe26 commit 1aaa985

File tree

4 files changed

+200
-210
lines changed

4 files changed

+200
-210
lines changed

lib/HeTree.tsx

Lines changed: 80 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export interface HeTreeProps<T extends Record<string, any>> extends Partial<type
6969
onChange: (data: T[]) => void,
7070
openIds?: Id[],
7171
checkedIds?: Id[],
72+
semiCheckedIds?: Id[],
7273
}
7374

7475
export function useHeTree<T extends Record<string, any>>(
@@ -82,7 +83,10 @@ export function useHeTree<T extends Record<string, any>>(
8283
}
8384
const openIdsStr = useMemo(() => props.openIds ? [...props.openIds].sort().toString() : '', [props.openIds])
8485
const openIdSet = useMemo(() => new Set(props.openIds), [openIdsStr])
85-
const checkedIdsStr = useMemo(() => props.checkedIds?.toString(), [props.checkedIds])
86+
const checkedIdsStr = useMemo(() => props.checkedIds ? [...props.checkedIds].sort().toString() : '', [props.checkedIds])
87+
const checkedIdSet = useMemo(() => new Set(props.checkedIds), [checkedIdsStr])
88+
const semiCheckedIdStr = useMemo(() => props.semiCheckedIds ? [...props.semiCheckedIds].sort().toString() : '', [props.semiCheckedIds])
89+
const semiCheckedIdSet = useMemo(() => new Set(props.semiCheckedIds), [semiCheckedIdStr])
8690
// mainCache ==================================
8791
const mainCache = useMemo(
8892
() => {
@@ -140,7 +144,7 @@ export function useHeTree<T extends Record<string, any>>(
140144
index,
141145
level,
142146
open: props.openIds ? openIdSet.has(id) : true,
143-
checked: false,
147+
checked: checkedIdSet.has(id) ? true : (semiCheckedIdSet.has(id) ? null : false),
144148
draggable: false,
145149
}
146150
stats[id] = stat
@@ -150,32 +154,6 @@ export function useHeTree<T extends Record<string, any>>(
150154
siblingStats.push(stat)
151155
count++
152156
}
153-
// checked
154-
let allCheckedIds: Id[] = [];
155-
let semiCheckedIds: Id[] = [];
156-
if (props.checkedIds) {
157-
for (const id of props.checkedIds) {
158-
const stat = stats[id];
159-
if (stat) {
160-
stat.checked = true;
161-
for (const [curStat] of walkTreeDataGenerator(stat.childStats, 'childStats')) {
162-
curStat.checked = true;
163-
}
164-
for (const curStat of walkParentsGenerator(stat, 'parentStat', { withSelf: false })) {
165-
let allChecked = true
166-
let hasChecked = false
167-
for (const child of curStat.childStats) {
168-
if (child.checked) {
169-
hasChecked = true
170-
} else {
171-
allChecked = false
172-
}
173-
}
174-
curStat.checked = allChecked ? true : (hasChecked ? null : false)
175-
}
176-
}
177-
}
178-
}
179157

180158
// after stats ready
181159
for (const [stat] of walkTreeDataGenerator(rootStats, 'childStats')) {
@@ -185,12 +163,6 @@ export function useHeTree<T extends Record<string, any>>(
185163
draggable = stat.parentStat ? stat.parentStat.draggable : true
186164
}
187165
stat.draggable = draggable
188-
// checked
189-
if (stat.checked) {
190-
allCheckedIds.push(stat.id)
191-
} else if (stat.checked === null) {
192-
semiCheckedIds.push(stat.id)
193-
}
194166
}
195167
const getStat = (idOrNodeOrStat: T | Stat<T> | Id) => {
196168
let id: Id
@@ -232,14 +204,12 @@ export function useHeTree<T extends Record<string, any>>(
232204
return {
233205
// root
234206
rootIds, rootNodes, rootStats,
235-
// open & checked
236-
allCheckedIds, semiCheckedIds,
237207
// methods
238208
getStat,
239209
getDraft,
240210
nextData,
241211
}
242-
}, [props.data, props.dataType, ID, PID, openIdSet, checkedIdsStr, props.rootId,
212+
}, [props.data, props.dataType, ID, PID, openIdSet, checkedIdSet, props.rootId,
243213
isFunctionReactive && props.canDrag,
244214
]
245215
);
@@ -677,54 +647,6 @@ export function useHeTree<T extends Record<string, any>>(
677647
renderHeTree,
678648
}
679649
}
680-
681-
// function getMapOfFlatData(params:type) {
682-
683-
// }
684-
685-
// function updateChecked<T extends Record<string, any>>(options: {
686-
// data: T[],
687-
// dataType: 'tree' | 'flat',
688-
// checkedIds: Id[],
689-
// idKey: 'id',
690-
// parentIdKey: 'parentId',
691-
// childrenKey: 'children',
692-
// }) {
693-
// const { data, dataType, checkedIds, idKey: ID, parentIdKey: PID, childrenKey: CHILDREN } = options
694-
// if (dataType === 'flat') {
695-
// const byId: Record<Id, T> = {}
696-
// const childrenById: Record<Id, T[]> = {}
697-
// const rootChildren: T[] = [];
698-
// for (const node of data) {
699-
// const id = node[ID];
700-
// byId[id] = node
701-
// childrenById[id] = [];
702-
// const pid = node[PID];
703-
// (childrenById[pid] || rootChildren).push(node);
704-
// }
705-
// //
706-
// const newCheckedIds = new Set<Id>(checkedIds)
707-
// const checked = false
708-
// const changedIds: Id[] = [];
709-
// const allChangedIds = new Set(changedIds)
710-
// data.forEach((node) => {
711-
// if (allChangedIds.has(node[PID])) {
712-
// allChangedIds.add(node[ID])
713-
// }
714-
// })
715-
// }
716-
// let pids = new Set<Id>()
717-
// let allCheckedIds = new Set<Id>(checkedIds)
718-
// for (let id of checkedIds) {
719-
// if (id in checkedIds) {
720-
// allCheckedIds.add(id)
721-
// pids.add(id)
722-
// } else if (allCheckedIds.has(byid[id].pid)) {
723-
// allCheckedIds.add(id)
724-
// pids.add(id)
725-
// }
726-
// }
727-
// }
728650
// react components ==================================
729651
// no components
730652
// utils methods ==================================
@@ -1079,7 +1001,8 @@ export function updateCheckedInFlatData<T extends Record<Id, any>>(
10791001
const changedPids = new Set<Id>(idsToUpdate)
10801002
const pidById: Record<Id, Id | null> = {}
10811003
const childIdsById = new Map<Id | null, Id[]>()
1082-
childIdsById.set(null, [])
1004+
const rootIds: Id[] = [];
1005+
childIdsById.set(null, rootIds)
10831006
for (const [node, { parents, id, pid }] of walkFlatDataGenerator(flatData, options)) {
10841007
pidById[id] = pid
10851008
childIdsById.get(pid)!.push(id)
@@ -1091,22 +1014,42 @@ export function updateCheckedInFlatData<T extends Record<Id, any>>(
10911014
changedPids.add(id)
10921015
}
10931016
}
1094-
// update parents
1095-
for (const id of idsToUpdate) {
1096-
let cur = pidById[id];
1097-
while (cur != null) {
1098-
let allChecked = true
1099-
let hasChecked = false
1100-
for (const childId of childIdsById.get(cur)!) {
1101-
if (all.get(childId) === false) {
1102-
allChecked = false
1103-
} else {
1104-
hasChecked = true
1105-
}
1017+
// check from root to each nodes
1018+
const walk = (id: Id) => {
1019+
const childIds = childIdsById.get(id)
1020+
if (!childIds || childIds.length === 0) {
1021+
return all.get(id)
1022+
}
1023+
let hasTrue = false
1024+
let hasFalse = false
1025+
let hasNull = false
1026+
for (const childId of childIds) {
1027+
let t = walk(childId)
1028+
if (t === false) {
1029+
hasFalse = true
1030+
} else if (t === null) {
1031+
hasNull = true
1032+
break
1033+
} else {
1034+
hasTrue = true
11061035
}
1107-
all.set(cur, allChecked ? true : (hasChecked ? null : false))
1108-
cur = pidById[cur]
11091036
}
1037+
let checked: Checked
1038+
if (hasNull) {
1039+
checked = null
1040+
} else if (hasFalse && hasTrue) {
1041+
checked = null
1042+
} else if (hasFalse) {
1043+
checked = false
1044+
} else {
1045+
checked = true
1046+
}
1047+
all.set(id, checked)
1048+
return checked
1049+
}
1050+
1051+
for (const id of rootIds) {
1052+
walk(id)
11101053
}
11111054
const newCheckedIds: Id[] = [];
11121055
const semiCheckedIds: Id[] = [];
@@ -1117,11 +1060,10 @@ export function updateCheckedInFlatData<T extends Record<Id, any>>(
11171060
semiCheckedIds.push(k)
11181061
}
11191062
})
1120-
const allChecked = [...newCheckedIds, ...semiCheckedIds];
1121-
return [newCheckedIds.sort(), semiCheckedIds.sort(), allChecked.sort()]
1063+
return [newCheckedIds.sort(), semiCheckedIds.sort()]
11221064
}
11231065
export function updateCheckedInTreeData<T extends Record<Id, any>>(
1124-
flatData: T[],
1066+
treeData: T[],
11251067
checkedIds: Id[],
11261068
idOrIds: Id | Id[],
11271069
checked: boolean,
@@ -1136,7 +1078,7 @@ export function updateCheckedInTreeData<T extends Record<Id, any>>(
11361078
const pidById: Record<Id, Id | null> = {}
11371079
const childIdsById = new Map<Id | null, Id[]>()
11381080
childIdsById.set(null, [])
1139-
for (const [node, { parents, parent }] of walkTreeDataGenerator(flatData, CHILDREN)) {
1081+
for (const [node, { parents, parent }] of walkTreeDataGenerator(treeData, CHILDREN)) {
11401082
const id = node[ID];
11411083
const pid = parent?.[ID] ?? null
11421084
pidById[id] = pid
@@ -1149,22 +1091,41 @@ export function updateCheckedInTreeData<T extends Record<Id, any>>(
11491091
changedPids.add(id)
11501092
}
11511093
}
1152-
// update parents
1153-
for (const id of idsToUpdate) {
1154-
let cur = pidById[id];
1155-
while (cur != null) {
1156-
let allChecked = true
1157-
let hasChecked = false
1158-
for (const childId of childIdsById.get(cur)!) {
1159-
if (all.get(childId) === false) {
1160-
allChecked = false
1161-
} else {
1162-
hasChecked = true
1163-
}
1094+
// check from root to each nodes
1095+
const walk = (id: Id) => {
1096+
const childIds = childIdsById.get(id)
1097+
if (!childIds || childIds.length === 0) {
1098+
return all.get(id)
1099+
}
1100+
let hasTrue = false
1101+
let hasFalse = false
1102+
let hasNull = false
1103+
for (const childId of childIds) {
1104+
let t = walk(childId)
1105+
if (t === false) {
1106+
hasFalse = true
1107+
} else if (t === null) {
1108+
hasNull = true
1109+
break
1110+
} else {
1111+
hasTrue = true
11641112
}
1165-
all.set(cur, allChecked ? true : (hasChecked ? null : false))
1166-
cur = pidById[cur]
11671113
}
1114+
let checked: Checked
1115+
if (hasNull) {
1116+
checked = null
1117+
} else if (hasFalse && hasTrue) {
1118+
checked = null
1119+
} else if (hasFalse) {
1120+
checked = false
1121+
} else {
1122+
checked = true
1123+
}
1124+
all.set(id, checked)
1125+
return checked
1126+
}
1127+
for (const node of treeData) {
1128+
walk(node[ID])
11681129
}
11691130
const newCheckedIds: Id[] = [];
11701131
const semiCheckedIds: Id[] = [];
@@ -1175,8 +1136,7 @@ export function updateCheckedInTreeData<T extends Record<Id, any>>(
11751136
semiCheckedIds.push(k)
11761137
}
11771138
})
1178-
const allChecked = [...newCheckedIds, ...semiCheckedIds];
1179-
return [newCheckedIds.sort(), semiCheckedIds.sort(), allChecked.sort()]
1139+
return [newCheckedIds.sort(), semiCheckedIds.sort()]
11801140
}
11811141
// private methods
11821142
function calculateDistance(x1: number, y1: number, x2: number, y2: number) {

src/App.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import example_data from "./examples/example_data.json";
2-
import { sortFlatData, useHeTree } from "../lib/index";
2+
import { sortFlatData, useHeTree, updateCheckedInFlatData } from "../lib/HeTree";
33
import { useState } from "react";
44
import { useImmer } from "use-immer";
55

@@ -125,14 +125,9 @@ function App() {
125125
}
126126
setopenIds(ids)
127127
}
128-
const toggleCheck = (id) => {
129-
let a = new Set(checkedIds)
130-
if (a.has(id)) {
131-
a.delete(id)
132-
} else {
133-
a.add(id)
134-
}
135-
setcheckedIds([...a])
128+
const toggleCheck = (id, checked) => {
129+
let newIds = updateCheckedInFlatData(flatData, checkedIds, id, checked, { parentIdKey: 'pid' })[0];
130+
setcheckedIds(newIds)
136131
}
137132
const [openIds, setopenIds] = useState([1, 2, 4]);
138133
const [checkedIds, setcheckedIds] = useState([4]);
@@ -141,7 +136,7 @@ function App() {
141136
dataType: 'flat',
142137
renderNode: ({ id, node, open, checked }) => <div>
143138
<button onClick={() => { updateOpen(id, !open) }}>{open ? '-' : '+'}</button>
144-
<input type="checkbox" checked={Boolean(checked)} onChange={() => { toggleCheck(id) }} />
139+
<input type="checkbox" checked={Boolean(checked)} onChange={() => { toggleCheck(id, !checked) }} />
145140
{node?.name}
146141
</div>,
147142
onChange: setflatData,

0 commit comments

Comments
 (0)