@ticatec/uniface-element 0.3.16 → 0.3.18

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.
@@ -1,5 +1,6 @@
1
- import { CommonTreeNodes } from "../lib/TreeNodes";
2
- export default class Menus extends CommonTreeNodes {
1
+ import {} from "../lib/TreeNodes";
2
+ import TreeNodes from "../lib/TreeNodes";
3
+ export default class Menus extends TreeNodes {
3
4
  /**
4
5
  * 删除所有没有子节点的目录类型节点
5
6
  */
@@ -12,8 +13,9 @@ export default class Menus extends CommonTreeNodes {
12
13
  }
13
14
  else {
14
15
  if (this.checkIsDirectory?.(node)) {
15
- nodes.splice(i, 1); // 从父节点的 children 中移除
16
- this.nodeMap.delete(node.item[this.keyField]); // nodeMap 中移除
16
+ node.detach();
17
+ //nodes.splice(i, 1); // 从父节点的 children 中移除
18
+ //this.nodeMap.delete(node._item[this.keyField]); // 从 nodeMap 中移除
17
19
  }
18
20
  }
19
21
  }
@@ -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 |
@@ -82,11 +83,38 @@ import TreeNodes, { type TreeNode } from "@ticatec/uniface-element/TreeNodes";
82
83
 
83
84
  ### TreeNode<T>
84
85
  ```typescript
85
- interface TreeNode<T> {
86
- item: T; // The actual data item
87
- children?: Array<TreeNode<T>>; // Child nodes
88
- expand?: boolean; // Whether node is expanded
89
- 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;
90
118
  }
91
119
  ```
92
120
 
@@ -730,6 +758,101 @@ const treeNodes = new TreeNodes({
730
758
  treeNodes.setData(flatData); // Convert flat data to tree structure
731
759
  ```
732
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
+
733
856
  ## Accessibility
734
857
 
735
858
  - Keyboard navigation with arrow keys and Enter
@@ -758,5 +881,7 @@ treeNodes.setData(flatData); // Convert flat data to tree structure
758
881
  ## Related Components
759
882
 
760
883
  - `TreeNodes` - Tree data structure utility
884
+ - `TreeNode` - Tree node class with data manipulation methods
885
+ - `NodeViewOptions` - View configuration for tree nodes
761
886
  - `ContextMenu` - Right-click context menu component
762
887
  - `LazyLoader` - Async data loading interface
@@ -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
 
@@ -27,11 +27,10 @@
27
27
  try {
28
28
  loading = true;
29
29
  const loadedChildren = await lazyLoader.load(node);
30
- // 初始化新加载的子节点,确保它们的 expand 属性为 false
31
- node.children = loadedChildren.map(child => ({
32
- ...child,
33
- expand: child.expand ?? false
34
- }));
30
+ for (const childData of loadedChildren) {
31
+ node.append(childData);
32
+ }
33
+ node.expand = true;
35
34
  } finally {
36
35
  loading = false;
37
36
  }
@@ -48,7 +47,7 @@
48
47
  if (lazyLoader != null && !node.children) {
49
48
  await loadChildren();
50
49
  // 加载完成后,如果有子节点则展开
51
- const children = node.children as TreeNode<any>[] | undefined;
50
+ const children = node.children;
52
51
  if (children && children.length > 0) {
53
52
  node.expand = true;
54
53
  }
@@ -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;
@@ -20,9 +20,10 @@
20
20
  export let onContextMenu: OnContextMenu = null as unknown as OnContextMenu;
21
21
  export let checkIsDirectory: CheckIsDirectory<any>;
22
22
  export let version: number | undefined = undefined;
23
+ export let title: string | undefined = undefined;
23
24
  let className: string = "";
24
25
 
25
- const onNodeSelectionChange = async (node: TreeNode<any>): Promise<boolean> => {
26
+ const onNodeSelectionChange = async (node: ITreeNode<any>): Promise<boolean> => {
26
27
  let accept: boolean = (await onchange?.(node));
27
28
  accept = accept == null ? true : accept;
28
29
  if (accept == null || accept == true) {
@@ -31,6 +32,14 @@
31
32
  return accept;
32
33
  }
33
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
+
34
43
  // 通过 version 触发响应式更新:当 version 变化时,重新读取 nodes
35
44
  // 这样可以确保在节点数据变化时,TreeView 能够正确重新渲染
36
45
  $: reactiveNodes = version !== undefined ? nodes : nodes;
@@ -41,10 +50,11 @@
41
50
  if (activeNode.children == null && lazyLoader != null) {
42
51
  try {
43
52
  const loadedChildren = await lazyLoader.load(activeNode);
44
- activeNode.children = loadedChildren.map(child => ({
45
- ...child,
46
- expand: child.expand ?? false
47
- }));
53
+ // 通过 append 方法添加子节点,并设置 expand
54
+ for (const childData of loadedChildren) {
55
+ const childNode = activeNode.append(childData);
56
+ childNode.expand = childData.expand ?? false;
57
+ }
48
58
  } catch (error) {
49
59
  console.error('Failed to load lazy node:', error);
50
60
  }
@@ -55,7 +65,12 @@
55
65
 
56
66
  </script>
57
67
  <div class="uniface-tree-view {className}" {style} on:focus={onfocus} on:blur={onblur} tabindex="0">
58
- <div>
68
+ {#if title}
69
+ <div class="tree-view-title" on:click={handleTitleClick}>
70
+ <span class="title-text">{title}</span>
71
+ </div>
72
+ {/if}
73
+ <div class="tree-view-content">
59
74
  {#each reactiveNodes as node}
60
75
  <TreeNodeView {activeNode} {node} {textField} {lazyLoader} {onNodeSelectionChange} {isVisible} {checkIsDirectory} {onContextMenu}/>
61
76
  {/each}
@@ -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;
@@ -28,6 +28,7 @@ declare const TreeView: $$__sveltets_2_IsomorphicComponent<{
28
28
  onContextMenu?: OnContextMenu;
29
29
  checkIsDirectory: CheckIsDirectory<any>;
30
30
  version?: number | undefined;
31
+ title?: string | undefined;
31
32
  }, {
32
33
  [evt: string]: CustomEvent<any>;
33
34
  }, {}, {
@@ -1,13 +1,13 @@
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<any>>;
11
11
  /**
12
12
  * 按需加载树节点的子节点
13
13
  */
@@ -1,4 +1,5 @@
1
1
  import TreeView from "./TreeView.svelte";
2
2
  import type { TreeLazyLoader, LazyLoader, NodeVisibleFun, OnNodeSelectionChange, LoadChildrenFun } from "./Types";
3
+ export type { ITreeNode, NodeViewOptions } from "../lib/TreeNodes";
3
4
  export default TreeView;
4
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.16",
3
+ "version": "0.3.18",
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",