@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.
@@ -2054,17 +2054,41 @@
2054
2054
 
2055
2055
  .uniface-tree-view {
2056
2056
  position: relative;
2057
+ display: flex;
2058
+ flex-direction: column;
2057
2059
  background-color: var(--uniface-default-page-content-bg, #ffffff);
2058
2060
  color: var(--uniface-default-text-color, #374151);
2059
2061
  box-sizing: border-box;
2060
2062
  overflow: hidden;
2061
- }
2062
- .uniface-tree-view > div {
2063
- width: 100%;
2064
2063
  height: 100%;
2065
- box-sizing: border-box;
2066
- padding-right: 8px;
2064
+ }
2065
+ .uniface-tree-view .tree-view-title {
2066
+ flex: 0 0 auto;
2067
+ padding: 12px 16px;
2068
+ background-color: var(--uniface-default-page-content-bg, #ffffff);
2069
+ border-bottom: 1px solid var(--uniface-default-border-color, #e5e7eb);
2070
+ cursor: pointer;
2071
+ user-select: none;
2072
+ z-index: 10;
2073
+ overflow: hidden;
2074
+ }
2075
+ .uniface-tree-view .tree-view-title .title-text {
2076
+ font-size: 16px;
2077
+ font-weight: 600;
2078
+ color: var(--uniface-default-text-color, #374151);
2079
+ white-space: nowrap;
2080
+ overflow: hidden;
2081
+ text-overflow: ellipsis;
2082
+ display: block;
2083
+ max-width: 100%;
2084
+ }
2085
+ .uniface-tree-view .tree-view-title:hover {
2086
+ background-color: var(--uniface-treeview-item-bg, #EEF4FF);
2087
+ }
2088
+ .uniface-tree-view .tree-view-content {
2089
+ flex: 1;
2067
2090
  overflow: auto;
2091
+ padding-right: 8px;
2068
2092
  user-select: none;
2069
2093
  }
2070
2094
  .uniface-tree-view .tree-node {
@@ -12,7 +12,7 @@ npm install @ticatec/uniface-element
12
12
 
13
13
  ```typescript
14
14
  import TreeView, { type LazyLoader, type NodeVisibleFun, type OnNodeSelectionChange } from "@ticatec/uniface-element/TreeView";
15
- import TreeNodes, { type TreeNode } from "@ticatec/uniface-element/TreeNodes";
15
+ import TreeNodes, { TreeNode, NodeViewOptions } from "@ticatec/uniface-element/TreeNodes";
16
16
  ```
17
17
 
18
18
  ## Basic Usage
@@ -68,6 +68,7 @@ import TreeNodes, { type TreeNode } from "@ticatec/uniface-element/TreeNodes";
68
68
  | `textField` | `string \| GetText<any>` | Required | Field name or function to get node text |
69
69
  | `activeNode` | `any` | `null` | Currently selected/active node |
70
70
  | `style` | `string` | `""` | Additional CSS styles |
71
+ | `title` | `string` | `undefined` | Optional title displayed at the top of the tree (fixed position, doesn't scroll) |
71
72
  | `lazyLoader` | `LazyLoader \| null` | `null` | Lazy loading configuration |
72
73
  | `isVisible` | `NodeVisibleFun` | `null` | Function to determine node visibility |
73
74
  | `onchange` | `OnNodeSelectionChange` | `null` | Callback when node selection changes |
@@ -75,17 +76,45 @@ import TreeNodes, { type TreeNode } from "@ticatec/uniface-element/TreeNodes";
75
76
  | `onblur` | `((event: FocusEvent) => void) \| null` | `null` | Callback when tree loses focus |
76
77
  | `onContextMenu` | `OnContextMenu` | `null` | Right-click context menu handler |
77
78
  | `checkIsDirectory` | `CheckIsDirectory<any>` | Required | Function to check if node is a directory |
79
+ | `version` | `number \| undefined` | `undefined` | Version number to trigger reactive updates (from TreeNodes object) |
78
80
  | `class` | `string` | `""` | CSS class name |
79
81
 
80
82
  ## Type Definitions
81
83
 
82
84
  ### TreeNode<T>
83
85
  ```typescript
84
- interface TreeNode<T> {
85
- item: T; // The actual data item
86
- children?: Array<TreeNode<T>>; // Child nodes
87
- expand?: boolean; // Whether node is expanded
88
- parent?: TreeNode<T>; // Parent node reference
86
+ class TreeNode<T> {
87
+ readonly item: T; // The actual data item
88
+ readonly children: ReadonlyArray<TreeNode<T>>; // Child nodes (read-only)
89
+ expand: boolean; // Whether node is expanded
90
+ loading: boolean; // Whether node is loading (for lazy loading)
91
+ parent: TreeNode<T> | null; // Parent node reference
92
+
93
+ // Methods
94
+ append(childItem: T): void; // Add a child node
95
+ detach(): void; // Remove this node from parent
96
+ moveTo(newParent: TreeNode<T>): void; // Move to different parent
97
+ replace(newItem: T): void; // Replace node data
98
+ removeChild(childId: any): void; // Remove specific child
99
+ removeChildren(): void; // Remove all children
100
+ }
101
+ ```
102
+
103
+ ### NodeViewOptions<T>
104
+ ```typescript
105
+ class NodeViewOptions<T> {
106
+ keyField: keyof T;
107
+ textField: keyof T | ((data: T) => string);
108
+ iconField?: keyof T | ((data: T) => string);
109
+ cssClassField?: keyof T | ((data: T) => string);
110
+ styleField?: keyof T | ((data: T) => Record<string, string>);
111
+
112
+ // Methods
113
+ getText(data: T): string;
114
+ getIcon(data: T): string | undefined;
115
+ getCssClass(data: T): string | undefined;
116
+ getStyle(data: T): Record<string, string> | undefined;
117
+ getKey(data: T): any;
89
118
  }
90
119
  ```
91
120
 
@@ -119,14 +148,14 @@ type NodeVisibleFun = (node: TreeNode<any>) => boolean;
119
148
  let activeNode = null;
120
149
 
121
150
  const fileSystemData = [
122
- { id: 1, name: "=� Documents", type: "folder", parent: null },
123
- { id: 2, name: "=� Pictures", type: "folder", parent: null },
124
- { id: 3, name: "=� Work", type: "folder", parent: 1 },
125
- { id: 4, name: "=� Personal", type: "folder", parent: 1 },
126
- { id: 5, name: "=� resume.pdf", type: "file", parent: 3 },
127
- { id: 6, name: "=� cover-letter.doc", type: "file", parent: 3 },
128
- { id: 7, name: "=� vacation.jpg", type: "file", parent: 2 },
129
- { id: 8, name: "=� family.png", type: "file", parent: 2 }
151
+ { id: 1, name: "=� Documents", type: "folder", parent: null },
152
+ { id: 2, name: "=� Pictures", type: "folder", parent: null },
153
+ { id: 3, name: "=� Work", type: "folder", parent: 1 },
154
+ { id: 4, name: "=� Personal", type: "folder", parent: 1 },
155
+ { id: 5, name: "=� resume.pdf", type: "file", parent: 3 },
156
+ { id: 6, name: "=� cover-letter.doc", type: "file", parent: 3 },
157
+ { id: 7, name: "=� vacation.jpg", type: "file", parent: 2 },
158
+ { id: 8, name: "=� family.png", type: "file", parent: 2 }
130
159
  ];
131
160
 
132
161
  const fileTree = new TreeNodes({
@@ -645,10 +674,10 @@ type NodeVisibleFun = (node: TreeNode<any>) => boolean;
645
674
  switch (type) {
646
675
  case 'country': return '<
647
676
  case 'province':
648
- case 'state': return '<�';
677
+ case 'state': return '<�';
649
678
  case 'district':
650
- case 'city': return '<�';
651
- default: return '=�';
679
+ case 'city': return '<�';
680
+ default: return '=�';
652
681
  }
653
682
  }
654
683
 
@@ -729,6 +758,101 @@ const treeNodes = new TreeNodes({
729
758
  treeNodes.setData(flatData); // Convert flat data to tree structure
730
759
  ```
731
760
 
761
+ ### Using TreeNode Methods
762
+
763
+ TreeNode is now a class with built-in methods for data manipulation:
764
+
765
+ ```typescript
766
+ // Get a node and manipulate it directly
767
+ const node = treeNodes.nodes[0];
768
+
769
+ // Add a child node
770
+ node.append({
771
+ id: 'new-child',
772
+ name: 'New Child',
773
+ parent: node.item.id
774
+ });
775
+
776
+ // Remove the node from tree
777
+ node.detach();
778
+
779
+ // Move node to different parent
780
+ const newParent = treeNodes.nodes[1];
781
+ node.moveTo(newParent);
782
+
783
+ // Replace node data
784
+ node.replace({
785
+ id: node.item.id,
786
+ name: 'Updated Name',
787
+ parent: node.item.parent
788
+ });
789
+
790
+ // Remove specific child
791
+ node.removeChild('child-id');
792
+
793
+ // Remove all children
794
+ node.removeChildren();
795
+
796
+ // Access properties (read-only)
797
+ console.log(node.item); // Node data
798
+ console.log(node.children); // Read-only child array
799
+ console.log(node.expand); // Expanded state
800
+ console.log(node.parent); // Parent node
801
+ ```
802
+
803
+ ### Using NodeViewOptions
804
+
805
+ NodeViewOptions allows you to configure how nodes are displayed:
806
+
807
+ ```typescript
808
+ const viewOptions = new NodeViewOptions({
809
+ keyField: 'code',
810
+ textField: 'name',
811
+ // Or use a function for dynamic text
812
+ textField: (data) => `${data.code} - ${data.name}`,
813
+
814
+ // Optional: icon field
815
+ iconField: 'icon',
816
+ // Or use a function
817
+ iconField: (data) => data.type === 'folder' ? 'folder-icon' : 'file-icon',
818
+
819
+ // Optional: CSS class
820
+ cssClassField: 'status',
821
+ // Or use a function
822
+ cssClassField: (data) => data.active ? 'active-node' : 'inactive-node',
823
+
824
+ // Optional: dynamic styles
825
+ styleField: (data) => ({
826
+ color: data.color,
827
+ fontWeight: data.important ? 'bold' : 'normal'
828
+ })
829
+ });
830
+
831
+ // Use in TreeView
832
+ <TreeView
833
+ nodes={treeNodes.nodes}
834
+ nodeViewOptions={viewOptions}
835
+ />
836
+ ```
837
+
838
+ ### Working with Tree Title
839
+
840
+ The title prop adds a fixed header to the tree view:
841
+
842
+ ```typescript
843
+ <TreeView
844
+ nodes={zones.nodes}
845
+ title="China" // Displays "中国" at the top, doesn't scroll
846
+ // Clicking the title clears the activeNode selection
847
+ />
848
+ ```
849
+
850
+ The title is useful for:
851
+ - Displaying the root entity name (e.g., "China" for provinces)
852
+ - Providing a way to clear selection by clicking
853
+ - Adding context to hierarchical data
854
+ - Organization charts showing company name
855
+
732
856
  ## Accessibility
733
857
 
734
858
  - Keyboard navigation with arrow keys and Enter
@@ -757,5 +881,7 @@ treeNodes.setData(flatData); // Convert flat data to tree structure
757
881
  ## Related Components
758
882
 
759
883
  - `TreeNodes` - Tree data structure utility
884
+ - `TreeNode` - Tree node class with data manipulation methods
885
+ - `NodeViewOptions` - View configuration for tree nodes
760
886
  - `ContextMenu` - Right-click context menu component
761
887
  - `LazyLoader` - Async data loading interface
@@ -51,8 +51,9 @@ import TreeNodes, { type TreeNode } from "@ticatec/uniface-element/TreeNodes";
51
51
  }
52
52
  </script>
53
53
 
54
- <TreeView
54
+ <TreeView
55
55
  nodes={treeNodes.nodes}
56
+ version={treeNodes.version}
56
57
  textField="name"
57
58
  bind:activeNode
58
59
  onchange={handleSelectionChange}
@@ -75,6 +76,7 @@ import TreeNodes, { type TreeNode } from "@ticatec/uniface-element/TreeNodes";
75
76
  | `onblur` | `((event: FocusEvent) => void) \| null` | `null` | 树失去焦点时的回调函数 |
76
77
  | `onContextMenu` | `OnContextMenu` | `null` | 右键上下文菜单处理程序 |
77
78
  | `checkIsDirectory` | `CheckIsDirectory<any>` | 必需 | 判断节点是否为目录的函数 |
79
+ | `version` | `number \| undefined` | `undefined` | 用于触发响应式更新的版本号(从 TreeNodes 对象传入) |
78
80
  | `class` | `string` | `""` | CSS 类名 |
79
81
 
80
82
  ## 类型定义
@@ -727,8 +729,146 @@ const treeNodes = new TreeNodes({
727
729
  });
728
730
 
729
731
  treeNodes.setData(flatData); // 将扁平数据转换为树结构
732
+
733
+ // 动态添加节点
734
+ treeNodes.append(newItem);
735
+
736
+ // 移除节点
737
+ treeNodes.removeItem(oldItem);
738
+
739
+ // 移动节点
740
+ treeNodes.moveTo(itemToMove, newParentId);
741
+ ```
742
+
743
+ ## 动态更新数据
744
+
745
+ TreeView 的每个节点都提供了便捷的方法来操作树结构:
746
+
747
+ ### 节点方法
748
+
749
+ 所有从 TreeNodes 获取的节点都包含以下方法:
750
+
751
+ - `node.append(childItem)` - 添加子节点到当前节点
752
+ - `node.remove()` - 删除当前节点(从父节点中移除)
753
+ - `node.replace(newItem)` - 替换当前节点的数据
754
+ - `node.moveTo(newParentId)` - 将当前节点移动到另一个父节点下
755
+
756
+ ### 使用示例
757
+
758
+ ```svelte
759
+ <script>
760
+ import TreeView from "@ticatec/uniface-element/TreeView";
761
+ import TreeNodes from "@ticatec/uniface-element/TreeNodes";
762
+
763
+ let activeNode = null;
764
+ let zones;
765
+
766
+ const treeNodes = new TreeNodes({
767
+ keyField: 'id',
768
+ textField: 'name',
769
+ parentKeyField: 'parent',
770
+ checkIsRoot: (item) => item.parent === null,
771
+ checkIsDirectory: (node) => node.children != null
772
+ });
773
+
774
+ treeNodes.setData(initialData);
775
+ zones = treeNodes;
776
+
777
+ // 添加子节点到选中的节点
778
+ function addNode() {
779
+ if (!activeNode) return;
780
+
781
+ const newItem = {
782
+ id: Date.now(),
783
+ name: "新节点",
784
+ parent: activeNode.item.id
785
+ };
786
+
787
+ // 方式1:直接在节点上调用 append 方法
788
+ activeNode.append(newItem);
789
+
790
+ // 方式2:使用 TreeNodes 的 append 方法(会自动找到父节点)
791
+ // treeNodes.append(newItem);
792
+
793
+ // version 会自动更新,TreeView 会重新渲染
794
+ }
795
+
796
+ // 删除选中的节点
797
+ function deleteNode() {
798
+ if (activeNode) {
799
+ // 直接在节点上调用 remove 方法
800
+ activeNode.remove();
801
+ activeNode = null;
802
+ }
803
+ }
804
+
805
+ // 替换节点的数据
806
+ function renameNode() {
807
+ if (activeNode) {
808
+ const newItem = {
809
+ ...activeNode.item,
810
+ name: "重命名后的节点"
811
+ };
812
+ activeNode.replace(newItem);
813
+ }
814
+ }
815
+
816
+ // 移动节点到另一个父节点
817
+ function moveNode() {
818
+ if (activeNode) {
819
+ const newParentId = prompt("输入新的父节点 ID:");
820
+ if (newParentId) {
821
+ activeNode.moveTo(newParentId);
822
+ }
823
+ }
824
+ }
825
+ </script>
826
+
827
+ <div>
828
+ <button on:click={addNode} disabled={!activeNode}>添加子节点</button>
829
+ <button on:click={deleteNode} disabled={!activeNode}>删除节点</button>
830
+ <button on:click={renameNode} disabled={!activeNode}>重命名</button>
831
+ <button on:click={moveNode} disabled={!activeNode}>移动节点</button>
832
+
833
+ <TreeView
834
+ nodes={treeNodes.nodes}
835
+ version={treeNodes.version}
836
+ textField="name"
837
+ bind:activeNode
838
+ checkIsDirectory={node => node.children != null}
839
+ />
840
+ </div>
841
+ ```
842
+
843
+ ### TreeNodes 类方法
844
+
845
+ 如果你只有一个数据项,可以使用 `TreeNodes` 类的方法:
846
+
847
+ ```typescript
848
+ // 添加新节点(会自动根据 parentKeyField 找到父节点)
849
+ treeNodes.append(newItem);
850
+
851
+ // 注意:删除、替换、移动操作建议直接在节点上调用方法
852
+ // 因为这些操作通常针对特定的节点实例
853
+ ```
854
+
855
+ ### Lazy 节点注意事项
856
+
857
+ 对于使用 lazyLoader 的节点(尚未展开加载的节点),直接添加子节点会给出警告:
858
+
859
+ ```javascript
860
+ // 如果父节点是 lazy 节点且尚未加载
861
+ if (parentNode.children == null) {
862
+ console.warn('Cannot add child: parent is a lazy-loaded node');
863
+ // 节点会被添加到 nodeMap,但不会添加到 parent.children
864
+ // 这样不会干扰后续的 lazyLoader 行为
865
+ }
730
866
  ```
731
867
 
868
+ 建议在使用前先展开节点,触发 lazyLoader 加载子节点。
869
+
870
+ **重要提示**:必须将 `treeNodes.version` 传递给 `TreeView` 的 `version` 属性,以确保在数据变化时组件能够正确重新渲染。
871
+
732
872
  ## 可访问性
733
873
 
734
874
  - 支持使用箭头键和回车键进行键盘导航
@@ -1,20 +1,20 @@
1
1
  <script lang="ts">
2
2
 
3
- import type {LazyLoader, NodeVisibleFun, OnNodeSelectionChange} from "./Types";
3
+ import type {TreeLazyLoader, NodeVisibleFun, OnNodeSelectionChange} from "./Types";
4
4
  import iconLoading from "../assets/loading.gif"
5
5
  import utils from "../common/utils";
6
6
  import type {OnContextMenu} from "../context-menu/ContextMenuItem";
7
- import type {CheckIsDirectory, GetText, TreeNode} from "../lib/TreeNodes";
7
+ import type {CheckIsDirectory, GetText, ITreeNode} from "../lib/TreeNodes";
8
8
 
9
- export let node: TreeNode<any>;
9
+ export let node: ITreeNode<any>;
10
10
  export let textField: string | GetText<any>;
11
- export let lazyLoader: LazyLoader | null;
11
+ export let lazyLoader: TreeLazyLoader | null;
12
12
  export let activeNode: any;
13
13
  export let isVisible: NodeVisibleFun;
14
14
  export let onContextMenu: OnContextMenu;
15
15
  export let onNodeSelectionChange: OnNodeSelectionChange;
16
16
 
17
- export let checkIsDirectory: CheckIsDirectory<TreeNode<any>>;
17
+ export let checkIsDirectory: CheckIsDirectory<ITreeNode<any>>;
18
18
 
19
19
  let loading: boolean = false;
20
20
 
@@ -26,7 +26,14 @@
26
26
  if (node.children == null && lazyLoader != null) {
27
27
  try {
28
28
  loading = true;
29
- node.children = await lazyLoader.load(node);
29
+ const loadedChildren = await lazyLoader.load(node);
30
+ // 通过 append 方法添加子节点
31
+ for (const child of loadedChildren) {
32
+ node.append({
33
+ ...child,
34
+ expand: child.expand ?? false
35
+ });
36
+ }
30
37
  } finally {
31
38
  loading = false;
32
39
  }
@@ -39,11 +46,18 @@
39
46
  if (respond) {
40
47
  respond = false;
41
48
  if (isDirectory()) {
42
- if (!node.children) {
43
- node.expand = false;
49
+ // 如果是懒加载模式且没有子节点,先加载子节点
50
+ if (lazyLoader != null && !node.children) {
44
51
  await loadChildren();
52
+ // 加载完成后,如果有子节点则展开
53
+ const children = node.children;
54
+ if (children && children.length > 0) {
55
+ node.expand = true;
56
+ }
57
+ } else {
58
+ // 非懒加载模式或已有子节点,直接切换展开状态
59
+ node.expand = !node.expand;
45
60
  }
46
- node.expand = !node.expand;
47
61
  }
48
62
  await utils.sleep(0.2);
49
63
  respond = true;
@@ -1,6 +1,6 @@
1
- import type { LazyLoader, NodeVisibleFun, OnNodeSelectionChange } from "./Types";
1
+ import type { TreeLazyLoader, NodeVisibleFun, OnNodeSelectionChange } from "./Types";
2
2
  import type { OnContextMenu } from "../context-menu/ContextMenuItem";
3
- import type { CheckIsDirectory, GetText, TreeNode } from "../lib/TreeNodes";
3
+ import type { CheckIsDirectory, GetText, ITreeNode } from "../lib/TreeNodes";
4
4
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
5
5
  new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
6
6
  $$bindings?: Bindings;
@@ -15,14 +15,14 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
15
15
  z_$$bindings?: Bindings;
16
16
  }
17
17
  declare const TreeNodeView: $$__sveltets_2_IsomorphicComponent<{
18
- node: TreeNode<any>;
18
+ node: ITreeNode<any>;
19
19
  textField: string | GetText<any>;
20
- lazyLoader: LazyLoader | null;
20
+ lazyLoader: TreeLazyLoader | null;
21
21
  activeNode: any;
22
22
  isVisible: NodeVisibleFun;
23
23
  onContextMenu: OnContextMenu;
24
24
  onNodeSelectionChange: OnNodeSelectionChange;
25
- checkIsDirectory: CheckIsDirectory<TreeNode<any>>;
25
+ checkIsDirectory: CheckIsDirectory<ITreeNode<any>>;
26
26
  }, {
27
27
  [evt: string]: CustomEvent<any>;
28
28
  }, {}, {}, string>;
@@ -4,10 +4,10 @@
4
4
  import type {LazyLoader, NodeVisibleFun, OnNodeSelectionChange} from "./Types";
5
5
  import TreeNodeView from "./TreeNodeView.svelte";
6
6
  import type {OnContextMenu} from "../context-menu/ContextMenuItem";
7
- import type {CheckIsDirectory, GetText, TreeNode} from "../lib/TreeNodes";
7
+ import type {CheckIsDirectory, GetText, ITreeNode} from "../lib/TreeNodes";
8
8
 
9
9
 
10
- export let nodes: Array<TreeNode<any>>;
10
+ export let nodes: Array<ITreeNode<any>>;
11
11
  export let textField: string | GetText<any>;
12
12
  export let style: string = "";
13
13
  export let lazyLoader: LazyLoader | null = null;
@@ -19,9 +19,11 @@
19
19
  export let onblur: ((event: FocusEvent) => void) | null = null;
20
20
  export let onContextMenu: OnContextMenu = null as unknown as OnContextMenu;
21
21
  export let checkIsDirectory: CheckIsDirectory<any>;
22
+ export let version: number | undefined = undefined;
23
+ export let title: string | undefined = undefined;
22
24
  let className: string = "";
23
25
 
24
- const onNodeSelectionChange = async (node: TreeNode<any>): Promise<boolean> => {
26
+ const onNodeSelectionChange = async (node: ITreeNode<any>): Promise<boolean> => {
25
27
  let accept: boolean = (await onchange?.(node));
26
28
  accept = accept == null ? true : accept;
27
29
  if (accept == null || accept == true) {
@@ -30,10 +32,48 @@
30
32
  return accept;
31
33
  }
32
34
 
35
+ const handleTitleClick = async () => {
36
+ let accept: boolean = (await onchange?.(null));
37
+ accept = accept == null ? true : accept;
38
+ if (accept == null || accept == true) {
39
+ activeNode = null;
40
+ }
41
+ }
42
+
43
+ // 通过 version 触发响应式更新:当 version 变化时,重新读取 nodes
44
+ // 这样可以确保在节点数据变化时,TreeView 能够正确重新渲染
45
+ $: reactiveNodes = version !== undefined ? nodes : nodes;
46
+
47
+ // 当 activeNode 变化时,如果是 lazy 节点且未加载,自动触发加载
48
+ $: if (activeNode && lazyLoader && activeNode.children == null) {
49
+ const loadLazyNode = async () => {
50
+ if (activeNode.children == null && lazyLoader != null) {
51
+ try {
52
+ const loadedChildren = await lazyLoader.load(activeNode);
53
+ // 通过 append 方法添加子节点
54
+ for (const child of loadedChildren) {
55
+ activeNode.append({
56
+ ...child,
57
+ expand: child.expand ?? false
58
+ });
59
+ }
60
+ } catch (error) {
61
+ console.error('Failed to load lazy node:', error);
62
+ }
63
+ }
64
+ };
65
+ loadLazyNode();
66
+ }
67
+
33
68
  </script>
34
69
  <div class="uniface-tree-view {className}" {style} on:focus={onfocus} on:blur={onblur} tabindex="0">
35
- <div>
36
- {#each nodes as node}
70
+ {#if title}
71
+ <div class="tree-view-title" on:click={handleTitleClick}>
72
+ <span class="title-text">{title}</span>
73
+ </div>
74
+ {/if}
75
+ <div class="tree-view-content">
76
+ {#each reactiveNodes as node}
37
77
  <TreeNodeView {activeNode} {node} {textField} {lazyLoader} {onNodeSelectionChange} {isVisible} {checkIsDirectory} {onContextMenu}/>
38
78
  {/each}
39
79
  </div>
@@ -1,6 +1,6 @@
1
1
  import type { LazyLoader, NodeVisibleFun, OnNodeSelectionChange } from "./Types";
2
2
  import type { OnContextMenu } from "../context-menu/ContextMenuItem";
3
- import type { CheckIsDirectory, GetText, TreeNode } from "../lib/TreeNodes";
3
+ import type { CheckIsDirectory, GetText, ITreeNode } from "../lib/TreeNodes";
4
4
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
5
5
  new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
6
6
  $$bindings?: Bindings;
@@ -15,7 +15,7 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
15
15
  z_$$bindings?: Bindings;
16
16
  }
17
17
  declare const TreeView: $$__sveltets_2_IsomorphicComponent<{
18
- nodes: Array<TreeNode<any>>;
18
+ nodes: Array<ITreeNode<any>>;
19
19
  textField: string | GetText<any>;
20
20
  style?: string;
21
21
  lazyLoader?: LazyLoader | null;
@@ -27,6 +27,8 @@ declare const TreeView: $$__sveltets_2_IsomorphicComponent<{
27
27
  onblur?: ((event: FocusEvent) => void) | null;
28
28
  onContextMenu?: OnContextMenu;
29
29
  checkIsDirectory: CheckIsDirectory<any>;
30
+ version?: number | undefined;
31
+ title?: string | undefined;
30
32
  }, {
31
33
  [evt: string]: CustomEvent<any>;
32
34
  }, {}, {
@@ -1,23 +1,27 @@
1
- import type { CheckIsDirectory, TreeNode } from "../lib/TreeNodes";
2
- export type OnNodeSelectionChange = (node: TreeNode<any>) => Promise<boolean>;
1
+ import type { CheckIsDirectory, ITreeNode } from "../lib/TreeNodes";
2
+ export type OnNodeSelectionChange = (node: ITreeNode<any>) => Promise<boolean>;
3
3
  /**
4
4
  * 节点是否显示
5
5
  */
6
- export type NodeVisibleFun = (node: TreeNode<any>) => boolean;
6
+ export type NodeVisibleFun = (node: ITreeNode<any>) => boolean;
7
7
  /**
8
8
  * 加载子节点
9
9
  */
10
- export type LoadChildrenFun = (item: TreeNode<any>) => Promise<Array<TreeNode<any>>>;
10
+ export type LoadChildrenFun = (item: ITreeNode<any>) => Promise<Array<ITreeNode<any>>>;
11
11
  /**
12
- * 按需数据加载器
12
+ * 按需加载树节点的子节点
13
13
  */
14
- export interface LazyLoader {
14
+ export interface TreeLazyLoader {
15
15
  /**
16
- * 检查是否是枝节点
16
+ * 检查是否是枝节点(可能包含子节点)
17
17
  */
18
18
  isBranch: CheckIsDirectory<any>;
19
19
  /**
20
- * 数据加载器
20
+ * 加载子节点数据
21
21
  */
22
22
  load: LoadChildrenFun;
23
23
  }
24
+ /**
25
+ * @deprecated 使用 TreeLazyLoader 代替。LazyLoader 已弃用,将在未来版本中移除。
26
+ */
27
+ export type LazyLoader = TreeLazyLoader;
@@ -1,4 +1,5 @@
1
1
  import TreeView from "./TreeView.svelte";
2
- import type { LazyLoader, NodeVisibleFun, OnNodeSelectionChange, LoadChildrenFun } from "./Types";
2
+ import type { TreeLazyLoader, LazyLoader, NodeVisibleFun, OnNodeSelectionChange, LoadChildrenFun } from "./Types";
3
+ export type { ITreeNode, NodeViewOptions } from "../lib/TreeNodes";
3
4
  export default TreeView;
4
- export type { LazyLoader, NodeVisibleFun, OnNodeSelectionChange, LoadChildrenFun };
5
+ export type { TreeLazyLoader, LazyLoader, NodeVisibleFun, OnNodeSelectionChange, LoadChildrenFun };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ticatec/uniface-element",
3
- "version": "0.3.15",
3
+ "version": "0.3.17",
4
4
  "description": "A comprehensive UI component library for Svelte applications with rich form controls, data tables, layouts and interactive elements",
5
5
  "keywords": [
6
6
  "svelte",