1use crate::components::config_provider::use_config;
37use crate::components::select_base::{OptionKey, TreeNode};
38use crate::theme::use_theme;
39use dioxus::prelude::*;
40use std::rc::Rc;
41
42#[derive(Clone, Debug)]
44pub struct FlatTreeNode {
45 pub key: OptionKey,
46 pub label: String,
47 pub disabled: bool,
48 pub depth: usize,
49 pub has_children: bool,
50 pub parent_key: Option<OptionKey>,
51 pub is_last: bool,
53 pub ancestor_is_last: Vec<bool>,
56}
57
58pub fn flatten_tree(
63 nodes: &[TreeNode],
64 depth: usize,
65 parent_key: Option<&str>,
66 out: &mut Vec<FlatTreeNode>,
67) {
68 flatten_tree_with_last(nodes, depth, parent_key, out, &[]);
69}
70
71fn flatten_tree_with_last(
72 nodes: &[TreeNode],
73 depth: usize,
74 parent_key: Option<&str>,
75 out: &mut Vec<FlatTreeNode>,
76 ancestor_is_last: &[bool],
77) {
78 let len = nodes.len();
79 for (idx, node) in nodes.iter().enumerate() {
80 let is_last = idx == len - 1;
81 out.push(FlatTreeNode {
82 key: node.key.clone(),
83 label: node.label.clone(),
84 disabled: node.disabled,
85 depth,
86 has_children: !node.children.is_empty(),
87 parent_key: parent_key.map(|s| s.to_string()),
88 is_last,
89 ancestor_is_last: ancestor_is_last.to_vec(),
90 });
91 if !node.children.is_empty() {
92 let mut next_ancestor_is_last = ancestor_is_last.to_vec();
93 next_ancestor_is_last.push(is_last);
94 flatten_tree_with_last(
95 &node.children,
96 depth + 1,
97 Some(&node.key),
98 out,
99 &next_ancestor_is_last,
100 );
101 }
102 }
103}
104
105fn collect_descendant_keys(nodes: &[TreeNode], target_key: &str) -> Vec<OptionKey> {
107 let mut result = Vec::new();
108 collect_descendant_keys_recursive(nodes, target_key, &mut result, false);
109 result
110}
111
112fn collect_descendant_keys_recursive(
113 nodes: &[TreeNode],
114 target_key: &str,
115 out: &mut Vec<OptionKey>,
116 collecting: bool,
117) -> bool {
118 for node in nodes {
119 let is_target = node.key == target_key;
120 let should_collect = collecting || is_target;
121
122 if should_collect {
123 out.push(node.key.clone());
124 }
125
126 if !node.children.is_empty() {
127 let found =
128 collect_descendant_keys_recursive(&node.children, target_key, out, should_collect);
129 if found && !collecting {
130 return true;
131 }
132 }
133
134 if is_target {
135 return true;
136 }
137 }
138 false
139}
140
141fn collect_parent_keys(flat_nodes: &[FlatTreeNode], target_key: &str) -> Vec<OptionKey> {
143 let mut result = Vec::new();
144 let mut current_key = Some(target_key.to_string());
145
146 while let Some(key) = current_key {
147 if let Some(node) = flat_nodes.iter().find(|n| n.key == key) {
148 if let Some(parent) = &node.parent_key {
149 result.push(parent.clone());
150 current_key = Some(parent.clone());
151 } else {
152 current_key = None;
153 }
154 } else {
155 current_key = None;
156 }
157 }
158
159 result
160}
161
162#[derive(Props, Clone)]
164pub struct TreeProps {
165 #[props(optional)]
167 pub tree_data: Option<Vec<TreeNode>>,
168
169 #[props(optional)]
172 pub expanded_keys: Option<Vec<String>>,
173 #[props(optional)]
175 pub default_expanded_keys: Option<Vec<String>>,
176 #[props(default)]
178 pub default_expand_all: bool,
179 #[props(default = true)]
181 pub auto_expand_parent: bool,
182 #[props(optional)]
184 pub on_expand: Option<EventHandler<Vec<String>>>,
185
186 #[props(optional)]
189 pub selected_keys: Option<Vec<String>>,
190 #[props(optional)]
192 pub default_selected_keys: Option<Vec<String>>,
193 #[props(default = true)]
195 pub selectable: bool,
196 #[props(default)]
198 pub multiple: bool,
199 #[props(optional)]
201 pub on_select: Option<EventHandler<Vec<String>>>,
202
203 #[props(default)]
206 pub checkable: bool,
207 #[props(optional)]
209 pub checked_keys: Option<Vec<String>>,
210 #[props(optional)]
212 pub default_checked_keys: Option<Vec<String>>,
213 #[props(default)]
215 pub check_strictly: bool,
216 #[props(optional)]
218 pub on_check: Option<EventHandler<Vec<String>>>,
219
220 #[props(default)]
223 pub show_line: bool,
224 #[props(default)]
226 pub show_icon: bool,
227 #[props(default)]
229 pub block_node: bool,
230 #[props(default)]
232 pub disabled: bool,
233
234 #[props(optional)]
237 pub draggable: Option<DraggableConfig>,
238 #[props(optional)]
240 pub load_data: Option<Rc<dyn Fn(&TreeNode) -> Vec<TreeNode>>>,
241 #[props(optional)]
243 pub field_names: Option<FieldNames>,
244 #[props(optional)]
246 pub filter_tree_node: Option<Rc<dyn Fn(&TreeNode) -> bool>>,
247 #[props(optional)]
249 pub icon: Option<Rc<dyn Fn(&TreeNode) -> Element>>,
250 #[props(optional)]
252 pub switcher_icon: Option<Rc<dyn Fn(bool, bool) -> Element>>,
253 #[props(optional)]
255 pub title_render: Option<Rc<dyn Fn(&TreeNode) -> Element>>,
256 #[props(optional)]
258 pub loaded_keys: Option<Vec<String>>,
259
260 #[props(optional)]
262 pub class: Option<String>,
263 #[props(optional)]
264 pub style: Option<String>,
265}
266
267#[derive(Clone)]
269pub struct DraggableConfig {
270 pub enabled: bool,
272 pub icon: Option<Element>,
274 pub node_draggable: Option<Rc<dyn Fn(&TreeNode) -> bool>>,
276}
277
278impl PartialEq for DraggableConfig {
279 fn eq(&self, other: &Self) -> bool {
280 self.enabled == other.enabled && self.icon == other.icon
281 }
283}
284
285impl std::fmt::Debug for DraggableConfig {
286 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287 f.debug_struct("DraggableConfig")
288 .field("enabled", &self.enabled)
289 .field("icon", &self.icon)
290 .field("node_draggable", &"<function>")
291 .finish()
292 }
293}
294
295#[derive(Clone, Debug, Default, PartialEq)]
297pub struct FieldNames {
298 pub title: Option<String>,
299 pub key: Option<String>,
300 pub children: Option<String>,
301}
302
303impl PartialEq for TreeProps {
304 fn eq(&self, other: &Self) -> bool {
305 self.tree_data == other.tree_data
307 && self.expanded_keys == other.expanded_keys
308 && self.default_expanded_keys == other.default_expanded_keys
309 && self.default_expand_all == other.default_expand_all
310 && self.on_expand == other.on_expand
311 && self.selected_keys == other.selected_keys
312 && self.default_selected_keys == other.default_selected_keys
313 && self.selectable == other.selectable
314 && self.multiple == other.multiple
315 && self.on_select == other.on_select
316 && self.checkable == other.checkable
317 && self.checked_keys == other.checked_keys
318 && self.default_checked_keys == other.default_checked_keys
319 && self.check_strictly == other.check_strictly
320 && self.on_check == other.on_check
321 && self.show_line == other.show_line
322 && self.show_icon == other.show_icon
323 && self.block_node == other.block_node
324 && self.disabled == other.disabled
325 && self.class == other.class
326 && self.style == other.style
327 && self.draggable == other.draggable
328 && self.field_names == other.field_names
329 && self.loaded_keys == other.loaded_keys
330 }
332}
333
334#[component]
336pub fn Tree(props: TreeProps) -> Element {
337 let TreeProps {
338 tree_data,
339 expanded_keys,
340 default_expanded_keys,
341 default_expand_all,
342 auto_expand_parent: _,
343 on_expand,
344 selected_keys,
345 default_selected_keys,
346 selectable,
347 multiple,
348 on_select,
349 checkable,
350 checked_keys,
351 default_checked_keys,
352 check_strictly,
353 on_check,
354 show_line,
355 show_icon,
356 block_node,
357 disabled,
358 class,
359 style,
360 ..
361 } = props;
362
363 let config = use_config();
364 let theme = use_theme();
365 let tokens = theme.tokens();
366
367 let is_disabled = disabled || config.disabled;
368
369 let nodes: Vec<TreeNode> = tree_data.unwrap_or_default();
371
372 let mut flat_nodes: Vec<FlatTreeNode> = Vec::new();
374 flatten_tree(&nodes, 0, None, &mut flat_nodes);
375
376 let all_parent_keys: Vec<String> = flat_nodes
378 .iter()
379 .filter(|n| n.has_children)
380 .map(|n| n.key.clone())
381 .collect();
382
383 let initial_expanded = if default_expand_all {
385 all_parent_keys.clone()
386 } else {
387 default_expanded_keys.unwrap_or_default()
388 };
389 let internal_expanded: Signal<Vec<String>> = use_signal(|| initial_expanded);
390
391 let is_expand_controlled = expanded_keys.is_some();
392 let current_expanded = if is_expand_controlled {
393 expanded_keys.clone().unwrap_or_default()
394 } else {
395 internal_expanded.read().clone()
396 };
397
398 let initial_selected = default_selected_keys.unwrap_or_default();
400 let internal_selected: Signal<Vec<String>> = use_signal(|| initial_selected);
401
402 let is_select_controlled = selected_keys.is_some();
403 let current_selected = if is_select_controlled {
404 selected_keys.clone().unwrap_or_default()
405 } else {
406 internal_selected.read().clone()
407 };
408
409 let initial_checked = default_checked_keys.unwrap_or_default();
411 let internal_checked: Signal<Vec<String>> = use_signal(|| initial_checked);
412
413 let is_check_controlled = checked_keys.is_some();
414 let current_checked = if is_check_controlled {
415 checked_keys.clone().unwrap_or_default()
416 } else {
417 internal_checked.read().clone()
418 };
419
420 let active_index: Signal<Option<usize>> = use_signal(|| None);
422
423 let visible_nodes: Vec<FlatTreeNode> = {
425 let mut result = Vec::new();
426 let mut skip_depth: Option<usize> = None;
427
428 for node in &flat_nodes {
429 if let Some(sd) = skip_depth {
431 if node.depth > sd {
432 continue;
433 } else {
434 skip_depth = None;
435 }
436 }
437
438 result.push(node.clone());
439
440 if node.has_children && !current_expanded.contains(&node.key) {
442 skip_depth = Some(node.depth);
443 }
444 }
445
446 result
447 };
448
449 let mut class_list = vec!["adui-tree".to_string()];
451 if show_line {
452 class_list.push("adui-tree-show-line".into());
453 }
454 if show_icon {
455 class_list.push("adui-tree-show-icon".into());
456 }
457 if block_node {
458 class_list.push("adui-tree-block-node".into());
459 }
460 if is_disabled {
461 class_list.push("adui-tree-disabled".into());
462 }
463 if let Some(extra) = class {
464 class_list.push(extra);
465 }
466 let class_attr = class_list.join(" ");
467
468 let selected_bg = format!("{}1a", &tokens.color_primary[..7]); let style_attr = format!(
471 "--adui-tree-node-hover-bg: {}; --adui-tree-node-selected-bg: {}; {}",
472 tokens.color_bg_base,
473 selected_bg,
474 style.unwrap_or_default()
475 );
476
477 rsx! {
478 div {
479 class: "{class_attr}",
480 style: "{style_attr}",
481 role: "tree",
482 tabindex: 0,
483 onkeydown: {
484 let visible_for_keydown = visible_nodes.clone();
485 let nodes_for_keydown = nodes.clone();
486 let flat_for_keydown = flat_nodes.clone();
487 let on_expand_cb = on_expand;
488 let on_select_cb = on_select;
489 let on_check_cb = on_check;
490 let current_expanded_for_keydown = current_expanded.clone();
491 let current_selected_for_keydown = current_selected.clone();
492 let current_checked_for_keydown = current_checked.clone();
493 move |evt: KeyboardEvent| {
494 if is_disabled {
495 return;
496 }
497 use dioxus::prelude::Key;
498
499 let nodes_len = visible_for_keydown.len();
500 if nodes_len == 0 {
501 return;
502 }
503
504 let mut active = active_index;
505
506 match evt.key() {
507 Key::ArrowDown => {
508 evt.prevent_default();
509 let current = *active.read();
510 let next = match current {
511 None => Some(0),
512 Some(idx) => Some((idx + 1) % nodes_len),
513 };
514 active.set(next);
515 }
516 Key::ArrowUp => {
517 evt.prevent_default();
518 let current = *active.read();
519 let next = match current {
520 None => Some(nodes_len.saturating_sub(1)),
521 Some(idx) => Some((idx + nodes_len - 1) % nodes_len),
522 };
523 active.set(next);
524 }
525 Key::ArrowRight => {
526 evt.prevent_default();
527 if let Some(idx) = *active.read() {
528 if idx < visible_for_keydown.len() {
529 let node = &visible_for_keydown[idx];
530 if node.has_children && !current_expanded_for_keydown.contains(&node.key) {
531 let mut next_expanded = current_expanded_for_keydown.clone();
533 next_expanded.push(node.key.clone());
534 if let Some(cb) = on_expand_cb {
535 cb.call(next_expanded.clone());
536 }
537 if !is_expand_controlled {
538 let mut signal = internal_expanded;
539 signal.set(next_expanded);
540 }
541 }
542 }
543 }
544 }
545 Key::ArrowLeft => {
546 evt.prevent_default();
547 if let Some(idx) = *active.read() {
548 if idx < visible_for_keydown.len() {
549 let node = &visible_for_keydown[idx];
550 if node.has_children && current_expanded_for_keydown.contains(&node.key) {
551 let next_expanded: Vec<String> = current_expanded_for_keydown
553 .iter()
554 .filter(|k| *k != &node.key)
555 .cloned()
556 .collect();
557 if let Some(cb) = on_expand_cb {
558 cb.call(next_expanded.clone());
559 }
560 if !is_expand_controlled {
561 let mut signal = internal_expanded;
562 signal.set(next_expanded);
563 }
564 }
565 }
566 }
567 }
568 Key::Enter => {
569 evt.prevent_default();
570 if let Some(idx) = *active.read() {
571 if idx < visible_for_keydown.len() {
572 let node = &visible_for_keydown[idx];
573 if node.disabled {
574 return;
575 }
576
577 if checkable {
578 let next_checked = toggle_check(
580 ¤t_checked_for_keydown,
581 &node.key,
582 check_strictly,
583 &nodes_for_keydown,
584 &flat_for_keydown,
585 );
586 if let Some(cb) = on_check_cb {
587 cb.call(next_checked.clone());
588 }
589 if !is_check_controlled {
590 let mut signal = internal_checked;
591 signal.set(next_checked);
592 }
593 } else if selectable {
594 let next_selected = toggle_selection(
596 ¤t_selected_for_keydown,
597 &node.key,
598 multiple,
599 );
600 if let Some(cb) = on_select_cb {
601 cb.call(next_selected.clone());
602 }
603 if !is_select_controlled {
604 let mut signal = internal_selected;
605 signal.set(next_selected);
606 }
607 }
608 }
609 }
610 }
611 _ => {}
612 }
613 }
614 },
615 ul { class: "adui-tree-list",
616 {visible_nodes.iter().enumerate().map(|(index, node)| {
617 let key = node.key.clone();
618 let label = node.label.clone();
619 let depth = node.depth;
620 let has_children = node.has_children;
621 let node_disabled = node.disabled || is_disabled;
622 let is_last = node.is_last;
623 let ancestor_is_last = node.ancestor_is_last.clone();
624
625 let is_expanded = current_expanded.contains(&key);
626 let is_selected = current_selected.contains(&key);
627 let is_checked = current_checked.contains(&key);
628 let is_active = (*active_index.read()).map(|i| i == index).unwrap_or(false);
629
630 let is_indeterminate = if checkable && !check_strictly && has_children {
632 let descendants = collect_descendant_keys(&nodes, &key);
633 let checked_count = descendants.iter().filter(|k| current_checked.contains(*k)).count();
634 checked_count > 0 && checked_count < descendants.len()
635 } else {
636 false
637 };
638
639 let on_expand_for_node = on_expand;
640 let on_select_for_node = on_select;
641 let on_check_for_node = on_check;
642 let current_expanded_for_node = current_expanded.clone();
643 let current_selected_for_node = current_selected.clone();
644 let current_checked_for_node = current_checked.clone();
645 let nodes_for_node = nodes.clone();
646 let flat_for_node = flat_nodes.clone();
647
648 rsx! {
649 li {
650 key: "{key}",
651 class: {
652 let mut classes = vec!["adui-tree-treenode".to_string()];
653 if is_selected {
654 classes.push("adui-tree-treenode-selected".into());
655 }
656 if node_disabled {
657 classes.push("adui-tree-treenode-disabled".into());
658 }
659 if is_active {
660 classes.push("adui-tree-treenode-active".into());
661 }
662 classes.join(" ")
663 },
664 role: "treeitem",
665 "aria-selected": is_selected,
666 "aria-expanded": if has_children { is_expanded.to_string() } else { String::new() },
667 if show_line {
669 {(0..depth).map(|i| {
670 let ancestor_was_last = ancestor_is_last.get(i).copied().unwrap_or(false);
672 let show_vertical = !ancestor_was_last;
673 rsx! {
674 span {
675 key: "{i}",
676 class: "adui-tree-indent-unit",
677 style: "display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 28px; position: relative;",
678 if show_vertical {
679 span {
680 style: "position: absolute; left: 11px; top: 0; bottom: 0; width: 1px; background: var(--adui-color-border, #d9d9d9);"
681 }
682 }
683 }
684 }
685 })}
686 } else {
687 {(0..depth).map(|i| {
688 rsx! {
689 span {
690 key: "{i}",
691 class: "adui-tree-indent-unit",
692 style: "display: inline-block; width: 24px;"
693 }
694 }
695 })}
696 }
697 span {
699 class: {
700 let mut classes = vec!["adui-tree-switcher".to_string()];
701 if has_children {
702 if is_expanded {
703 classes.push("adui-tree-switcher-open".into());
704 } else {
705 classes.push("adui-tree-switcher-close".into());
706 }
707 } else {
708 classes.push("adui-tree-switcher-leaf".into());
709 }
710 classes.join(" ")
711 },
712 style: if show_line {
713 "display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 28px; position: relative;"
714 } else {
715 ""
716 },
717 onclick: {
718 let key_for_expand = key.clone();
719 let current_expanded_for_expand = current_expanded_for_node.clone();
720 move |evt: MouseEvent| {
721 evt.stop_propagation();
722 if !has_children {
723 return;
724 }
725
726 let next_expanded = if current_expanded_for_expand.contains(&key_for_expand) {
727 current_expanded_for_expand
728 .iter()
729 .filter(|k| *k != &key_for_expand)
730 .cloned()
731 .collect()
732 } else {
733 let mut next = current_expanded_for_expand.clone();
734 next.push(key_for_expand.clone());
735 next
736 };
737
738 if let Some(cb) = on_expand_for_node {
739 cb.call(next_expanded.clone());
740 }
741 if !is_expand_controlled {
742 let mut signal = internal_expanded;
743 signal.set(next_expanded);
744 }
745 }
746 },
747 if show_line {
748 if depth > 0 {
750 span {
752 style: "position: absolute; left: 11px; top: 0; height: calc(50% - 5px); width: 1px; background: var(--adui-color-border, #d9d9d9);"
753 }
754 if !is_last {
756 span {
757 style: "position: absolute; left: 11px; top: calc(50% + 5px); bottom: 0; width: 1px; background: var(--adui-color-border, #d9d9d9);"
758 }
759 }
760 span {
762 style: "position: absolute; left: 11px; top: 50%; width: 6px; height: 1px; background: var(--adui-color-border, #d9d9d9); transform: translateY(-50%);"
763 }
764 }
765 if has_children {
767 span {
768 style: "position: relative; z-index: 1; display: inline-flex; align-items: center; justify-content: center; width: 10px; height: 10px; border: 1px solid var(--adui-color-border, #d9d9d9); border-radius: 2px; background: var(--adui-color-bg-container, #fff); font-size: 10px; line-height: 1; cursor: pointer; color: var(--adui-color-text-secondary, rgba(0,0,0,0.65));",
769 if is_expanded { "−" } else { "+" }
770 }
771 } else if depth > 0 {
772 span {
774 style: "position: absolute; left: 17px; top: 50%; width: 6px; height: 1px; background: var(--adui-color-border, #d9d9d9); transform: translateY(-50%);"
775 }
776 }
777 } else if has_children {
778 {
779 let rotate_deg = if is_expanded { 90 } else { 0 };
780 rsx! {
781 span {
782 class: "adui-tree-switcher-icon",
783 style: "display: inline-block; transition: transform 0.2s; transform: rotate({rotate_deg}deg);",
784 "▶"
785 }
786 }
787 }
788 }
789 }
790 if checkable {
792 span {
793 class: {
794 let mut classes = vec!["adui-tree-checkbox".to_string()];
795 if is_checked {
796 classes.push("adui-tree-checkbox-checked".into());
797 }
798 if is_indeterminate {
799 classes.push("adui-tree-checkbox-indeterminate".into());
800 }
801 if node_disabled {
802 classes.push("adui-tree-checkbox-disabled".into());
803 }
804 classes.join(" ")
805 },
806 onclick: {
807 let key_for_check = key.clone();
808 let current_checked_for_check = current_checked_for_node.clone();
809 let nodes_for_check = nodes_for_node.clone();
810 let flat_for_check = flat_for_node.clone();
811 move |evt: MouseEvent| {
812 evt.stop_propagation();
813 if node_disabled {
814 return;
815 }
816
817 let next_checked = toggle_check(
818 ¤t_checked_for_check,
819 &key_for_check,
820 check_strictly,
821 &nodes_for_check,
822 &flat_for_check,
823 );
824
825 if let Some(cb) = on_check_for_node {
826 cb.call(next_checked.clone());
827 }
828 if !is_check_controlled {
829 let mut signal = internal_checked;
830 signal.set(next_checked);
831 }
832 }
833 },
834 span { class: "adui-tree-checkbox-inner" }
835 }
836 }
837 span {
839 class: {
840 let mut classes = vec!["adui-tree-node-content-wrapper".to_string()];
841 if is_selected {
842 classes.push("adui-tree-node-selected".into());
843 }
844 classes.join(" ")
845 },
846 onclick: {
847 let key_for_select = key.clone();
848 let current_selected_for_select = current_selected_for_node.clone();
849 move |_| {
850 if node_disabled || !selectable {
851 return;
852 }
853
854 let next_selected = toggle_selection(
855 ¤t_selected_for_select,
856 &key_for_select,
857 multiple,
858 );
859
860 if let Some(cb) = on_select_for_node {
861 cb.call(next_selected.clone());
862 }
863 if !is_select_controlled {
864 let mut signal = internal_selected;
865 signal.set(next_selected);
866 }
867 }
868 },
869 if show_icon {
870 span {
871 class: "adui-tree-iconEle",
872 style: "margin-right: 4px;",
873 if has_children {
874 if is_expanded { "📂" } else { "📁" }
875 } else {
876 "📄"
877 }
878 }
879 }
880 span { class: "adui-tree-title", "{label}" }
881 }
882 }
883 }
884 })}
885 }
886 }
887 }
888}
889
890fn toggle_selection(current: &[String], key: &str, multiple: bool) -> Vec<String> {
892 if multiple {
893 if current.contains(&key.to_string()) {
894 current.iter().filter(|k| *k != key).cloned().collect()
895 } else {
896 let mut next = current.to_vec();
897 next.push(key.to_string());
898 next
899 }
900 } else {
901 if current.contains(&key.to_string()) {
902 Vec::new()
903 } else {
904 vec![key.to_string()]
905 }
906 }
907}
908
909fn toggle_check(
911 current: &[String],
912 key: &str,
913 check_strictly: bool,
914 nodes: &[TreeNode],
915 flat_nodes: &[FlatTreeNode],
916) -> Vec<String> {
917 let is_checked = current.contains(&key.to_string());
918
919 if check_strictly {
920 if is_checked {
922 current.iter().filter(|k| *k != key).cloned().collect()
923 } else {
924 let mut next = current.to_vec();
925 next.push(key.to_string());
926 next
927 }
928 } else {
929 let descendants = collect_descendant_keys(nodes, key);
931
932 if is_checked {
933 current
935 .iter()
936 .filter(|k| !descendants.contains(k))
937 .cloned()
938 .collect()
939 } else {
940 let mut next: Vec<String> = current.to_vec();
942 for dk in descendants {
943 if !next.contains(&dk) {
944 next.push(dk);
945 }
946 }
947
948 let parents = collect_parent_keys(flat_nodes, key);
950 for parent_key in parents {
951 let siblings = collect_descendant_keys(nodes, &parent_key);
952 let all_checked = siblings
953 .iter()
954 .all(|sk| next.contains(sk) || sk == &parent_key);
955 if all_checked && !next.contains(&parent_key) {
956 next.push(parent_key);
957 }
958 }
959
960 next
961 }
962 }
963}
964
965#[derive(Props, Clone, PartialEq)]
967pub struct DirectoryTreeProps {
968 #[props(optional)]
970 pub tree_data: Option<Vec<TreeNode>>,
971
972 #[props(optional)]
974 pub expanded_keys: Option<Vec<String>>,
975 #[props(optional)]
976 pub default_expanded_keys: Option<Vec<String>>,
977 #[props(default)]
978 pub default_expand_all: bool,
979 #[props(optional)]
980 pub on_expand: Option<EventHandler<Vec<String>>>,
981
982 #[props(optional)]
984 pub selected_keys: Option<Vec<String>>,
985 #[props(optional)]
986 pub default_selected_keys: Option<Vec<String>>,
987 #[props(default)]
988 pub multiple: bool,
989 #[props(optional)]
990 pub on_select: Option<EventHandler<Vec<String>>>,
991
992 #[props(optional)]
994 pub class: Option<String>,
995 #[props(optional)]
996 pub style: Option<String>,
997}
998
999#[component]
1001pub fn DirectoryTree(props: DirectoryTreeProps) -> Element {
1002 let DirectoryTreeProps {
1003 tree_data,
1004 expanded_keys,
1005 default_expanded_keys,
1006 default_expand_all,
1007 on_expand,
1008 selected_keys,
1009 default_selected_keys,
1010 multiple,
1011 on_select,
1012 class,
1013 style,
1014 } = props;
1015
1016 let mut class_list = vec!["adui-tree-directory".to_string()];
1017 if let Some(extra) = class {
1018 class_list.push(extra);
1019 }
1020
1021 rsx! {
1022 Tree {
1023 tree_data,
1024 expanded_keys,
1025 default_expanded_keys,
1026 default_expand_all,
1027 on_expand,
1028 selected_keys,
1029 default_selected_keys,
1030 multiple,
1031 on_select,
1032 show_icon: true,
1033 block_node: true,
1034 class: class_list.join(" "),
1035 style,
1036 }
1037 }
1038}
1039
1040#[cfg(test)]
1041mod tests {
1042 use super::*;
1043
1044 #[test]
1045 fn flatten_tree_produces_correct_depth() {
1046 let nodes = vec![TreeNode {
1047 key: "parent".into(),
1048 label: "Parent".into(),
1049 disabled: false,
1050 children: vec![
1051 TreeNode {
1052 key: "child1".into(),
1053 label: "Child 1".into(),
1054 disabled: false,
1055 children: vec![],
1056 },
1057 TreeNode {
1058 key: "child2".into(),
1059 label: "Child 2".into(),
1060 disabled: false,
1061 children: vec![TreeNode {
1062 key: "grandchild".into(),
1063 label: "Grandchild".into(),
1064 disabled: false,
1065 children: vec![],
1066 }],
1067 },
1068 ],
1069 }];
1070
1071 let mut flat = Vec::new();
1072 flatten_tree(&nodes, 0, None, &mut flat);
1073
1074 assert_eq!(flat.len(), 4);
1075 assert_eq!(flat[0].key, "parent");
1076 assert_eq!(flat[0].depth, 0);
1077 assert!(flat[0].has_children);
1078 assert_eq!(flat[1].key, "child1");
1079 assert_eq!(flat[1].depth, 1);
1080 assert!(!flat[1].has_children);
1081 assert_eq!(flat[2].key, "child2");
1082 assert_eq!(flat[2].depth, 1);
1083 assert!(flat[2].has_children);
1084 assert_eq!(flat[3].key, "grandchild");
1085 assert_eq!(flat[3].depth, 2);
1086 }
1087
1088 #[test]
1089 fn toggle_selection_single_mode() {
1090 let current: Vec<String> = vec![];
1091 let next = toggle_selection(¤t, "a", false);
1092 assert_eq!(next, vec!["a".to_string()]);
1093
1094 let next2 = toggle_selection(&next, "b", false);
1095 assert_eq!(next2, vec!["b".to_string()]);
1096
1097 let next3 = toggle_selection(&next2, "b", false);
1098 assert!(next3.is_empty());
1099 }
1100
1101 #[test]
1102 fn toggle_selection_multiple_mode() {
1103 let current: Vec<String> = vec![];
1104 let next = toggle_selection(¤t, "a", true);
1105 assert_eq!(next, vec!["a".to_string()]);
1106
1107 let next2 = toggle_selection(&next, "b", true);
1108 assert_eq!(next2, vec!["a".to_string(), "b".to_string()]);
1109
1110 let next3 = toggle_selection(&next2, "a", true);
1111 assert_eq!(next3, vec!["b".to_string()]);
1112 }
1113
1114 #[test]
1115 fn collect_descendant_keys_finds_all_children() {
1116 let nodes = vec![TreeNode {
1117 key: "root".into(),
1118 label: "Root".into(),
1119 disabled: false,
1120 children: vec![
1121 TreeNode {
1122 key: "a".into(),
1123 label: "A".into(),
1124 disabled: false,
1125 children: vec![TreeNode {
1126 key: "a1".into(),
1127 label: "A1".into(),
1128 disabled: false,
1129 children: vec![],
1130 }],
1131 },
1132 TreeNode {
1133 key: "b".into(),
1134 label: "B".into(),
1135 disabled: false,
1136 children: vec![],
1137 },
1138 ],
1139 }];
1140
1141 let descendants = collect_descendant_keys(&nodes, "a");
1142 assert!(descendants.contains(&"a".to_string()));
1143 assert!(descendants.contains(&"a1".to_string()));
1144 assert!(!descendants.contains(&"b".to_string()));
1145 assert!(!descendants.contains(&"root".to_string()));
1146 }
1147}