@ticatec/uniface-element 0.3.15 → 0.3.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,577 @@
1
+ # TreeNode - Tree Node Data Structure
2
+
3
+ ## Overview
4
+
5
+ `TreeNode` is a class that represents nodes in a tree structure. It encapsulates the node's data, child nodes, expansion state, and provides convenient methods to manipulate the node itself and its children.
6
+
7
+ Each node created from `TreeNodes` or `CommonTreeNodes` is an instance of the `TreeNode` class with built-in methods.
8
+
9
+ ## TreeNode Class
10
+
11
+ ```typescript
12
+ class TreeNode<T> {
13
+ /** The node's data object (readonly) */
14
+ public readonly item: T;
15
+
16
+ /** Whether the node is expanded (showing children) */
17
+ public expand: boolean;
18
+
19
+ /** Whether the node is currently loading (for lazy loading) */
20
+ public loading: boolean;
21
+
22
+ /** Parent node (null for root nodes) */
23
+ public parent: TreeNode<T> | null;
24
+
25
+ /** Child nodes (readonly, use methods to modify) */
26
+ get children(): ReadonlyArray<TreeNode<T>>;
27
+
28
+ /** Add a child node to the current node */
29
+ append(childItem: T): void;
30
+
31
+ /** Remove the current node (from its parent) */
32
+ detach(): void;
33
+
34
+ /** Move the current node to a different parent */
35
+ moveTo(newParent: TreeNode<T>): void;
36
+
37
+ /** Replace the current node's data */
38
+ replace(newItem: T): void;
39
+
40
+ /** Remove a specific child node by ID */
41
+ removeChild(childId: any): void;
42
+
43
+ /** Remove all child nodes */
44
+ removeChildren(): void;
45
+ }
46
+ ```
47
+
48
+ ## NodeViewOptions Class
49
+
50
+ The `NodeViewOptions` class handles view configuration for tree nodes, separating display concerns from data:
51
+
52
+ ```typescript
53
+ class NodeViewOptions<T> {
54
+ /** Field name for unique identifier */
55
+ keyField: keyof T;
56
+
57
+ /** Field name or function for display text */
58
+ textField: keyof T | ((data: T) => string);
59
+
60
+ /** Optional: Field name or function for icon */
61
+ iconField?: keyof T | ((data: T) => string);
62
+
63
+ /** Optional: Field name or function for CSS class */
64
+ cssClassField?: keyof T | ((data: T) => string);
65
+
66
+ /** Optional: Field name or function for inline styles */
67
+ styleField?: keyof T | ((data: T) => Record<string, string>);
68
+
69
+ /** Get display text from data */
70
+ getText(data: T): string;
71
+
72
+ /** Get icon from data */
73
+ getIcon(data: T): string | undefined;
74
+
75
+ /** Get CSS class from data */
76
+ getCssClass(data: T): string | undefined;
77
+
78
+ /** Get styles from data */
79
+ getStyle(data: T): Record<string, string> | undefined;
80
+
81
+ /** Get unique key from data */
82
+ getKey(data: T): any;
83
+ }
84
+ ```
85
+
86
+ ## TreeNode Methods
87
+
88
+ ### 1. append(childItem)
89
+
90
+ Add a child node to the current node.
91
+
92
+ **Parameter**:
93
+ - `childItem: T` - The child node's data object
94
+
95
+ **Example**:
96
+ ```typescript
97
+ // Pass data object directly
98
+ parentNode.append({
99
+ id: 123,
100
+ name: "New Child",
101
+ parentId: parentNode.item.id // Will be set automatically
102
+ });
103
+ ```
104
+
105
+ **Notes**:
106
+ - Automatically initializes children array if needed
107
+ - If a sort function is configured, nodes will be automatically sorted
108
+ - The parent's `expand` is automatically set to `true`
109
+ - Triggers version update for reactive updates
110
+
111
+ ---
112
+
113
+ ### 2. detach()
114
+
115
+ Remove the current node from its parent.
116
+
117
+ **Parameter**: None
118
+
119
+ **Example**:
120
+ ```typescript
121
+ // Remove the node from its parent
122
+ node.detach();
123
+
124
+ // Clear reference after removal
125
+ activeNode.detach();
126
+ activeNode = null;
127
+ ```
128
+
129
+ **Notes**:
130
+ - Removes from parent's `children` array
131
+ - Deletes from internal map
132
+ - If it's a root node, removes from root node list
133
+ - Triggers version update for reactive updates
134
+
135
+ ---
136
+
137
+ ### 3. moveTo(newParent)
138
+
139
+ Move the current node to a different parent node.
140
+
141
+ **Parameter**:
142
+ - `newParent: TreeNode<T>` - The new parent node (not an ID)
143
+
144
+ **Example**:
145
+ ```typescript
146
+ // Move to a different parent node
147
+ node.moveTo(newParentNode);
148
+
149
+ // Get parent node first
150
+ const parentNode = treeNodes.nodeMap.get(parentId);
151
+ if (parentNode) {
152
+ node.moveTo(parentNode);
153
+ }
154
+ ```
155
+
156
+ **Notes**:
157
+ - Takes a TreeNode instance, not an ID
158
+ - Automatically updates the node's `parentKeyField` in data
159
+ - Removes from current parent's `children`
160
+ - Adds to new parent's `children`
161
+ - If a sort function is configured, will automatically re-sort
162
+ - Triggers version update for reactive updates
163
+
164
+ ---
165
+
166
+ ### 4. replace(newItem)
167
+
168
+ Replace the current node's data.
169
+
170
+ **Parameter**:
171
+ - `newItem: T` - New data object
172
+
173
+ **Example**:
174
+ ```typescript
175
+ // Update some fields
176
+ node.replace({
177
+ ...node.item, // Keep other fields
178
+ name: "New Name", // Update name
179
+ status: "active" // Update status
180
+ });
181
+
182
+ // Or complete replacement
183
+ node.replace({
184
+ id: node.item.id, // Must keep ID
185
+ name: "Completely New Data",
186
+ newField: "value"
187
+ });
188
+ ```
189
+
190
+ **Notes**:
191
+ - Usually need to keep the `id` field unchanged
192
+ - If a sort function is configured and sort field changes, will automatically re-sort
193
+ - Automatically triggers view update
194
+ - Preserves children, expand state, and loading state
195
+
196
+ ---
197
+
198
+ ### 5. removeChild(childId)
199
+
200
+ Remove a specific child node.
201
+
202
+ **Parameter**:
203
+ - `childId: any` - ID of the child node to remove
204
+
205
+ **Example**:
206
+ ```typescript
207
+ // Remove child with specific ID
208
+ parentNode.removeChild(123);
209
+
210
+ // Use variable
211
+ const childId = childNode.item.id;
212
+ parentNode.removeChild(childId);
213
+ ```
214
+
215
+ **Notes**:
216
+ - Only removes direct children, not grandchildren recursively
217
+ - Silently ignores if child doesn't exist
218
+ - All descendants of the child node are also removed
219
+ - Triggers version update for reactive updates
220
+
221
+ ---
222
+
223
+ ### 6. removeChildren()
224
+
225
+ Remove all child nodes.
226
+
227
+ **Parameter**: None
228
+
229
+ **Example**:
230
+ ```typescript
231
+ // Clear all children
232
+ parentNode.removeChildren();
233
+
234
+ // Remove and then add new children
235
+ parentNode.removeChildren();
236
+ parentNode.append(newChildData);
237
+ ```
238
+
239
+ **Notes**:
240
+ - Removes all children and their descendants
241
+ - Node's `expand` state remains unchanged
242
+ - Internal map is updated accordingly
243
+ - Triggers version update for reactive updates
244
+
245
+ ## CommonTreeNodes Options
246
+
247
+ When creating a tree, you can configure it with these options:
248
+
249
+ ```typescript
250
+ interface TreeNodeOptions<T> {
251
+ /** Field name for unique identifier (default: 'id') */
252
+ keyField?: keyof T;
253
+
254
+ /** Field name or function for display text (default: 'text') */
255
+ textField?: keyof T | ((data: T) => string);
256
+
257
+ /** Field name for parent reference (default: 'parentId') */
258
+ parentKeyField?: keyof T;
259
+
260
+ /** Function to check if data is a root node */
261
+ checkIsRoot: (data: T) => boolean;
262
+
263
+ /** Optional: Function to check if node is a branch (has children) */
264
+ checkIsDirectory?: (node: TreeNode<T>) => boolean;
265
+
266
+ /** Optional: Comparison function for sorting */
267
+ compareFun?: (o1: T, o2: T) => number | undefined;
268
+
269
+ /** Optional: Expand depth (default: 1) */
270
+ expendDepth?: number;
271
+ }
272
+ ```
273
+
274
+ ## Complete Usage Example
275
+
276
+ ```svelte
277
+ <script>
278
+ import TreeView from "@ticatec/uniface-element/TreeView";
279
+ import { CommonTreeNodes, type TreeNode } from "@ticatec/uniface-element/lib/TreeNodes";
280
+
281
+ // Data type definition
282
+ interface MyData {
283
+ id: number;
284
+ name: string;
285
+ parentId: number | null;
286
+ }
287
+
288
+ let activeNode: TreeNode<MyData> | null = null;
289
+
290
+ // Create tree structure
291
+ const treeNodes = new CommonTreeNodes<MyData>({
292
+ keyField: 'id',
293
+ textField: 'name',
294
+ parentKeyField: 'parentId',
295
+ checkIsRoot: (item) => item.parentId === null,
296
+ checkIsDirectory: (node) => node.children.length > 0,
297
+ expendDepth: 2
298
+ });
299
+
300
+ // Initialize data
301
+ treeNodes.setData([
302
+ { id: 1, name: "Root", parentId: null },
303
+ { id: 2, name: "Child 1", parentId: 1 },
304
+ { id: 3, name: "Child 2", parentId: 1 }
305
+ ]);
306
+
307
+ // Add child node
308
+ function addChild() {
309
+ if (!activeNode) return;
310
+
311
+ activeNode.append({
312
+ id: Date.now(),
313
+ name: "New Child",
314
+ parentId: activeNode.item.id
315
+ });
316
+ }
317
+
318
+ // Delete current node
319
+ function deleteCurrentNode() {
320
+ if (!activeNode) return;
321
+ activeNode.detach();
322
+ activeNode = null;
323
+ }
324
+
325
+ // Delete specific child
326
+ function deleteChild(childId: number) {
327
+ if (!activeNode) return;
328
+ activeNode.removeChild(childId);
329
+ }
330
+
331
+ // Clear all children
332
+ function clearChildren() {
333
+ if (!activeNode) return;
334
+ activeNode.removeChildren();
335
+ }
336
+
337
+ // Rename node
338
+ function renameNode(newName: string) {
339
+ if (!activeNode) return;
340
+ activeNode.replace({
341
+ ...activeNode.item,
342
+ name: newName
343
+ });
344
+ }
345
+
346
+ // Move node
347
+ function moveNodeTo(newParentId: number) {
348
+ if (!activeNode) return;
349
+ const newParent = treeNodes.nodeMap.get(newParentId);
350
+ if (newParent) {
351
+ activeNode.moveTo(newParent);
352
+ }
353
+ }
354
+ </script>
355
+
356
+ <div>
357
+ <TreeView
358
+ nodes={treeNodes.nodes}
359
+ version={treeNodes.version}
360
+ textField="name"
361
+ bind:activeNode
362
+ checkIsDirectory={(node) => node.children.length > 0}
363
+ />
364
+
365
+ {#if activeNode}
366
+ <div class="node-actions">
367
+ <h3>Selected: {activeNode.item.name}</h3>
368
+ <button on:click={addChild}>Add Child</button>
369
+ <button on:click={deleteCurrentNode}>Delete Node</button>
370
+ <button on:click={() => renameNode("New Name")}>Rename</button>
371
+ <button on:click={() => moveNodeTo(1)}>Move to Root</button>
372
+ <button on:click={clearChildren}>Clear Children</button>
373
+ </div>
374
+ {/if}
375
+ </div>
376
+ ```
377
+
378
+ ## Difference from TreeNodes Class Methods
379
+
380
+ `TreeNode` instance methods and `CommonTreeNodes`/`TreeNodes` class methods have clear responsibilities:
381
+
382
+ ### TreeNode Instance Methods (For Operating on Specific Node Instances)
383
+
384
+ ```typescript
385
+ // Operate directly on a node instance
386
+ node.append(childItem); // Add child to this node
387
+ node.detach(); // Remove this node from its parent
388
+ node.replace(newItem); // Replace this node's data
389
+ node.moveTo(newParentNode); // Move this node to another parent
390
+ node.removeChild(childId); // Remove specific child
391
+ node.removeChildren(); // Remove all children
392
+ ```
393
+
394
+ ### CommonTreeNodes/TreeNodes Class Methods (For Tree-Level Operations)
395
+
396
+ ```typescript
397
+ // Tree-level operations
398
+ treeNodes.setData(data); // Initialize tree structure
399
+ treeNodes.append(item); // Add new node (auto-find parent)
400
+ treeNodes.nodes; // Get root node list
401
+ treeNodes.version; // Get version number
402
+ treeNodes.getHierarchyList(); // Get expanded node list (TreeNodes only)
403
+ treeNodes.extractDirectories(item); // Extract directory structure (TreeNodes only)
404
+ ```
405
+
406
+ ## Key Properties
407
+
408
+ ### item (readonly)
409
+ The data object stored in the node. Cannot be modified directly. Use `replace()` to update data.
410
+
411
+ ### children (readonly)
412
+ Returns a read-only array of child nodes. To modify children, use the methods:
413
+ - `append()` - add a child
414
+ - `removeChild()` - remove a specific child
415
+ - `removeChildren()` - remove all children
416
+
417
+ ### expand
418
+ Controls whether the node is expanded (showing its children). Can be set directly:
419
+ ```typescript
420
+ node.expand = true; // Expand the node
421
+ node.expand = false; // Collapse the node
422
+ ```
423
+
424
+ ### loading
425
+ Indicates if the node is currently loading data (used for lazy loading). Can be set directly:
426
+ ```typescript
427
+ node.loading = true; // Show loading state
428
+ node.loading = false; // Hide loading state
429
+ ```
430
+
431
+ ### parent
432
+ Reference to the parent node. Is `null` for root nodes.
433
+
434
+ ```typescript
435
+ if (node.parent) {
436
+ console.log("Parent name:", node.parent.item.name);
437
+ }
438
+ ```
439
+
440
+ ## Reactive Updates
441
+
442
+ All node method operations automatically trigger `version` updates, ensuring Svelte components can respond to data changes:
443
+
444
+ ```svelte
445
+ <TreeView
446
+ nodes={treeNodes.nodes}
447
+ version={treeNodes.version} ← Auto-updated
448
+ ...
449
+ />
450
+ ```
451
+
452
+ You don't need to manually trigger updates; node methods handle it automatically.
453
+
454
+ ## FAQ
455
+
456
+ ### Q: How to get a node instance?
457
+
458
+ ```typescript
459
+ // Method 1: Through activeNode (selected node)
460
+ let activeNode: TreeNode | null = null;
461
+ <TreeView bind:activeNode />
462
+
463
+ // Method 2: Through nodeMap (need to keep TreeNodes reference)
464
+ const node = treeNodes.nodeMap.get(nodeId);
465
+ ```
466
+
467
+ ### Q: How to iterate over child nodes?
468
+
469
+ ```typescript
470
+ // children is readonly, but you can iterate over it
471
+ node.children.forEach(child => {
472
+ console.log(child.item.name);
473
+ });
474
+
475
+ // Or use for...of
476
+ for (const child of node.children) {
477
+ console.log(child.item.name);
478
+ }
479
+ ```
480
+
481
+ ### Q: How to find a specific child node?
482
+
483
+ ```typescript
484
+ function findChild(node: TreeNode, childId: number) {
485
+ return node.children.find(child => child.item.id === childId) || null;
486
+ }
487
+ ```
488
+
489
+ ### Q: Can I modify the children array directly?
490
+
491
+ No. The `children` property is read-only. Use the provided methods instead:
492
+ - `append()` to add
493
+ - `removeChild()` to remove specific child
494
+ - `removeChildren()` to remove all
495
+
496
+ ### Q: How to recursively operate on all descendants?
497
+
498
+ ```typescript
499
+ function traverse(node: TreeNode, callback: (node: TreeNode) => void) {
500
+ callback(node);
501
+ for (const child of node.children) {
502
+ traverse(child, callback);
503
+ }
504
+ }
505
+
506
+ // Usage
507
+ traverse(rootNode, (node) => {
508
+ console.log(node.item.name);
509
+ node.expand = true; // Expand all nodes
510
+ });
511
+ ```
512
+
513
+ ### Q: What's the difference between TreeNode and NodeViewOptions?
514
+
515
+ - **TreeNode**: Represents the data structure. Each node is an instance with methods for manipulation.
516
+ - **NodeViewOptions**: Configuration class for how nodes should be displayed (text field, icon, CSS, etc.)
517
+
518
+ This separation allows TreeNode to be used in different contexts (TreeView, TreeDataGrid, etc.) without coupling to view-specific concerns.
519
+
520
+ ## Best Practices
521
+
522
+ 1. **Prefer node methods**: When you have a node instance, calling node methods directly is more intuitive
523
+
524
+ 2. **Keep ID unchanged**: When using `replace()`, ensure the `id` field remains unchanged
525
+
526
+ 3. **Use moveTo correctly**: The `moveTo()` method takes a TreeNode instance, not an ID:
527
+ ```typescript
528
+ // ❌ Wrong
529
+ node.moveTo(123);
530
+
531
+ // ✅ Correct
532
+ const parentNode = treeNodes.nodeMap.get(123);
533
+ node.moveTo(parentNode);
534
+ ```
535
+
536
+ 4. **Version management**: No need to manually manage versions, node methods update automatically
537
+
538
+ 5. **Type safety**: Use TypeScript generics to ensure type safety
539
+
540
+ ```typescript
541
+ // ✅ Recommended: Use generics
542
+ const treeNodes = new CommonTreeNodes<MyData>({ ... });
543
+
544
+ // Node type is automatically inferred
545
+ node.append({ id: 1, name: "..." }); // Type checking
546
+ ```
547
+
548
+ 6. **Children is readonly**: Don't try to modify the children array directly. Always use the provided methods.
549
+
550
+ ## Performance Considerations
551
+
552
+ - All node methods have O(1) or O(n) time complexity, where n is the number of child nodes
553
+ - Delete operations also remove from internal map to maintain consistency
554
+ - For large batch operations, consider using `setData()` to rebuild the tree structure
555
+
556
+ ## Architecture
557
+
558
+ ### Decoupling from View
559
+
560
+ TreeNode is designed to be independent of any specific view component:
561
+ - No view-specific logic in TreeNode
562
+ - NodeViewOptions handles view configuration separately
563
+ - Can be used in TreeView, TreeDataGrid, or any other tree-based component
564
+
565
+ ### Data Immutability
566
+
567
+ The `item` property is readonly to ensure data integrity:
568
+ - Prevents accidental mutations
569
+ - Makes state changes explicit through `replace()` method
570
+ - Helps with debugging and state tracking
571
+
572
+ ## Related Components
573
+
574
+ - `CommonTreeNodes` - Base tree management class
575
+ - `TreeNodes` - Extended tree management with hierarchy features
576
+ - `TreeView` - Tree view component
577
+ - `NodeViewOptions` - View configuration class