Skip to content

Commit e097cdf

Browse files
committed
feat: add methods for 'open'
1 parent d8fc420 commit e097cdf

File tree

4 files changed

+158
-97
lines changed

4 files changed

+158
-97
lines changed

lib/HeTree.tsx

Lines changed: 113 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,7 @@ export interface HeTreeProps<T extends Record<string, any>> extends Partial<type
6767
onDrop?: (e: React.DragEvent<HTMLElement>, parentStat: Stat<T> | null, index: number, isExternal: boolean) => boolean | void,
6868
onDragEnd?: (e: React.DragEvent<HTMLElement>, stat: Stat<T>, isOutside: boolean) => void,
6969
onChange: (data: T[]) => void,
70-
dragOpen?: boolean,
71-
// onDragOpen?: (stat: Stat<T>) => void,
7270
openIds?: Id[],
73-
autoOpenParent?: boolean,
74-
onOpenIdsChange?: (ids: Id[]) => void,
7571
checkedIds?: Id[],
7672
}
7773

@@ -80,10 +76,12 @@ export function useHeTree<T extends Record<string, any>>(
8076
) {
8177
const props = { ...defaultProps, ...props0 }
8278
const { idKey: ID, parentIdKey: PID, childrenKey: CHILDREN, placeholderId, isFunctionReactive, } = props
79+
const flatOpt = { idKey: ID, parentIdKey: PID } // shared options for flat data
8380
if (!props.renderNode && !props.renderNodeBox) {
8481
throw new Error("Either renderNodeBox or renderNode is required.");
8582
}
86-
const openIdsStr = useMemo(() => props.openIds?.toString(), [props.openIds])
83+
const openIdsStr = useMemo(() => props.openIds ? [...props.openIds].sort().toString() : '', [props.openIds])
84+
const openIdSet = useMemo(() => new Set(props.openIds), [openIdsStr])
8785
const checkedIdsStr = useMemo(() => props.checkedIds?.toString(), [props.checkedIds])
8886
// mainCache ==================================
8987
const mainCache = useMemo(
@@ -106,7 +104,6 @@ export function useHeTree<T extends Record<string, any>>(
106104
}
107105
}
108106
let count = 0
109-
const openIdSet = new Set(props.openIds)
110107
for (const node of simpleWalk()) {
111108
const id: Id = node[ID] ?? count
112109
const pid = node[PID] as Id
@@ -125,7 +122,7 @@ export function useHeTree<T extends Record<string, any>>(
125122
siblings = parentStat.children
126123
siblingStats = parentStat.childStats
127124
}
128-
const index = siblingIds.indexOf(id)
125+
const index = siblingIds.length
129126
const level = parentStat?.level + 1 || 1
130127
const stat = {
131128
_isStat: true,
@@ -148,32 +145,11 @@ export function useHeTree<T extends Record<string, any>>(
148145
}
149146
stats[id] = stat
150147
nodes[id] = node
151-
if (parentStat) {
152-
parentStat.childIds.push(id)
153-
parentStat.children.push(node)
154-
parentStat.childStats.push(stat)
155-
} else {
156-
rootIds.push(id)
157-
rootNodes.push(node)
158-
rootStats.push(stat)
159-
}
148+
siblingIds.push(id)
149+
siblings.push(node)
150+
siblingStats.push(stat)
160151
count++
161152
}
162-
// open
163-
let allOpenIds: Id[] | null = null;
164-
if (props.openIds) {
165-
allOpenIds = [];
166-
if (props.autoOpenParent) {
167-
for (const id of props.openIds) {
168-
const stat = stats[id];
169-
if (stat) {
170-
for (const curStat of walkParentsGenerator(stat, 'parentStat', { withSelf: false })) {
171-
curStat.open = true
172-
}
173-
}
174-
}
175-
}
176-
}
177153
// checked
178154
let allCheckedIds: Id[] = [];
179155
let semiCheckedIds: Id[] = [];
@@ -209,8 +185,6 @@ export function useHeTree<T extends Record<string, any>>(
209185
draggable = stat.parentStat ? stat.parentStat.draggable : true
210186
}
211187
stat.draggable = draggable
212-
// open
213-
stat.open && allOpenIds?.push(stat.id)
214188
// checked
215189
if (stat.checked) {
216190
allCheckedIds.push(stat.id)
@@ -259,13 +233,13 @@ export function useHeTree<T extends Record<string, any>>(
259233
// root
260234
rootIds, rootNodes, rootStats,
261235
// open & checked
262-
allOpenIds, allCheckedIds, semiCheckedIds,
236+
allCheckedIds, semiCheckedIds,
263237
// methods
264238
getStat,
265239
getDraft,
266240
nextData,
267241
}
268-
}, [props.data, props.dataType, ID, PID, openIdsStr, checkedIdsStr, props.autoOpenParent, props.rootId,
242+
}, [props.data, props.dataType, ID, PID, openIdSet, checkedIdsStr, props.rootId,
269243
isFunctionReactive && props.canDrag,
270244
]
271245
);
@@ -538,37 +512,52 @@ export function useHeTree<T extends Record<string, any>>(
538512
}
539513
if (!customized && !isExternal) {
540514
// move node
541-
const draft = getDraft()
542-
let draggedSiblings: T[], targetSiblings: T[]
543-
if (!placeholder.parentStat) {
544-
targetSiblings = draft
545-
}
546-
let draggedNodeDraft: T
547-
for (const [node, { siblings }] of walkTreeDataGenerator(draft, CHILDREN)) {
548-
if (node[ID] === draggedStat.id) {
549-
draggedSiblings = siblings
550-
draggedNodeDraft = node
551-
}
552-
if (!targetSiblings! && node[ID] === placeholder.parentStat!.id) {
553-
targetSiblings = node[CHILDREN]
515+
// const draft = getDraft()
516+
// let draggedSiblings: T[], targetSiblings: T[]
517+
// if (!placeholder.parentStat) {
518+
// targetSiblings = draft
519+
// }
520+
// let draggedNodeDraft: T
521+
// for (const [node, { siblings }] of walkTreeDataGenerator(draft, CHILDREN)) {
522+
// if (node[ID] === draggedStat.id) {
523+
// draggedSiblings = siblings
524+
// draggedNodeDraft = node
525+
// }
526+
// if (!targetSiblings! && node[ID] === placeholder.parentStat!.id) {
527+
// targetSiblings = node[CHILDREN]
528+
// }
529+
// if (draggedSiblings! && targetSiblings!) {
530+
// break
531+
// }
532+
// }
533+
// const ds = draggedSiblings!
534+
// const ts = targetSiblings!
535+
// const startIndex = ds.findIndex(v => v[ID] === draggedStat.id)
536+
// let targetIndex = placeholder.index
537+
// // check index
538+
// if (ds === ts && placeholder.index > startIndex) {
539+
// targetIndex -= 1
540+
// }
541+
// // remove from start position
542+
// ds.splice(startIndex, 1)
543+
// // move to target position
544+
// ts.splice(targetIndex, 0, draggedNodeDraft!)
545+
// props.onChange(nextData())
546+
if (props.dataType === 'flat') {
547+
const startIndex = props.data.findIndex(v => v[ID] === draggedStat.id)
548+
let targetIndexInSiblings = placeholder.index
549+
if (placeholder.parentStat === draggedStat.parentStat && draggedStat.index < targetIndexInSiblings) {
550+
targetIndexInSiblings--
554551
}
555-
if (draggedSiblings! && targetSiblings!) {
556-
break
557-
}
558-
}
559-
const ds = draggedSiblings!
560-
const ts = targetSiblings!
561-
const startIndex = ds.findIndex(v => v[ID] === draggedStat.id)
562-
let targetIndex = placeholder.index
563-
// check index
564-
if (ds === ts && placeholder.index > startIndex) {
565-
targetIndex -= 1
552+
const targetParentId = placeholder.parentStat?.id ?? null
553+
const newData = [...props.data];
554+
const removed = removeByIdInFlatData(newData, draggedStat.id, flatOpt)
555+
const newNode = { ...draggedStat.node, [PID]: targetParentId }
556+
removed[0] = newNode
557+
const targetTreeIndex = convertIndexToTreeIndexInFlatData(newData, targetParentId, targetIndexInSiblings, flatOpt)
558+
newData.splice(targetTreeIndex, 0, ...removed)
559+
props.onChange!(newData)
566560
}
567-
// remove from start position
568-
ds.splice(startIndex, 1)
569-
// move to target position
570-
ts.splice(targetIndex, 0, draggedNodeDraft!)
571-
props.onChange(nextData())
572561
}
573562
}
574563
if (!customized) {
@@ -1015,6 +1004,66 @@ export function removeByIdInFlatData<T extends Record<Id, any>>(
10151004
return flatData.splice(startIndex, endIndex - startIndex)
10161005
}
10171006

1007+
// 'open' utils methods =============
1008+
export function openParentsInFlatData<T extends Record<Id, any>>(
1009+
flatData: T[],
1010+
openIds: Id[],
1011+
idOrIds: Id | Id[],
1012+
options0?: Partial<typeof flatDataDefaultOptions>
1013+
) {
1014+
const options = { ...flatDataDefaultOptions, ...options0 }
1015+
const { idKey: ID, parentIdKey: PID } = options
1016+
const openIdSet = new Set(openIds)
1017+
const idsToOpen = Array.isArray(idOrIds) ? idOrIds : [idOrIds];
1018+
const idsToOpenSet = new Set(idsToOpen)
1019+
if (idsToOpenSet.size > 0) {
1020+
for (const [node, { parents }] of walkFlatDataGenerator(flatData, options)) {
1021+
const id = node[ID];
1022+
if (idsToOpenSet.has(id)) {
1023+
for (const parent of parents) {
1024+
openIdSet.add(parent[ID])
1025+
}
1026+
idsToOpenSet.delete(id)
1027+
if (idsToOpenSet.size === 0) {
1028+
break
1029+
}
1030+
}
1031+
}
1032+
}
1033+
return Array.from(openIdSet).sort()
1034+
}
1035+
const treeDataDefaultOptions = {
1036+
idKey: 'id',
1037+
childrenKey: 'children',
1038+
}
1039+
export function openParentsInTreeData<T extends Record<Id, any>>(
1040+
treeData: T[],
1041+
openIds: Id[],
1042+
idOrIds: Id | Id[],
1043+
options0?: Partial<typeof treeDataDefaultOptions>
1044+
) {
1045+
const options = { ...treeDataDefaultOptions, ...options0 }
1046+
const { idKey: ID, childrenKey: CHILDREN } = options
1047+
const openIdSet = new Set(openIds)
1048+
const idsToOpen = Array.isArray(idOrIds) ? idOrIds : [idOrIds];
1049+
const idsToOpenSet = new Set(idsToOpen)
1050+
if (idsToOpenSet.size > 0) {
1051+
for (const [node, { parents }] of walkTreeDataGenerator(treeData, options.childrenKey)) {
1052+
const id = node[ID];
1053+
if (idsToOpenSet.has(id)) {
1054+
for (const parent of parents) {
1055+
openIdSet.add(parent[ID])
1056+
}
1057+
idsToOpenSet.delete(id)
1058+
if (idsToOpenSet.size === 0) {
1059+
break
1060+
}
1061+
}
1062+
}
1063+
}
1064+
return Array.from(openIdSet).sort()
1065+
}
1066+
10181067
// private methods
10191068
function calculateDistance(x1: number, y1: number, x2: number, y2: number) {
10201069
return Math.sqrt(Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2));

src/App.tsx

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -115,38 +115,14 @@ function App() {
115115
{ "id": 100, "pid": 50, "name": "Black Holes" },
116116
{ "id": 101, "pid": 50, "name": "Wormholes" }
117117
], 'id', 'pid'));
118-
const toggleOpen = (id) => {
119-
// setflatData(draft => {
120-
// let x = draft.find(v => v.id === node.id)
121-
// x.open = !x.open
122-
// })
123-
// let a = new Set(openIds)
124-
// if (a.has(id)) {
125-
// a.delete(id)
126-
// } else {
127-
// a.add(id)
128-
// }
129-
// setopenIds([...a])
130-
}
131-
let stop = false
132-
function* aaa() {
133-
for (const a of [4, 5]) {
134-
yield a;
135-
if (stop) {
136-
return
137-
}
138-
yield* b()
139-
}
140-
function* b() {
141-
yield 6
142-
yield 7
143-
}
144-
}
145-
for (const a of aaa()) {
146-
console.log(a);
147-
if (a === 4) {
148-
stop = true
118+
const updateOpen = (id, open) => {
119+
let ids = [...openIds];
120+
if (open) {
121+
ids.push(id)
122+
} else {
123+
ids.splice(ids.indexOf(id), 1)
149124
}
125+
setopenIds(ids)
150126
}
151127
const toggleCheck = (id) => {
152128
let a = new Set(checkedIds)
@@ -163,7 +139,7 @@ function App() {
163139
data: flatData,
164140
dataType: 'flat',
165141
renderNode: ({ id, node, open, checked }) => <div>
166-
<button onClick={() => { toggleOpen(id) }}>{open ? '-' : '+'}</button>
142+
<button onClick={() => { updateOpen(id, !open) }}>{open ? '-' : '+'}</button>
167143
<input type="checkbox" checked={Boolean(checked)} onChange={() => { toggleCheck(id) }} />
168144
{node?.name}
169145
</div>,

src/test/flatData.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
convertIndexToTreeIndexInFlatData,
55
addToFlatData,
66
removeByIdInFlatData,
7+
openParentsInFlatData,
78
} from "../../lib/HeTree";
89

910
test("walkFlatDataGenerator: node, treeIndex", () => {
@@ -216,9 +217,31 @@ test("removeByIdInFlatData", () => {
216217
expect(removed[0].key).toBe(10);
217218
expect(cur.length).toBe(data.length - 1);
218219
});
220+
test("openParentsInFlatData", () => {
221+
let data = createData();
222+
let cur = [...data];
223+
let openIds = [3];
224+
let newOpenids = openParentsInFlatData(cur, openIds, 10);
225+
expect(newOpenids.toString()).toBe("1,2,3,5");
226+
newOpenids = openParentsInFlatData(cur, [], 10);
227+
expect(newOpenids.toString()).toBe("1,2,5");
228+
newOpenids = openParentsInFlatData(cur, [], 11);
229+
expect(newOpenids.toString()).toBe("");
230+
});
219231

220232
function createData(id = "id", parent_id = "parent_id") {
221233
// size 9
234+
/* structure
235+
1
236+
2
237+
5
238+
10
239+
4
240+
8
241+
3
242+
7
243+
6
244+
*/
222245
return [
223246
{
224247
[id]: 1,

src/test/treeData.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
walkTreeData,
44
findTreeData,
55
filterTreeData,
6+
openParentsInTreeData,
67
} from "../../lib/HeTree";
78

89
test("walkTreeDataGenerator", () => {
@@ -26,7 +27,6 @@ test("walkTreeDataGenerator: exitWalk", () => {
2627
node,
2728
{ parent, index, parents, skipChildren, exitWalk },
2829
] of walkTreeDataGenerator(data)) {
29-
console.log(node.text);
3030
if (i === 3) {
3131
exitWalk();
3232
}
@@ -142,6 +142,19 @@ test("filterTreeData", () => {
142142
r = filterTreeData(data, (node) => node.text === "===========");
143143
expect(r.length).toBe(0);
144144
});
145+
test("openParentsInTreeData", () => {
146+
let data = createData();
147+
let cur = [...data];
148+
let openIds = ["Frontend"];
149+
let newOpenids = openParentsInTreeData(cur, openIds, "The Godfather", {
150+
idKey: "text",
151+
});
152+
expect(newOpenids.toString()).toBe("Frontend,Movie,Videos");
153+
newOpenids = openParentsInTreeData(cur, [], "Next", { idKey: "text" });
154+
expect(newOpenids.toString()).toBe("Frontend,Projects,React");
155+
newOpenids = openParentsInTreeData(cur, [], "===================");
156+
expect(newOpenids.toString()).toBe("");
157+
});
145158

146159
function createData() {
147160
// return example tree data

0 commit comments

Comments
 (0)