1
+ /* eslint-disable react/no-unused-state,@typescript-eslint/consistent-type-assertions */
1
2
import React , {
2
3
ComponentType ,
3
4
PropsWithChildren ,
@@ -87,8 +88,15 @@ export type TreeState<
87
88
TData extends NodeData
88
89
> = Readonly < {
89
90
component : ComponentType < TNodeComponentProps > ;
90
- methods : OverridableMethods ;
91
91
order ?: ReadonlyArray < string | symbol > ;
92
+ computeTree : TreeComputer <
93
+ TNodeComponentProps ,
94
+ TNodeRecord ,
95
+ TUpdateOptions ,
96
+ TData ,
97
+ any ,
98
+ any
99
+ > ;
92
100
records : Readonly < Record < string , TNodeRecord | undefined > > ;
93
101
treeData ?: any ;
94
102
recomputeTree : ( options ?: TUpdateOptions ) => Promise < void > ;
@@ -126,17 +134,81 @@ export const Row = <TData extends NodeData>({
126
134
/>
127
135
) ;
128
136
129
- const computeTree = (
130
- { treeWalker} : TreeProps < any , any > ,
131
- state : TreeState < any , NodeRecord < any > , any , any > ,
132
- options : UpdateOptions = { } ,
137
+ export type TreeCreatorOptions <
138
+ TNodeComponentProps extends NodeComponentProps < TData > ,
139
+ TNodeRecord extends NodeRecord < TData > ,
140
+ TUpdateOptions extends UpdateOptions ,
141
+ TData extends NodeData ,
142
+ TState extends TreeState <
143
+ TNodeComponentProps ,
144
+ TNodeRecord ,
145
+ TUpdateOptions ,
146
+ TData
147
+ >
148
+ > = Readonly < {
149
+ createRecord : ( data : TData , state : TState ) => TNodeRecord ;
150
+ shouldUpdateRecords : ( options : TUpdateOptions ) => boolean ;
151
+ updateRecord : (
152
+ record : TNodeRecord ,
153
+ recordId : string | symbol ,
154
+ options : TUpdateOptions ,
155
+ ) => void ;
156
+ updateRecordOnWalk : ( record : TNodeRecord , options : TUpdateOptions ) => void ;
157
+ } > ;
158
+
159
+ export type TreeComputer <
160
+ TNodeComponentProps extends NodeComponentProps < TData > ,
161
+ TNodeRecord extends NodeRecord < TData > ,
162
+ TUpdateOptions extends UpdateOptions ,
163
+ TData extends NodeData ,
164
+ TProps extends TreeProps < TNodeComponentProps , TData > ,
165
+ TState extends TreeState <
166
+ TNodeComponentProps ,
167
+ TNodeRecord ,
168
+ TUpdateOptions ,
169
+ TData
170
+ >
171
+ > = (
172
+ props : TProps ,
173
+ state : TState ,
174
+ options ?: TUpdateOptions ,
175
+ ) => Pick < TState , 'order' | 'records' > ;
176
+
177
+ export const createTreeComputer = <
178
+ TNodeComponentProps extends NodeComponentProps < TData > ,
179
+ TNodeRecord extends NodeRecord < TData > ,
180
+ TUpdateOptions extends UpdateOptions ,
181
+ TData extends NodeData ,
182
+ TProps extends TreeProps < TNodeComponentProps , TData > ,
183
+ TState extends TreeState <
184
+ TNodeComponentProps ,
185
+ TNodeRecord ,
186
+ TUpdateOptions ,
187
+ TData
188
+ >
189
+ > ( {
190
+ createRecord,
191
+ shouldUpdateRecords,
192
+ updateRecord,
193
+ updateRecordOnWalk,
194
+ } : TreeCreatorOptions <
195
+ TNodeComponentProps ,
196
+ TNodeRecord ,
197
+ TUpdateOptions ,
198
+ TData ,
199
+ TState
200
+ > ) : TreeComputer <
201
+ TNodeComponentProps ,
202
+ TNodeRecord ,
203
+ TUpdateOptions ,
204
+ TData ,
205
+ TProps ,
206
+ TState
207
+ > => (
208
+ { treeWalker} ,
209
+ state ,
210
+ options = { } as TUpdateOptions ,
133
211
) : Pick < TreeState < any , any , any , any > , 'order' | 'records' > => {
134
- const {
135
- constructRecord,
136
- shouldUpdateRecords,
137
- updateRecord,
138
- updateRecordDuringTreeWalk,
139
- } = state . methods ;
140
212
const order : Array < string | symbol > = [ ] ;
141
213
const records = { ...state . records } ;
142
214
const iter = treeWalker ( options . refreshNodes ?? false ) ;
@@ -170,10 +242,10 @@ const computeTree = (
170
242
const record = records [ id as string ] ;
171
243
172
244
if ( ! record ) {
173
- records [ id as string ] = constructRecord ( value , state ) ;
245
+ records [ id as string ] = createRecord ( value , state ) ;
174
246
} else {
175
247
record . data = value ;
176
- updateRecordDuringTreeWalk ( record , options ) ;
248
+ updateRecordOnWalk ( record , options ) ;
177
249
}
178
250
}
179
251
@@ -192,7 +264,7 @@ const computeTree = (
192
264
} ;
193
265
} ;
194
266
195
- abstract class Tree <
267
+ class Tree <
196
268
TNodeComponentProps extends NodeComponentProps < TData > ,
197
269
TNodeRecord extends NodeRecord < TData > ,
198
270
TUpdateOptions extends UpdateOptions ,
@@ -215,7 +287,7 @@ abstract class Tree<
215
287
state : TreeState < any , any , any , any > ,
216
288
) : Partial < TreeState < any , any , any , any > > {
217
289
const { children : component , itemData : treeData , treeWalker} = props ;
218
- const { treeWalker : oldTreeWalker , order } = state ;
290
+ const { computeTree , order , treeWalker : oldTreeWalker } = state ;
219
291
220
292
return {
221
293
component,
@@ -226,68 +298,23 @@ abstract class Tree<
226
298
} ;
227
299
}
228
300
229
- protected static constructRecord (
230
- data : NodeData ,
231
- { recomputeTree} : TreeState < any , any , any , any > ,
232
- ) : NodeRecord < NodeData > {
233
- const record = {
234
- data,
235
- isOpen : data . isOpenByDefault ,
236
- async toggle ( ) : Promise < void > {
237
- record . isOpen = ! record . isOpen ;
238
- await recomputeTree ( { refreshNodes : record . isOpen } ) ;
239
- } ,
240
- } ;
241
-
242
- return record ;
243
- }
244
-
245
- protected static shouldUpdateRecords ( {
246
- opennessState,
247
- useDefaultOpenness = false ,
248
- } : UpdateOptions ) : boolean {
249
- return ! ! opennessState || useDefaultOpenness ;
250
- }
251
-
252
- protected static updateRecord (
253
- record : NodeRecord < NodeData > ,
254
- recordId : string ,
255
- { opennessState, useDefaultOpenness = false } : UpdateOptions ,
256
- ) : void {
257
- record . isOpen = useDefaultOpenness
258
- ? record . data . isOpenByDefault
259
- : opennessState ?. [ recordId ] ?? record . isOpen ;
260
- }
261
-
262
- protected static updateRecordDuringTreeWalk (
263
- record : NodeRecord < NodeData > ,
264
- { useDefaultOpenness = false } : UpdateOptions ,
265
- ) : void {
266
- if ( useDefaultOpenness ) {
267
- record . isOpen = record . data . isOpenByDefault ;
268
- }
269
- }
270
-
271
301
protected readonly list : React . RefObject < TListComponent > = React . createRef ( ) ;
272
302
273
- protected constructor ( props : TProps , context : any ) {
303
+ public constructor ( props : TProps , context : any ) {
274
304
super ( props , context ) ;
275
305
276
- this . state = this . constructState ( {
306
+ this . state = {
277
307
component : props . children ,
278
- // Remembering the current constructor to use overridable static methods
279
- // in computeTree function
280
- methods : ( this . constructor as unknown ) as OverridableMethods ,
281
308
recomputeTree : this . recomputeTree . bind ( this ) ,
282
309
records : { } ,
283
310
treeWalker : props . treeWalker ,
284
- } ) ;
311
+ } as TState ;
285
312
}
286
313
287
314
public async recomputeTree ( options ?: TUpdateOptions ) : Promise < void > {
288
315
return new Promise ( ( resolve ) => {
289
316
this . setState < never > (
290
- ( prevState ) => computeTree ( this . props , prevState , options ) ,
317
+ ( prevState ) => prevState . computeTree ( this . props , prevState , options ) ,
291
318
resolve ,
292
319
) ;
293
320
} ) ;
@@ -301,12 +328,6 @@ abstract class Tree<
301
328
// eslint-disable-next-line react/destructuring-assignment
302
329
this . list . current ?. scrollToItem ( this . state . order ! . indexOf ( id ) , align ) ;
303
330
}
304
-
305
- public abstract render ( ) : React . ReactNode ;
306
-
307
- protected abstract constructState (
308
- state : TreeState < any , any , any , any > ,
309
- ) : TState ;
310
331
}
311
332
312
333
export default Tree ;
0 commit comments