diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/collapsible/TreeHeightCollapseStrategy.java b/domino-ui/src/main/java/org/dominokit/domino/ui/collapsible/TreeHeightCollapseStrategy.java
index e9ced3112..9ef0b9c64 100644
--- a/domino-ui/src/main/java/org/dominokit/domino/ui/collapsible/TreeHeightCollapseStrategy.java
+++ b/domino-ui/src/main/java/org/dominokit/domino/ui/collapsible/TreeHeightCollapseStrategy.java
@@ -17,20 +17,19 @@
import static org.dominokit.domino.ui.collapsible.Collapsible.DUI_COLLAPSED;
import static org.dominokit.domino.ui.style.GenericCss.dui_transition_none;
-import static org.dominokit.domino.ui.utils.Domino.*;
import static org.dominokit.domino.ui.utils.ElementsFactory.elements;
import elemental2.dom.AddEventListenerOptions;
import elemental2.dom.DomGlobal;
import elemental2.dom.Element;
import elemental2.dom.EventListener;
-import org.dominokit.domino.ui.tree.TreeItem;
+import org.dominokit.domino.ui.tree.TreeNode;
import org.dominokit.domino.ui.utils.DominoId;
import org.dominokit.domino.ui.utils.IsCollapsible;
/**
- * An implementation of {@link org.dominokit.domino.ui.collapsible.CollapseStrategy} that is meant
- * to be used with the {@link org.dominokit.domino.ui.tree.Tree} component
+ * An implementation of {@link CollapseStrategy} that is meant to be used with the {@link
+ * org.dominokit.domino.ui.tree.Tree} component
*/
public class TreeHeightCollapseStrategy implements CollapseStrategy, CollapsibleStyles {
@@ -43,42 +42,42 @@ public class TreeHeightCollapseStrategy implements CollapseStrategy, Collapsible
private final CollapsibleDuration transition;
private final String heightVar;
private CollapsibleHandlers handlers;
- private final TreeItem> treeItem;
+ private final TreeNode, ?, ?> node;
private boolean expanding = false;
private boolean collapsing = false;
/**
* Constructor for TreeHeightCollapseStrategy.
*
- * @param treeItem a {@link org.dominokit.domino.ui.tree.TreeItem} object
+ * @param node a {@link org.dominokit.domino.ui.tree.TreeItem} object
*/
- public TreeHeightCollapseStrategy(TreeItem> treeItem) {
- this(treeItem, CollapsibleDuration._300ms);
+ public TreeHeightCollapseStrategy(TreeNode, ?, ?> node) {
+ this(node, CollapsibleDuration._300ms);
}
/**
* Constructor for TreeHeightCollapseStrategy.
*
- * @param treeItem a {@link org.dominokit.domino.ui.tree.TreeItem} object
+ * @param node a {@link org.dominokit.domino.ui.tree.TreeItem} object
* @param transition a {@link CollapsibleDuration} object
*/
- public TreeHeightCollapseStrategy(TreeItem> treeItem, CollapsibleDuration transition) {
- this.treeItem = treeItem;
+ public TreeHeightCollapseStrategy(TreeNode, ?, ?> node, CollapsibleDuration transition) {
+ this.node = node;
this.transition = transition;
this.heightVar = DominoId.unique(EXPAND_COLLAPSE_HEIGHT_VAR);
- this.treeItem.setAttribute(DUI_EXPAND_COLLAPSE_VAR, this.heightVar);
- this.treeItem.setCssProperty("height", "var(" + this.heightVar + ", auto)");
+ this.node.setAttribute(DUI_EXPAND_COLLAPSE_VAR, this.heightVar);
+ this.node.setCssProperty("height", "var(" + this.heightVar + ", auto)");
}
/** @dominokit-site-ignore {@inheritDoc} */
@Override
public void init(Element element, CollapsibleHandlers handlers) {
this.handlers = handlers;
- this.treeItem.addCss(dui_height_collapsed_overflow).addCss(transition.getStyle());
- this.treeItem.nowOrWhenAttached(
+ this.node.addCss(dui_height_collapsed_overflow).addCss(transition.getStyle());
+ this.node.nowOrWhenAttached(
() -> {
- double height = treeItem.getBoundingClientRect().height;
- treeItem.setAttribute(DUI_COLLAPSED_HEIGHT, height);
+ double height = node.getBoundingClientRect().height;
+ node.setAttribute(DUI_COLLAPSED_HEIGHT, height);
});
element.setAttribute(DUI_COLLAPSED, true);
@@ -94,17 +93,17 @@ public void cleanup(Element element) {
/** @dominokit-site-ignore {@inheritDoc} */
@Override
public void expand(Element element) {
- treeItem.nowOrWhenAttached(
+ node.nowOrWhenAttached(
() -> {
if (!collapsing) {
this.expanding = true;
this.handlers.onBeforeExpand().run();
- double height = treeItem.getBoundingClientRect().height;
- treeItem.setAttribute(DUI_COLLAPSED_HEIGHT, height);
- treeItem.getSubTree().show();
- treeItem.setCssProperty(this.heightVar, height + "px");
+ double height = node.getBoundingClientRect().height;
+ node.setAttribute(DUI_COLLAPSED_HEIGHT, height);
+ node.getSubTree().show();
+ node.setCssProperty(this.heightVar, height + "px");
double expandedHeight = getActualHeight();
- treeItem.setAttribute(DUI_EXPANDED_HEIGHT, expandedHeight);
+ node.setAttribute(DUI_EXPANDED_HEIGHT, expandedHeight);
expandElement(element);
}
});
@@ -112,7 +111,7 @@ public void expand(Element element) {
private double getActualHeight() {
double expandedHeight =
- treeItem.childElements().stream()
+ node.childElements().stream()
.filter(IsCollapsible::isExpanded)
.mapToDouble(e -> e.getBoundingClientRect().height)
.sum();
@@ -120,38 +119,36 @@ private double getActualHeight() {
}
private void expandElement(Element element) {
- if (dui_transition_none.isAppliedTo(treeItem)) {
- treeItem.setCssProperty(this.heightVar, "auto");
- treeItem.removeAttribute(DUI_COLLAPSED);
+ if (dui_transition_none.isAppliedTo(node)) {
+ node.setCssProperty(this.heightVar, "auto");
+ node.removeAttribute(DUI_COLLAPSED);
handlers.onExpandCompleted().run();
expanding = false;
} else {
EventListener stopListener =
evt -> {
- resetParentHeight(treeItem);
- treeItem.setCssProperty(this.heightVar, "auto");
+ resetParentHeight(node);
+ node.setCssProperty(this.heightVar, "auto");
handlers.onExpandCompleted().run();
expanding = false;
};
createAnimationEndListeners(stopListener);
- String expandedHeight = treeItem.getAttribute(DUI_EXPANDED_HEIGHT);
- treeItem.setCssProperty(this.heightVar, expandedHeight + "px");
- treeItem.removeAttribute(DUI_COLLAPSED);
+ String expandedHeight = node.getAttribute(DUI_EXPANDED_HEIGHT);
+ node.setCssProperty(this.heightVar, expandedHeight + "px");
+ node.removeAttribute(DUI_COLLAPSED);
}
}
private void createAnimationEndListeners(EventListener stopListener) {
AddEventListenerOptions addEventListenerOptions = AddEventListenerOptions.create();
addEventListenerOptions.setOnce(true);
- treeItem
- .element()
- .addEventListener("webkitTransitionEnd", stopListener, addEventListenerOptions);
- treeItem.element().addEventListener("MSTransitionEnd", stopListener, addEventListenerOptions);
- treeItem.element().addEventListener("mozTransitionEnd", stopListener, addEventListenerOptions);
- treeItem.element().addEventListener("oanimationend", stopListener, addEventListenerOptions);
- treeItem.element().addEventListener("animationend", stopListener, addEventListenerOptions);
+ node.element().addEventListener("webkitTransitionEnd", stopListener, addEventListenerOptions);
+ node.element().addEventListener("MSTransitionEnd", stopListener, addEventListenerOptions);
+ node.element().addEventListener("mozTransitionEnd", stopListener, addEventListenerOptions);
+ node.element().addEventListener("oanimationend", stopListener, addEventListenerOptions);
+ node.element().addEventListener("animationend", stopListener, addEventListenerOptions);
}
/** @dominokit-site-ignore {@inheritDoc} */
@@ -159,9 +156,9 @@ private void createAnimationEndListeners(EventListener stopListener) {
public void collapse(Element element) {
if (!expanding) {
collapsing = true;
- treeItem.setCssProperty(this.heightVar, getActualHeight() + "px");
- boolean disableAnimation = dui_transition_none.isAppliedTo(treeItem);
- treeItem.apply(
+ node.setCssProperty(this.heightVar, getActualHeight() + "px");
+ boolean disableAnimation = dui_transition_none.isAppliedTo(node);
+ node.apply(
self -> {
if (self.isAttached()) {
this.handlers.onBeforeCollapse().run();
@@ -176,11 +173,11 @@ public void collapse(Element element) {
self.onAttached(
(e, mutationRecord) -> {
this.handlers.onBeforeCollapse().run();
- treeItem.addCss(dui_transition_none);
+ node.addCss(dui_transition_none);
EventListener stopListener =
evt -> {
if (!disableAnimation) {
- dui_transition_none.remove(treeItem);
+ dui_transition_none.remove(node);
}
handlers.onCollapseCompleted().run();
collapsing = false;
@@ -193,13 +190,13 @@ public void collapse(Element element) {
}
}
- private void resetParentHeight(TreeItem> treeItem) {
- treeItem
+ private void resetParentHeight(TreeNode, ?, ?> treeNode) {
+ treeNode
.getParent()
.ifPresent(
parent -> {
- if (parent instanceof TreeItem) {
- TreeItem> parentItem = (TreeItem>) parent;
+ if (parent instanceof TreeNode) {
+ TreeNode, ?, ?> parentItem = (TreeNode, ?, ?>) parent;
parentItem.removeCssProperty(parentItem.getAttribute(DUI_EXPAND_COLLAPSE_VAR));
parent.getParent().ifPresent(treeItem1 -> resetParentHeight(parentItem));
}
@@ -207,15 +204,14 @@ private void resetParentHeight(TreeItem> treeItem) {
}
private void collapseElement(Element element) {
- if (dui_transition_none.isAppliedTo(treeItem)) {
- treeItem.setAttribute(DUI_COLLAPSED, "true");
- treeItem.setCssProperty(this.heightVar, treeItem.getAttribute(DUI_COLLAPSED_HEIGHT) + "px");
+ if (dui_transition_none.isAppliedTo(node)) {
+ node.setAttribute(DUI_COLLAPSED, "true");
+ node.setCssProperty(this.heightVar, node.getAttribute(DUI_COLLAPSED_HEIGHT) + "px");
} else {
DomGlobal.requestAnimationFrame(
timestamp -> {
- treeItem.setAttribute(DUI_COLLAPSED, "true");
- treeItem.setCssProperty(
- this.heightVar, treeItem.getAttribute(DUI_COLLAPSED_HEIGHT) + "px");
+ node.setAttribute(DUI_COLLAPSED, "true");
+ node.setCssProperty(this.heightVar, node.getAttribute(DUI_COLLAPSED_HEIGHT) + "px");
});
}
}
diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/config/TreeConfig.java b/domino-ui/src/main/java/org/dominokit/domino/ui/config/TreeConfig.java
index 3c90865dd..d95aa9e71 100644
--- a/domino-ui/src/main/java/org/dominokit/domino/ui/config/TreeConfig.java
+++ b/domino-ui/src/main/java/org/dominokit/domino/ui/config/TreeConfig.java
@@ -18,7 +18,7 @@
import java.util.function.Supplier;
import org.dominokit.domino.ui.collapsible.CollapseStrategy;
import org.dominokit.domino.ui.collapsible.TreeHeightCollapseStrategy;
-import org.dominokit.domino.ui.tree.TreeItem;
+import org.dominokit.domino.ui.tree.TreeNode;
/**
* Implementations of this interface can be used to configure defaults for {@link
@@ -31,10 +31,10 @@ public interface TreeConfig extends ComponentConfig {
*
*
Defaults to : {@code TreeHeightCollapseStrategy}
*
- * @param treeItem The TreeItem we are creating the strategy for.
+ * @param node The TreeItem we are creating the strategy for.
* @return a {@code Supplier}
*/
- default Supplier getTreeDefaultCollapseStrategy(TreeItem> treeItem) {
- return () -> new TreeHeightCollapseStrategy(treeItem);
+ default Supplier getTreeDefaultCollapseStrategy(TreeNode, ?, ?> node) {
+ return () -> new TreeHeightCollapseStrategy(node);
}
}
diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/HasActiveNode.java b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/HasActiveNode.java
new file mode 100644
index 000000000..d0c99ae14
--- /dev/null
+++ b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/HasActiveNode.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright © 2019 Dominokit
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.dominokit.domino.ui.tree;
+
+public interface HasActiveNode, S> {
+ void setActiveNode(N node);
+
+ void setActiveNode(N node, boolean silent);
+
+ N getActiveNode();
+}
diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/HasTreeRoot.java b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/HasTreeRoot.java
new file mode 100644
index 000000000..5a88f2399
--- /dev/null
+++ b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/HasTreeRoot.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright © 2019 Dominokit
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.dominokit.domino.ui.tree;
+
+public interface HasTreeRoot {}
diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/IsParentNode.java b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/IsParentNode.java
new file mode 100644
index 000000000..f7156f97f
--- /dev/null
+++ b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/IsParentNode.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2019 Dominokit
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.dominokit.domino.ui.tree;
+
+import java.util.Optional;
+
+public interface IsParentNode, S> extends HasActiveNode {
+ RootNode getRootNode();
+
+ IsParentNode expandNode();
+
+ IsParentNode expandNode(boolean expandParent);
+
+ TreeItemFilter getFilter();
+
+ Optional> getParent();
+
+ void removeNode(N node);
+
+ V getValue();
+
+ void setValue(V value);
+}
diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/NodeIconSupplier.java b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/NodeIconSupplier.java
new file mode 100644
index 000000000..e94c33616
--- /dev/null
+++ b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/NodeIconSupplier.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright © 2019 Dominokit
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.dominokit.domino.ui.tree;
+
+import org.dominokit.domino.ui.icons.Icon;
+
+/**
+ * An interface to provide custom icons for tree items.
+ *
+ * @param The type of data associated with each tree item.
+ */
+public interface NodeIconSupplier, S> {
+ /**
+ * Creates an icon for the given tree item.
+ *
+ * @param item The tree item for which to create the icon.
+ * @return The created icon.
+ */
+ Icon> createIcon(N item);
+}
diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/RootNode.java b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/RootNode.java
new file mode 100644
index 000000000..f35f30d04
--- /dev/null
+++ b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/RootNode.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright © 2019 Dominokit
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.dominokit.domino.ui.tree;
+
+public interface RootNode, S> extends HasActiveNode {
+ boolean isAutoCollapse();
+
+ boolean isAutoExpandFound();
+
+ NodeIconSupplier getIconSupplier();
+
+ default void onActiveNodeChanged(N node, S selection, boolean silent) {}
+
+ void onDeselectionChanged(N source, S selection);
+}
diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/Tree.java b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/Tree.java
index 46d04091f..2d79fc6d6 100644
--- a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/Tree.java
+++ b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/Tree.java
@@ -15,117 +15,17 @@
*/
package org.dominokit.domino.ui.tree;
-import static java.util.Objects.isNull;
-import static java.util.Objects.nonNull;
-import static org.dominokit.domino.ui.utils.Domino.*;
-
-import elemental2.dom.HTMLDivElement;
-import elemental2.dom.HTMLElement;
-import java.util.*;
-import org.dominokit.domino.ui.IsElement;
-import org.dominokit.domino.ui.collapsible.CollapseStrategy;
-import org.dominokit.domino.ui.elements.DivElement;
-import org.dominokit.domino.ui.elements.SpanElement;
-import org.dominokit.domino.ui.elements.UListElement;
-import org.dominokit.domino.ui.icons.Icon;
-import org.dominokit.domino.ui.icons.ToggleMdiIcon;
-import org.dominokit.domino.ui.icons.lib.Icons;
-import org.dominokit.domino.ui.search.Search;
-import org.dominokit.domino.ui.utils.*;
-
-/**
- * The Tree class provides a tree structure for displaying hierarchical data.
- *
- * Example usage:
- *
- *
- * Tree tree = Tree.create("Root");
- * TreeItem item1 = TreeItem.create("Item 1", "Value 1");
- * TreeItem item2 = TreeItem.create("Item 2", "Value 2");
- * tree.appendChild(item1);
- * tree.appendChild(item2);
- *
- *
- * @param The type of data associated with each tree item.
- * @see BaseDominoElement
- */
-public class Tree extends BaseDominoElement>
- implements TreeParent,
- IsElement,
- TreeStyles,
- HasSelectionListeners, TreeItem, TreeItem> {
-
- private ToggleTarget toggleTarget = ToggleTarget.ANY;
- private TreeItemFilter> filter =
- (treeItem, searchToken) ->
- treeItem.getTitle().toLowerCase().contains(searchToken.toLowerCase());
-
- private TreeItem activeTreeItem;
-
- private boolean autoCollapse = true;
- private final List> subItems = new ArrayList<>();
- private boolean autoExpandFound;
- private LazyChild search;
- private LazyChild> collapseExpandAllIcon;
-
- private T value;
-
- private CollapseStrategy collapseStrategy;
-
- private DivElement element;
- private DivElement bodyElement;
- private UListElement subTree;
- private LazyChild headerElement;
- private LazyChild> searchIcon;
- private TreeItemIconSupplier iconSupplier;
- private boolean selectionListenersPaused;
- private final Set, ? super TreeItem>>
- selectionListeners = new HashSet<>();
- private final Set, ? super TreeItem>>
- deselectionListeners = new HashSet<>();
-
- /** Creates a new empty tree. */
- public Tree() {
- element =
- div()
- .addCss(dui_tree)
- .appendChild(
- bodyElement =
- div().addCss(dui_tree_body).appendChild(subTree = ul().addCss(dui_tree_nav)));
- headerElement = LazyChild.of(TreeHeader.create(), element);
- init(this);
- }
-
- /**
- * Creates a new tree with the given title.
- *
- * @param treeTitle The title of the tree.
- */
- public Tree(String treeTitle) {
- this();
- headerElement.get().setTitle(treeTitle);
- }
-
- /**
- * Creates a new tree with the given title and associated data value.
- *
- * @param treeTitle The title of the tree.
- * @param value The data value associated with the tree.
- */
- public Tree(String treeTitle, T value) {
- this(treeTitle);
- this.value = value;
- }
+public class Tree extends TreeRoot, Tree, TreeItem> {
/**
* Creates a new instance of Tree with the specified title and associated data value.
*
* @param title The title of the tree.
* @param value The data value associated with the tree.
- * @param The type of data associated with each tree item.
+ * @param The type of data associated with each tree item.
* @return A new Tree instance.
*/
- public static Tree create(String title, T value) {
+ public static Tree create(String title, V value) {
return new Tree<>(title, value);
}
@@ -133,709 +33,45 @@ public static Tree create(String title, T value) {
* Creates a new instance of Tree with the specified title.
*
* @param title The title of the tree.
- * @param The type of data associated with each tree item.
+ * @param The type of data associated with each tree item.
* @return A new Tree instance.
*/
- public static Tree create(String title) {
+ public static Tree create(String title) {
return new Tree<>(title);
}
/**
* Creates a new empty instance of Tree.
*
- * @param The type of data associated with each tree item.
+ * @param The type of data associated with each tree item.
* @return A new Tree instance.
*/
- public static Tree create() {
+ public static Tree create() {
return new Tree<>();
}
- @Override
- public HTMLElement getAppendTarget() {
- return subTree.element();
- }
-
- /**
- * Appends a child tree item to this tree.
- *
- * @param treeItem The tree item to append.
- * @return This Tree instance for method chaining.
- */
- public Tree appendChild(TreeItem treeItem) {
- super.appendChild(treeItem.element());
- treeItem.setParent(this);
- treeItem.setToggleTarget(this.toggleTarget);
- if (nonNull(collapseStrategy)) {
- treeItem.setCollapseStrategy(collapseStrategy);
- }
- this.subItems.add(treeItem);
- if (nonNull(iconSupplier)) {
- treeItem.onSuppliedIconChanged(iconSupplier);
- }
- return this;
- }
-
- public Tree appendChild(TreeItem... treeItems) {
- Arrays.stream(treeItems).forEach(this::appendChild);
- return this;
- }
-
- /**
- * Sets a custom icon supplier for tree items in this tree. The icon supplier provides icons for
- * each tree item based on its content.
- *
- * @param iconSupplier The custom icon supplier to set.
- * @return This {@code Tree} instance for method chaining.
- */
- public Tree setTreeItemIconSupplier(TreeItemIconSupplier iconSupplier) {
- this.iconSupplier = iconSupplier;
- if (nonNull(this.iconSupplier)) {
- subItems.forEach(
- item -> {
- item.onSuppliedIconChanged(iconSupplier);
- });
- }
- return this;
- }
-
- /**
- * Gets the custom icon supplier set for this tree. The icon supplier provides icons for each tree
- * item based on its content.
- *
- * @return The custom icon supplier for tree items, or {@code null} if not set.
- */
- TreeItemIconSupplier getIconSupplier() {
- return iconSupplier;
- }
-
- /**
- * Appends a separator to this tree.
- *
- * @return This Tree instance for method chaining.
- */
- public Tree addSeparator() {
- appendChild(Separator.create());
- return this;
- }
-
- /**
- * Sets the toggle target for this tree.
- *
- * @param toggleTarget The toggle target to set.
- * @return This `Tree` instance for method chaining.
- */
- public Tree setToggleTarget(ToggleTarget toggleTarget) {
- if (nonNull(toggleTarget)) {
- subItems.forEach(item -> item.setToggleTarget(toggleTarget));
- this.toggleTarget = toggleTarget;
- }
- return this;
- }
-
- /**
- * Gets the currently active tree item in this tree.
- *
- * @return The currently active tree item, or {@code null} if none is active.
- */
- @Override
- public TreeItem getActiveItem() {
- return activeTreeItem;
- }
-
- /**
- * Sets the currently active tree item in this tree. The tree item will be activated, and any
- * previously active item will be deactivated.
- *
- * @param activeItem The tree item to set as active.
- */
- @Override
- public void setActiveItem(TreeItem activeItem) {
- setActiveItem(activeItem, false);
- }
-
- /**
- * Sets the currently active tree item in this tree with an option to suppress selection events.
- * The tree item will be activated, and any previously active item will be deactivated.
- *
- * @param activeItem The tree item to set as active.
- * @param silent {@code true} to suppress selection events, {@code false} otherwise.
- */
- @Override
- public void setActiveItem(TreeItem activeItem, boolean silent) {
- TreeItem source = null;
- if (nonNull(this.activeTreeItem) && !this.activeTreeItem.equals(activeItem)) {
- source = this.activeTreeItem;
- this.activeTreeItem.deactivate();
- }
-
- this.activeTreeItem = activeItem;
- this.activeTreeItem.activate();
- if (!silent) {
- triggerSelectionListeners(activeItem, activeItem);
- activeItem.triggerSelectionListeners(activeItem, activeItem);
- Optional.ofNullable(source)
- .ifPresent(
- item -> {
- triggerDeselectionListeners(item, activeItem);
- item.triggerDeselectionListeners(item, activeItem);
- });
- }
- }
-
- /**
- * Gets the header of this tree.
- *
- * @return The `TreeHeader` of this tree.
- */
- public TreeHeader getHeader() {
- return headerElement.get();
- }
-
- /**
- * Gets the sub-tree element of this tree.
- *
- * @return The sub-tree element.
- */
- public UListElement getSubTree() {
- return subTree;
- }
-
- /**
- * Gets the title element of this tree's header.
- *
- * @return The title element.
- */
- public SpanElement getTitle() {
- return headerElement.get().getTitle();
- }
-
- /**
- * Sets whether this tree is searchable.
- *
- * @param searchable `true` to enable search functionality, `false` otherwise.
- * @return This `Tree` instance for method chaining.
- */
- public Tree setSearchable(boolean searchable) {
- if (searchable) {
-
- if (isNull(search)) {
- search =
- LazyChild.of(
- Search.create(true).onSearch(Tree.this::filter).onClose(this::clearFilter),
- headerElement);
-
- search.whenInitialized(
- () -> {
- search
- .element()
- .getInputElement()
- .onKeyDown(
- keyEvents -> {
- keyEvents.onArrowDown(
- evt -> {
- subItems.stream()
- .filter(item -> !dui_hidden.isAppliedTo(item))
- .findFirst()
- .ifPresent(item -> item.getClickableElement().focus());
- });
- });
- });
- }
-
- if (isNull(searchIcon)) {
- searchIcon =
- LazyChild.of(
- PostfixAddOn.of(
- Icons.magnify()
- .clickable()
- .addClickListener(
- evt -> {
- evt.stopPropagation();
- search.get().open();
- }))
- .addCss(dui_tree_header_item),
- headerElement.get().getContent());
- }
- searchIcon.get();
- } else {
- if (nonNull(searchIcon)) {
- searchIcon.remove();
- }
-
- if (nonNull(search)) {
- search.remove();
- }
- }
- return this;
- }
-
- /**
- * Sets whether this tree is foldable.
- *
- * @param foldingEnabled `true` to enable folding functionality, `false` otherwise.
- * @return This `Tree` instance for method chaining.
- */
- public Tree setFoldable(boolean foldingEnabled) {
- if (foldingEnabled) {
- if (isNull(collapseExpandAllIcon)) {
- collapseExpandAllIcon =
- LazyChild.of(
- PostfixAddOn.of(
- ToggleMdiIcon.create(Icons.fullscreen(), Icons.fullscreen_exit())
- .clickable()
- .apply(
- self ->
- self.addClickListener(
- evt -> {
- evt.stopPropagation();
- if (self.isToggled()) {
- collapseAll();
- } else {
- expandAll();
- }
- self.toggle();
- })))
- .addCss(dui_tree_header_item),
- headerElement.get().getContent());
- }
- collapseExpandAllIcon.get();
- } else {
- if (nonNull(collapseExpandAllIcon)) {
- collapseExpandAllIcon.remove();
- }
- }
- return this;
- }
-
- /** Expands all tree items in this tree. */
- public void expandAll() {
- getSubItems().forEach(TreeItem::expandAll);
- }
-
- /** Collapses all tree items in this tree. */
- public void collapseAll() {
- getSubItems().forEach(TreeItem::collapseAll);
- }
-
- /** Deactivates all tree items in this tree. */
- public void deactivateAll() {
- getSubItems().forEach(TreeItem::deactivate);
- }
-
- /**
- * Enables automatic expansion of found tree items when using the search feature.
- *
- * @return This tree instance to allow method chaining.
- */
- public Tree autoExpandFound() {
- this.autoExpandFound = true;
- return this;
- }
-
- /**
- * Checks if automatic expansion of found tree items is enabled when using the search feature.
- *
- * @return {@code true} if automatic expansion is enabled, {@code false} otherwise.
- */
- @Override
- public boolean isAutoExpandFound() {
- return autoExpandFound;
- }
-
- /**
- * Sets whether to auto-expand found items in the tree.
- *
- * @param autoExpandFound `true` to auto-expand found items, `false` otherwise.
- * @return This `Tree` instance for method chaining.
- */
- public Tree setAutoExpandFound(boolean autoExpandFound) {
- this.autoExpandFound = autoExpandFound;
- return this;
- }
-
- /** Clears the search filter applied to tree items in this tree. */
- public void clearFilter() {
- subItems.forEach(TreeItem::clearFilter);
- }
-
- /**
- * Filters tree items in this tree based on the given search token.
- *
- * @param searchToken The search token to filter tree items.
- */
- public void filter(String searchToken) {
- subItems.forEach(treeItem -> treeItem.filter(searchToken));
- }
-
- /**
- * Returns a reference to the root tree within which this tree is contained. Since a tree is
- * self-contained and typically not nested within other trees, this method returns a reference to
- * the current tree instance.
- *
- * @return A reference to the current tree instance.
- */
- @Override
- public Tree getTreeRoot() {
- return this;
- }
-
- /**
- * Sets whether this tree should automatically collapse when a new item is selected.
- *
- * @param autoCollapse `true` to automatically collapse the tree, `false` otherwise.
- * @return This `Tree` instance for method chaining.
- */
- public Tree setAutoCollapse(boolean autoCollapse) {
- this.autoCollapse = autoCollapse;
- return this;
- }
-
- /**
- * Sets the title of this tree.
- *
- * @param title The title to set.
- * @return This `Tree` instance for method chaining.
- */
- public Tree setTitle(String title) {
- headerElement.get().setTitle(title);
- return this;
- }
-
- /**
- * Sets the icon for this tree.
- *
- * @param icon The icon to set.
- * @return This `Tree` instance for method chaining.
- */
- public Tree setIcon(Icon> icon) {
- headerElement.get().setIcon(icon);
- return this;
- }
-
- /**
- * Checks whether this tree is set to auto-collapse when a new item is selected.
- *
- * @return `true` if auto-collapse is enabled, `false` otherwise.
- */
- public boolean isAutoCollapse() {
- return autoCollapse;
- }
-
- /**
- * Returns a list of sub-items contained within this tree. The sub-items are represented as
- * instances of {@link TreeItem}.
- *
- * @return A list of sub-items contained within this tree.
- */
- @Override
- public List> getSubItems() {
- return new ArrayList<>(subItems);
- }
-
- /**
- * Expands the node within the tree, indicating that it should be expanded or collapsed. This
- * method has no effect on the root tree.
- *
- * @param expandParent {@code true} to expand the parent node, {@code false} to collapse it.
- * @return A reference to this tree.
- */
- @Override
- public TreeParent expandNode(boolean expandParent) {
- return this;
- }
-
- /**
- * Expands the node within the tree. This method has no effect on the root tree.
- *
- * @return A reference to this tree.
- */
- @Override
- public TreeParent expandNode() {
- return this;
- }
-
- /**
- * Returns an empty optional, as this tree does not have a parent tree.
- *
- * @return An empty optional.
- */
- @Override
- public Optional> getParent() {
- return Optional.empty();
- }
-
- /** Activates this tree, making it the active tree item. */
- @Override
- public void activate() {}
-
- /**
- * Activates this tree, making it the active tree item. This method has no effect on the parent
- * tree.
- *
- * @param activateParent {@code true} to activate the parent tree, {@code false} to deactivate it.
- */
- @Override
- public void activate(boolean activateParent) {}
-
- /**
- * Gets the search input field associated with this tree if it is searchable.
- *
- * @return An `Optional` containing the search input field, or empty if not searchable.
- */
- public Optional getSearch() {
- if (nonNull(search) && search.isInitialized()) {
- return Optional.ofNullable(search.get());
- }
- return Optional.empty();
- }
-
- /**
- * Gets the search icon associated with this tree if it is searchable.
- *
- * @return An `Optional` containing the search icon, or empty if not searchable.
- */
- public Optional> getSearchIcon() {
- if (nonNull(searchIcon) && search.isInitialized()) {
- return Optional.of(searchIcon.get());
- }
- return Optional.empty();
- }
-
- /**
- * Gets the collapse/expand all icon associated with this tree if it is foldable.
- *
- * @return An `Optional` containing the collapse/expand all icon, or empty if not foldable.
- */
- public Optional> getCollapseExpandAllIcon() {
- if (nonNull(collapseExpandAllIcon) && collapseExpandAllIcon.isInitialized()) {
- return Optional.of(collapseExpandAllIcon.get());
- }
- return Optional.empty();
- }
-
- /**
- * Gets the value associated with this tree.
- *
- * @return The value associated with this tree.
- */
- public T getValue() {
- return value;
- }
-
- /**
- * Sets the value associated with this tree.
- *
- * @param value The value to set.
- */
- public void setValue(T value) {
- this.value = value;
- }
-
- /**
- * Gets a list of active tree items in the path from the root to the currently active item.
- *
- * @return A list of active tree items.
- */
- public List> getActivePath() {
- List> activeItems = new ArrayList<>();
- TreeItem activeItem = getActiveItem();
- while (nonNull(activeItem)) {
- activeItems.add(activeItem);
- activeItem = activeItem.getActiveItem();
- }
-
- return activeItems;
- }
-
- /**
- * Gets a list of values associated with active tree items in the path from the root to the
- * currently active item.
- *
- * @return A list of values associated with active tree items.
- */
- public List getActivePathValues() {
- List activeValues = new ArrayList<>();
- TreeItem activeItem = getActiveItem();
- while (nonNull(activeItem)) {
- activeValues.add(activeItem.getValue());
- activeItem = activeItem.getActiveItem();
- }
-
- return activeValues;
- }
-
- /**
- * Removes the specified tree item from this tree. This method removes the tree item from the list
- * of sub-items and calls the {@link TreeItem#remove()} method on the item to detach it from the
- * DOM.
- *
- * @param item The tree item to be removed.
- */
- @Override
- public void removeItem(TreeItem item) {
- subItems.remove(item);
- item.remove();
- }
-
- /**
- * Clears all child items from this tree.
- *
- * @return This `Tree` instance for method chaining.
- */
- public Tree clear() {
- subItems.forEach(TreeItem::remove);
- return this;
- }
-
- /**
- * Sets a filter for tree items in this tree.
- *
- * @param filter The filter to set.
- * @return This `Tree` instance for method chaining.
- */
- public Tree setFilter(TreeItemFilter> filter) {
- this.filter = filter;
- return this;
- }
-
- /**
- * Gets the current filter used for searching within the tree.
- *
- * @return The current filter applied to the tree items for searching.
- */
- @Override
- public TreeItemFilter> getFilter() {
- return this.filter;
- }
-
- /**
- * Sets the collapse strategy for all tree items in this tree.
- *
- * @param collapseStrategy The collapse strategy to set.
- * @return This `Tree` instance for method chaining.
- */
- public Tree setCollapseStrategy(CollapseStrategy collapseStrategy) {
- getSubItems().forEach(tTreeItem -> setCollapseStrategy(collapseStrategy));
- this.collapseStrategy = collapseStrategy;
- return this;
- }
-
- /**
- * Gets the collapse strategy set for this tree.
- *
- * @return The collapse strategy.
- */
- public CollapseStrategy getCollapseStrategy() {
- return collapseStrategy;
- }
-
- /**
- * Configures the header of this tree using a `ChildHandler`.
- *
- * @param handler The `ChildHandler` to configure the header.
- * @return This `Tree` instance for method chaining.
- */
- public Tree withHeader(ChildHandler, TreeHeader> handler) {
- handler.apply(this, headerElement.get());
- return this;
- }
- /**
- * Pauses the selection listeners of the tree, preventing them from reacting to selection events.
- *
- * @return The current tree instance with selection listeners paused or resumed based on the
- * toggle value.
- */
- @Override
- public Tree pauseSelectionListeners() {
- this.selectionListenersPaused = true;
- return this;
- }
-
- /**
- * Resumes the paused selection listeners of the tree, allowing them to react to selection events.
- *
- * @return The current tree instance with selection listeners resumed.
- */
- @Override
- public Tree resumeSelectionListeners() {
- this.selectionListenersPaused = false;
- return this;
- }
-
- /**
- * Toggles the pause state of selection listeners of the tree.
- *
- * @param toggle {@code true} to pause the listeners, {@code false} to resume them.
- * @return The current tree instance with selection listeners paused or resumed based on the
- * toggle value.
- */
- @Override
- public Tree togglePauseSelectionListeners(boolean toggle) {
- this.selectionListenersPaused = toggle;
- return this;
- }
-
- /**
- * Gets the set of selection listeners registered with the tree.
- *
- * @return A set containing selection listeners.
- */
- @Override
- public Set, ? super TreeItem>> getSelectionListeners() {
- return this.selectionListeners;
- }
-
- /**
- * Gets the set of deselection listeners registered with the tree.
- *
- * @return A set containing deselection listeners.
- */
- @Override
- public Set, ? super TreeItem>>
- getDeselectionListeners() {
- return this.deselectionListeners;
- }
-
- /**
- * Checks if the selection listeners of the tree are currently paused.
- *
- * @return {@code true} if selection listeners are paused, {@code false} otherwise.
- */
- @Override
- public boolean isSelectionListenersPaused() {
- return this.selectionListenersPaused;
+ /** Creates a new empty tree. */
+ public Tree() {
+ super();
}
/**
- * Triggers selection listeners with the provided source and selection tree items.
+ * Creates a new tree with the given title.
*
- * @param source The source tree item that triggered the selection.
- * @param selection The selected tree item.
- * @return The current tree instance with selection listeners triggered.
+ * @param treeTitle The title of the tree.
*/
- @Override
- public Tree triggerSelectionListeners(TreeItem source, TreeItem selection) {
- if (!this.selectionListenersPaused) {
- this.selectionListeners.forEach(
- listener -> listener.onSelectionChanged(Optional.ofNullable(source), selection));
- }
- return this;
+ public Tree(String treeTitle) {
+ super(treeTitle);
}
/**
- * Triggers deselection listeners with the provided source and deselected tree items.
+ * Creates a new tree with the given title and associated data value.
*
- * @param source The source tree item that triggered the deselection.
- * @param selection The deselected tree item.
- * @return The current tree instance with deselection listeners triggered.
+ * @param treeTitle The title of the tree.
+ * @param value The data value associated with the tree.
*/
- @Override
- public Tree triggerDeselectionListeners(TreeItem source, TreeItem selection) {
- if (!this.selectionListenersPaused) {
- this.deselectionListeners.forEach(
- listener -> listener.onSelectionChanged(Optional.ofNullable(source), selection));
- }
- return this;
+ public Tree(String treeTitle, V value) {
+ super(treeTitle, value);
}
/**
@@ -844,46 +80,16 @@ public Tree triggerDeselectionListeners(TreeItem source, TreeItem selec
* @return The currently selected tree item, or {@code null} if none is selected.
*/
@Override
- public TreeItem getSelection() {
- return this.activeTreeItem;
+ public TreeItem getSelection() {
+ return this.getActiveNode();
}
- /**
- * Gets the HTMLDivElement representing the tree element.
- *
- * @return The HTMLDivElement representing the tree.
- */
@Override
- public HTMLDivElement element() {
- return element.element();
- }
-
- /**
- * An interface to handle item click events in the tree.
- *
- * @param The type of data associated with each tree item.
- */
- public interface ItemClickListener {
- /**
- * Called when a tree item is clicked.
- *
- * @param treeItem The tree item that was clicked.
- */
- void onTreeItemClicked(TreeItem treeItem);
- }
-
- /**
- * An interface to provide custom icons for tree items.
- *
- * @param The type of data associated with each tree item.
- */
- public interface TreeItemIconSupplier {
- /**
- * Creates an icon for the given tree item.
- *
- * @param item The tree item for which to create the icon.
- * @return The created icon.
- */
- Icon> createIcon(TreeItem item);
+ public void onActiveNodeChanged(TreeItem source, TreeItem selection, boolean silent) {
+ withPauseSelectionListenersToggle(
+ silent,
+ field -> {
+ triggerSelectionListeners(source, selection);
+ });
}
}
diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeItem.java b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeItem.java
index c35919c1a..9c9277e1c 100644
--- a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeItem.java
+++ b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeItem.java
@@ -15,185 +15,12 @@
*/
package org.dominokit.domino.ui.tree;
-import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
-import static org.dominokit.domino.ui.utils.Domino.*;
-import elemental2.dom.Element;
-import elemental2.dom.EventListener;
-import elemental2.dom.HTMLAnchorElement;
-import elemental2.dom.HTMLLIElement;
-import java.util.*;
-import org.dominokit.domino.ui.collapsible.Collapsible;
-import org.dominokit.domino.ui.config.HasComponentConfig;
-import org.dominokit.domino.ui.config.TreeConfig;
-import org.dominokit.domino.ui.elements.*;
+import java.util.Optional;
import org.dominokit.domino.ui.icons.Icon;
-import org.dominokit.domino.ui.icons.StateChangeIcon;
-import org.dominokit.domino.ui.icons.ToggleIcon;
-import org.dominokit.domino.ui.style.WaveStyle;
-import org.dominokit.domino.ui.utils.*;
-/**
- * Represents a tree item in a tree structure.
- *
- * TreeItem is a component that can be used to build hierarchical tree structures. Each tree item
- * can have child tree items, and they can be expanded or collapsed. TreeItem supports icons,
- * selection, and filtering.
- *
- *
Usage example:
- *
- *
- * // Create a tree item with an icon and title
- * TreeItem treeItem = new TreeItem<>(Icon.create("folder"), "Folder 1");
- *
- * // Add child tree items
- * TreeItem childItem1 = TreeItem.create("Child 1");
- * TreeItem childItem2 = TreeItem.create("Child 2");
- * treeItem.appendChild(childItem1);
- * treeItem.appendChild(childItem2);
- *
- *
- * @see BaseDominoElement
- */
-public class TreeItem extends BaseDominoElement>
- implements TreeParent,
- CanActivate,
- CanDeactivate,
- HasClickableElement,
- TreeStyles,
- HasComponentConfig,
- HasSelectionListeners, TreeItem, TreeItem> {
-
- private String title;
- private LIElement element;
- private final AnchorElement anchorElement;
- private final DivElement contentElement;
- private LazyChild> itemIcon;
- private final LazyChild textElement;
- private final List> subItems = new LinkedList<>();
- private TreeItem activeTreeItem;
- private TreeParent parent;
- private final UListElement subTree;
-
- private T value;
-
- private ToggleTarget toggleTarget = ToggleTarget.ANY;
-
- private OriginalState originalState;
- private boolean selectionListenersPaused;
- private Set, ? super TreeItem>> selectionListeners =
- new HashSet<>();
- private Set, ? super TreeItem>> deselectionListeners =
- new HashSet<>();
-
- private final EventListener anchorListener =
- evt -> {
- if (ToggleTarget.ANY.equals(this.toggleTarget)) {
- evt.stopPropagation();
- if (isParent()) {
- toggle();
- }
- activateItem();
- }
- };
-
- private final EventListener iconListener =
- evt -> {
- if (ToggleTarget.ICON.equals(this.toggleTarget)) {
- evt.stopPropagation();
- if (isParent()) {
- toggle();
- }
- activateItem();
- }
- };
-
- /** Constructs a new TreeItem instance. */
- private TreeItem() {
- this.element =
- li().addCss(dui_tree_item)
- .appendChild(
- anchorElement =
- a().removeHref()
- .addCss(dui_tree_anchor)
- .appendChild(contentElement = div().addCss(dui_tree_item_content)))
- .appendChild(subTree = ul().addCss(dui_tree_nav).hide());
- this.textElement = LazyChild.of(span().addCss(dui_tree_item_text), contentElement);
- init(this);
-
- setCollapseStrategy(getConfig().getTreeDefaultCollapseStrategy(this).get());
- setAttribute(Collapsible.DUI_COLLAPSED, "true");
- }
-
- /**
- * Constructs a new TreeItem instance with an icon and title.
- *
- * @param icon The icon to be displayed for the tree item.
- * @param title The title of the tree item.
- */
- public TreeItem(Icon> icon, String title) {
- this();
- setIcon(icon);
- setTitle(title);
- init();
- }
-
- /**
- * Constructs a new TreeItem instance with a title.
- *
- * @param title The title of the tree item.
- */
- public TreeItem(String title) {
- this();
- setTitle(title);
- init();
- }
-
- /**
- * Constructs a new TreeItem instance with an icon.
- *
- * @param icon The icon to be displayed for the tree item.
- */
- public TreeItem(Icon> icon) {
- this();
- setIcon(icon);
- init();
- }
-
- /**
- * Constructs a new TreeItem instance with a title and a value.
- *
- * @param title The title of the tree item.
- * @param value The value associated with the tree item.
- */
- public TreeItem(String title, T value) {
- this(title);
- this.value = value;
- }
-
- /**
- * Constructs a new TreeItem instance with an icon, title, and a value.
- *
- * @param icon The icon to be displayed for the tree item.
- * @param title The title of the tree item.
- * @param value The value associated with the tree item.
- */
- public TreeItem(Icon> icon, String title, T value) {
- this(icon, title);
- this.value = value;
- }
-
- /**
- * Constructs a new TreeItem instance with an icon and a value.
- *
- * @param icon The icon to be displayed for the tree item.
- * @param value The value associated with the tree item.
- */
- public TreeItem(Icon> icon, T value) {
- this(icon);
- this.value = value;
- }
+public class TreeItem extends TreeNode, TreeItem> {
/**
* Creates a new TreeItem instance with the given title.
@@ -203,7 +30,7 @@ public TreeItem(Icon> icon, T value) {
*/
public static TreeItem create(String title) {
TreeItem treeItem = new TreeItem<>(title);
- treeItem.value = title;
+ treeItem.setValue(title);
return treeItem;
}
@@ -216,7 +43,7 @@ public static TreeItem create(String title) {
*/
public static TreeItem create(Icon> icon, String title) {
TreeItem treeItem = new TreeItem<>(icon, title);
- treeItem.value = title;
+ treeItem.setValue(title);
return treeItem;
}
@@ -228,7 +55,7 @@ public static TreeItem create(Icon> icon, String title) {
*/
public static TreeItem create(Icon> icon) {
TreeItem treeItem = new TreeItem<>(icon);
- treeItem.value = "";
+ treeItem.setValue("");
return treeItem;
}
@@ -269,242 +96,92 @@ public static TreeItem create(Icon> icon, T value) {
return new TreeItem<>(icon, value);
}
- private void init() {
- addBeforeCollapseListener(() -> updateIcon(true));
- addBeforeExpandListener(() -> updateIcon(false));
- anchorElement.addClickListener(anchorListener);
- applyWaves();
- }
-
- private void applyWaves() {
- withWaves((item, waves) -> waves.setWaveStyle(WaveStyle.BLOCK));
- }
-
- private void activateItem() {
- if (nonNull(TreeItem.this.getActiveItem())) {
- TreeItem source = this.activeTreeItem;
- this.activeTreeItem.deactivate();
- this.activeTreeItem = null;
- triggerDeselectionListeners(source, this);
- }
- if (getParent().isPresent()) {
- getParent().get().setActiveItem(this);
- triggerSelectionListeners(this, this);
- } else {
- getTreeRoot().setActiveItem(this);
- }
- }
-
/**
- * Appends a child tree item to this tree item.
- *
- * @param treeItem The child tree item to append.
- * @return This tree item with the child tree item appended.
- */
- public TreeItem appendChild(TreeItem treeItem) {
- this.subItems.add(treeItem);
- subTree.appendChild(treeItem);
- treeItem.parent = this;
- treeItem.setToggleTarget(this.toggleTarget);
- updateIcon(isCollapsed());
- getParent()
- .ifPresent(
- p -> {
- if (nonNull(p.getTreeRoot())) {
- Tree.TreeItemIconSupplier iconSupplier = p.getTreeRoot().getIconSupplier();
- if (nonNull(iconSupplier)) {
- subItems.forEach(item -> item.onSuppliedIconChanged(iconSupplier));
- }
- }
- });
- return this;
- }
-
- public TreeItem appendChild(TreeItem... treeItems) {
- Arrays.stream(treeItems).forEach(this::appendChild);
- return this;
- }
-
- @Override
- public PrefixElement getPrefixElement() {
- return PrefixElement.of(contentElement);
- }
-
- @Override
- public PostfixElement getPostfixElement() {
- return PostfixElement.of(contentElement);
- }
-
- private void updateIcon(boolean collapsed) {
- if (nonNull(itemIcon) && itemIcon.isInitialized()) {
- if (itemIcon.element() instanceof ToggleIcon) {
- ((ToggleIcon, ?>) itemIcon.element()).toggle();
- } else if (itemIcon.element() instanceof StateChangeIcon) {
- StateChangeIcon, ?> icon = (StateChangeIcon, ?>) itemIcon.element();
- if (isParent()) {
- icon.setState(collapsed ? TreeItemIcon.STATE_COLLAPSED : TreeItemIcon.STATE_EXPANDED);
- } else {
- if (dui_active.isAppliedTo(this)) {
- icon.setState(TreeItemIcon.STATE_ACTIVE);
- } else {
- icon.setState(TreeItemIcon.STATE_LEAF);
- }
- }
- }
- }
- }
-
- /**
- * Adds a separator to this tree item.
- *
- * @return This tree item with a separator added.
- */
- public TreeItem addSeparator() {
- subTree.appendChild(li().addCss(dui_separator));
- return this;
- }
-
- /**
- * Sets the toggle target for this tree item.
+ * Constructs a new TreeItem instance with an icon and title.
*
- * @param toggleTarget The toggle target to set.
- * @return This tree item with the toggle target set.
+ * @param icon The icon to be displayed for the tree item.
+ * @param title The title of the tree item.
*/
- public TreeItem setToggleTarget(ToggleTarget toggleTarget) {
- if (nonNull(toggleTarget)) {
-
- this.toggleTarget = toggleTarget;
- if (ToggleTarget.ICON.equals(toggleTarget)) {
- removeWaves();
- if (nonNull(itemIcon) && itemIcon.isInitialized()) {
- itemIcon.get().setClickable(true).addEventListener("click", iconListener, true);
- }
- } else {
- applyWaves();
- if (nonNull(itemIcon) && itemIcon.isInitialized()) {
- itemIcon.get().setClickable(false).removeEventListener("click", iconListener);
- ;
- }
- }
-
- subItems.forEach(item -> item.setToggleTarget(toggleTarget));
- }
- return this;
- }
-
- private void toggle() {
- if (isParent()) {
- toggleCollapse();
- }
+ public TreeItem(Icon> icon, String title) {
+ super(icon, title);
}
/**
- * Expands the tree node represented by this tree item.
+ * Constructs a new TreeItem instance with a title.
*
- * @return This tree item with the node expanded.
+ * @param title The title of the tree item.
*/
- @Override
- public TreeItem expandNode() {
- return show(false);
+ public TreeItem(String title) {
+ super(title);
}
/**
- * Shows the tree node represented by this tree item.
- *
- * @param expandParent {@code true} to expand the parent nodes, {@code false} otherwise.
- * @return This tree item with the node shown.
- */
- public TreeItem show(boolean expandParent) {
- if (isParent()) {
- super.expand();
- }
- if (expandParent) {
- getParent().ifPresent(itemParent -> itemParent.expandNode(true));
- }
- return this;
- }
- /**
- * Expands the tree node represented by this tree item. If the 'expandParent' parameter is true,
- * parent nodes will also be expanded. This method returns the current TreeItem instance.
+ * Constructs a new TreeItem instance with an icon.
*
- * @param expandParent True to expand parent nodes, false to expand only the current node.
- * @return The current TreeItem instance.
+ * @param icon The icon to be displayed for the tree item.
*/
- @Override
- public TreeItem expandNode(boolean expandParent) {
- return show(expandParent);
+ public TreeItem(Icon> icon) {
+ super(icon);
}
/**
- * Toggles the collapse/expand state of the tree item. If the tree item is a parent (i.e., it has
- * sub-items), it will toggle its collapse/expand state. This method returns the current TreeItem
- * instance.
+ * Constructs a new TreeItem instance with a title and a value.
*
- * @return The current TreeItem instance.
+ * @param title The title of the tree item.
+ * @param value The value associated with the tree item.
*/
- @Override
- public TreeItem toggleCollapse() {
- if (isParent()) {
- super.toggleCollapse();
- }
- return this;
+ public TreeItem(String title, V value) {
+ super(title, value);
}
/**
- * Returns the HTML list item element associated with this tree item.
+ * Constructs a new TreeItem instance with an icon, title, and a value.
*
- * @return The HTML list item element associated with this tree item.
+ * @param icon The icon to be displayed for the tree item.
+ * @param title The title of the tree item.
+ * @param value The value associated with the tree item.
*/
- @Override
- public HTMLLIElement element() {
- return element.element();
+ public TreeItem(Icon> icon, String title, V value) {
+ super(icon, title, value);
}
/**
- * Returns the currently active (selected) tree item within the tree structure.
+ * Constructs a new TreeItem instance with an icon and a value.
*
- * @return The currently active TreeItem instance.
+ * @param icon The icon to be displayed for the tree item.
+ * @param value The value associated with the tree item.
*/
- @Override
- public TreeItem getActiveItem() {
- return activeTreeItem;
+ public TreeItem(Icon> icon, V value) {
+ super(icon, value);
}
/**
- * Returns the root of the tree structure to which this tree item belongs.
+ * Retrieves the selected tree item. In the context of a single tree item, this method returns the
+ * current tree item itself.
*
- * @return The root Tree instance.
+ * @return The selected tree item.
*/
@Override
- public Tree getTreeRoot() {
- return parent.getTreeRoot();
+ public TreeItem getSelection() {
+ return this;
}
- /**
- * Returns an optional parent of this tree item. If the parent exists and is a TreeItem, it
- * returns an Optional containing the parent; otherwise, it returns an empty Optional.
- *
- * @return An Optional containing the parent TreeItem or an empty Optional.
- */
@Override
- public Optional> getParent() {
- if (parent instanceof TreeItem) {
- return Optional.of(parent);
+ protected void activateNode() {
+ if (nonNull(this.getActiveNode())) {
+ TreeItem source = this.activeNode;
+ this.activeNode.deactivate();
+ this.activeNode = null;
+ triggerDeselectionListeners(source, getSelection());
+ }
+ if (getParent().isPresent()) {
+ getParent().get().setActiveNode(this);
+ triggerSelectionListeners(this, getSelection());
} else {
- return Optional.empty();
+ getRootNode().setActiveNode(this);
}
}
- /**
- * Sets the currently active (selected) tree item within the tree structure.
- *
- * @param activeItem The TreeItem to set as the active item.
- */
- @Override
- public void setActiveItem(TreeItem activeItem) {
- setActiveItem(activeItem, isSelectionListenersPaused());
- }
-
/**
* Sets the currently active (selected) tree item within the tree structure. The 'silent'
* parameter allows you to control whether triggering selection and deselection listeners should
@@ -514,547 +191,26 @@ public void setActiveItem(TreeItem activeItem) {
* @param silent True to suppress listener notifications; false to trigger listeners.
*/
@Override
- public void setActiveItem(TreeItem activeItem, boolean silent) {
- TreeItem source = null;
+ public void setActiveNode(TreeItem activeItem, boolean silent) {
+ TreeItem source = null;
if (nonNull(activeItem)) {
- if (nonNull(this.activeTreeItem) && !this.activeTreeItem.equals(activeItem)) {
- source = this.activeTreeItem;
- this.activeTreeItem.deactivate();
+ if (nonNull(this.activeNode) && !this.activeNode.equals(activeItem)) {
+ source = this.activeNode;
+ this.activeNode.deactivate();
}
- this.activeTreeItem = activeItem;
- this.activeTreeItem.activate();
- getParent().ifPresent(itemParent -> itemParent.setActiveItem(this, true));
+ this.activeNode = activeItem;
+ this.activeNode.activate();
+ getParent().ifPresent(itemParent -> itemParent.setActiveNode(this, true));
if (!silent) {
- triggerSelectionListeners(activeItem, activeItem);
- getTreeRoot().triggerSelectionListeners(activeItem, activeItem);
+ triggerSelectionListeners(activeItem, getSelection());
+ getRootNode().onActiveNodeChanged(activeItem, getSelection(), silent);
Optional.ofNullable(source)
.ifPresent(
item -> {
- triggerDeselectionListeners(item, activeItem);
- getTreeRoot().triggerDeselectionListeners(item, activeItem);
+ triggerDeselectionListeners(item, getSelection());
+ getRootNode().onDeselectionChanged(item, getSelection());
});
}
}
}
-
- /**
- * Returns the path to this tree item.
- *
- * @return A list of tree items representing the path to this item, starting from the root.
- */
- public List> getPath() {
- List> items = new ArrayList<>();
- items.add(this);
- Optional> parent = getParent();
-
- while (parent.isPresent()) {
- items.add((TreeItem) parent.get());
- parent = parent.get().getParent();
- }
-
- Collections.reverse(items);
-
- return items;
- }
-
- /**
- * Returns the values associated with the path to this tree item.
- *
- * @return A list of values representing the path to this item, starting from the root.
- */
- public List getPathValues() {
- List values = new ArrayList<>();
- values.add(this.getValue());
- Optional> parent = getParent();
-
- while (parent.isPresent()) {
- values.add(((TreeItem) parent.get()).getValue());
- parent = parent.get().getParent();
- }
-
- Collections.reverse(values);
-
- return values;
- }
-
- /**
- * Activates (selects) this tree item without activating its parent items. This method returns
- * void.
- */
- @Override
- public void activate() {
- activate(false);
- }
-
- /**
- * Activates (selects) this tree item. If 'activateParent' is true, it also activates its parent
- * items in the tree structure. This method returns void.
- *
- * @param activateParent True to activate parent items, false to activate only this item.
- */
- @Override
- public void activate(boolean activateParent) {
- addCss(dui_active);
- if (activateParent) {
- getParent().ifPresent(itemParent -> itemParent.setActiveItem(this));
- }
- updateIcon(isCollapsed());
- }
-
- /**
- * Deactivates (deselects) this tree item and any of its sub-items if it is a parent. If
- * auto-collapse is enabled in the tree root, it will collapse the tree item as well. This method
- * returns void.
- */
- @Override
- public void deactivate() {
- dui_active.remove(this);
- if (isParent()) {
- subItems.forEach(TreeItem::deactivate);
- if (getTreeRoot().isAutoCollapse()) {
- collapse();
- }
- }
- updateIcon(isCollapsed());
- }
-
- /**
- * Returns the clickable HTML anchor element associated with this tree item.
- *
- * @return The clickable HTML anchor element.
- */
- @Override
- public HTMLAnchorElement getClickableElement() {
- return anchorElement.element();
- }
-
- /**
- * Sets the icon for this tree item. If the 'icon' parameter is not null, it replaces the existing
- * icon with the new one. This method returns the current TreeItem instance.
- *
- * @param icon The new icon to set.
- * @return The current TreeItem instance.
- */
- public TreeItem setIcon(Icon> icon) {
- if (nonNull(itemIcon)) {
- if (itemIcon.isInitialized()) {
- itemIcon.remove();
- }
- }
- itemIcon = LazyChild.of(icon.addCss(dui_tree_item_icon), contentElement);
- itemIcon.whenInitialized(
- () -> {
- itemIcon.element().forEachChild(i -> i.addCss(dui_tree_item_icon));
- });
- itemIcon.whenInitialized(
- () -> {
- if (ToggleTarget.ICON.equals(this.toggleTarget)) {
- itemIcon.element().clickable();
- }
- itemIcon
- .element()
- .addClickListener(
- evt -> {
- if (ToggleTarget.ICON.equals(this.toggleTarget)) {
- evt.stopPropagation();
- toggle();
- }
- activateItem();
- });
- });
- itemIcon.get();
- updateIcon(isCollapsed());
-
- return this;
- }
-
- /**
- * Checks if this tree item is a parent (has sub-items).
- *
- * @return True if this tree item is a parent, false otherwise.
- */
- boolean isParent() {
- return !subItems.isEmpty();
- }
-
- /**
- * Sets the parent tree node for this tree item.
- *
- * @param parentTree The parent tree node.
- */
- void setParent(TreeParent parentTree) {
- this.parent = parentTree;
- }
-
- /**
- * Retrieves the title of this tree item.
- *
- * @return The title of this tree item.
- */
- public String getTitle() {
- return title;
- }
-
- /**
- * Filters this tree item and its children based on a search token.
- *
- * @param searchToken The search token to filter by.
- * @return True if this tree item or any of its children match the search token, false otherwise.
- */
- public boolean filter(String searchToken) {
- boolean found;
- if (isNull(this.originalState)) {
- this.originalState = new OriginalState(isExpanded());
- }
-
- if (isParent()) {
- found = getFilter().filter(this, searchToken) | filterChildren(searchToken);
- } else {
- found = getFilter().filter(this, searchToken);
- }
-
- if (found) {
- dui_hidden.remove(this);
- if (isParent() && isAutoExpandFound() && isCollapsed()) {
- this.expandNode();
- }
- return true;
- } else {
- addCss(dui_hidden);
- return false;
- }
- }
-
- /**
- * Checks if this tree item should be automatically expanded when it's found during a search
- * operation.
- *
- * @return True if this tree item should be automatically expanded when found, false otherwise.
- */
- @Override
- public boolean isAutoExpandFound() {
- return getTreeRoot().isAutoExpandFound();
- }
-
- /**
- * Clears the filter applied to this tree item and its children, restoring their original state.
- */
- public void clearFilter() {
- if (nonNull(originalState)) {
- if (isExpanded() != originalState.expanded) {
- if (this.equals(this.getTreeRoot().getActiveItem())) {
- this.expandNode();
- } else {
- toggleCollapse(originalState.expanded);
- }
- }
- this.originalState = null;
- }
- dui_hidden.remove(this);
- subItems.forEach(TreeItem::clearFilter);
- }
-
- /**
- * Filters the children of this tree item based on a search token.
- *
- * @param searchToken The search token to filter children by.
- * @return True if any of the children match the search token, false otherwise.
- */
- public boolean filterChildren(String searchToken) {
- // We use the noneMatch here instead of anyMatch to make sure we are looping all children
- // instead of early exit on first matching one
- return subItems.stream().filter(treeItem -> treeItem.filter(searchToken)).count() > 0;
- }
-
- /** Collapses all sub-items under this tree item recursively. */
- public void collapseAll() {
- if (isParent() && !isCollapsed()) {
- addCss(dui_transition_none);
- subItems.forEach(TreeItem::collapseAll);
- collapse();
- dui_transition_none.remove(this);
- }
- }
-
- /** Expands all sub-items under this tree item recursively. */
- public void expandAll() {
- if (isParent() && isCollapsed()) {
- addCss(dui_transition_none);
- this.expandNode();
- subItems.forEach(TreeItem::expandAll);
- dui_transition_none.remove(this);
- }
- }
-
- /**
- * Retrieves the Waves effect element associated with this tree item.
- *
- * @return The Waves effect element.
- */
- @Override
- public Element getWavesElement() {
- return anchorElement.element();
- }
-
- /**
- * Checks if this tree item is a leaf (has no sub-items).
- *
- * @return True if this tree item is a leaf, false otherwise.
- */
- public boolean isLeaf() {
- return subItems.isEmpty();
- }
-
- /**
- * Gets the list of sub-items under this tree item.
- *
- * @return The list of sub-items.
- */
- @Override
- public List> getSubItems() {
- return subItems;
- }
-
- /**
- * Selects (shows and activates) this tree item. This method expands the item and activates it.
- */
- public void select() {
- this.show(true).activate(true);
- }
-
- /**
- * Retrieves the value associated with this tree item.
- *
- * @return The value associated with this tree item.
- */
- public T getValue() {
- return value;
- }
-
- /**
- * Sets the value associated with this tree item.
- *
- * @param value The new value to set.
- */
- public void setValue(T value) {
- this.value = value;
- }
-
- /**
- * Removes a sub-item from this tree item.
- *
- * @param item The sub-item to remove.
- */
- @Override
- public void removeItem(TreeItem item) {
- if (subItems.contains(item)) {
- subItems.remove(item);
- }
-
- if (subItems.isEmpty()) {
- collapse();
- }
- }
-
- /**
- * Clears all sub-items from this tree item.
- *
- * @return The current TreeItem instance after clearing all sub-items.
- */
- public TreeItem clear() {
- new ArrayList<>(subItems).forEach(TreeItem::remove);
- return this;
- }
-
- /**
- * Removes this tree item from its parent.
- *
- * @return The current TreeItem instance after removal.
- */
- @Override
- public TreeItem remove() {
- getParent().ifPresent(itemParent -> itemParent.removeItem(this));
- return super.remove();
- }
-
- /**
- * Retrieves the filter associated with this tree item, which is typically inherited from its
- * parent.
- *
- * @return The filter associated with this tree item.
- */
- @Override
- public TreeItemFilter> getFilter() {
- return parent.getFilter();
- }
-
- /**
- * Retrieves the text element associated with this tree item.
- *
- * @return The text element.
- */
- public SpanElement getTextElement() {
- return textElement.get();
- }
-
- /**
- * Sets the title of this tree item and updates the text content of the associated text element.
- *
- * @param title The new title to set.
- * @return The current TreeItem instance.
- */
- public TreeItem setTitle(String title) {
- this.title = title;
- textElement.get().setTextContent(title);
- return this;
- }
-
- /**
- * Retrieves the UListElement` that represents the sub-tree of this tree item.
- *
- * @return The `UListElement` representing the sub-tree.
- */
- public UListElement getSubTree() {
- return subTree;
- }
-
- /**
- * Handles changes in the supplied icon for this tree item and its sub-items.
- *
- * @param iconSupplier The icon supplier responsible for creating icons.
- */
- void onSuppliedIconChanged(Tree.TreeItemIconSupplier iconSupplier) {
- Icon> icon = iconSupplier.createIcon(this);
- if (nonNull(icon) && isNull(itemIcon)) {
- setIcon(icon);
- subItems.forEach(item -> item.onSuppliedIconChanged(iconSupplier));
- }
- }
-
- /**
- * Pauses selection listeners for this tree item.
- *
- * @return The current TreeItem instance after pausing selection listeners.
- */
- @Override
- public TreeItem pauseSelectionListeners() {
- this.selectionListenersPaused = true;
- return this;
- }
-
- /**
- * Resumes selection listeners for this tree item.
- *
- * @return The current TreeItem instance after resuming selection listeners.
- */
- @Override
- public TreeItem resumeSelectionListeners() {
- this.selectionListenersPaused = false;
- return this;
- }
-
- /**
- * Toggles the pause state of selection listeners.
- *
- * @param toggle True to pause selection listeners, false to resume them.
- * @return The current TreeItem instance after toggling selection listeners.
- */
- @Override
- public TreeItem togglePauseSelectionListeners(boolean toggle) {
- this.selectionListenersPaused = toggle;
- return this;
- }
-
- /**
- * Retrieves the set of selection listeners associated with this tree item.
- *
- * @return The set of selection listeners.
- */
- @Override
- public Set, ? super TreeItem>> getSelectionListeners() {
- return this.selectionListeners;
- }
-
- /**
- * Retrieves the set of deselection listeners associated with this tree item.
- *
- * @return The set of deselection listeners.
- */
- @Override
- public Set, ? super TreeItem>>
- getDeselectionListeners() {
- return this.deselectionListeners;
- }
-
- /**
- * Checks if selection listeners for this tree item are currently paused.
- *
- * @return True if selection listeners are paused, false otherwise.
- */
- @Override
- public boolean isSelectionListenersPaused() {
- return this.selectionListenersPaused;
- }
-
- /**
- * Triggers selection listeners for this tree item when a selection event occurs.
- *
- * @param source The source tree item that triggered the event.
- * @param selection The selected tree item.
- * @return The current TreeItem instance after triggering selection listeners.
- */
- @Override
- public TreeItem triggerSelectionListeners(TreeItem source, TreeItem selection) {
- if (!isSelectionListenersPaused()) {
- this.selectionListeners.forEach(
- selectionListener ->
- selectionListener.onSelectionChanged(Optional.ofNullable(source), selection));
- }
- return this;
- }
-
- /**
- * Triggers deselection listeners for this tree item when a deselection event occurs.
- *
- * @param source The source tree item that triggered the event.
- * @param selection The deselected tree item.
- * @return The current TreeItem instance after triggering deselection listeners.
- */
- @Override
- public TreeItem triggerDeselectionListeners(TreeItem source, TreeItem selection) {
- if (!isSelectionListenersPaused()) {
- this.deselectionListeners.forEach(
- selectionListener ->
- selectionListener.onSelectionChanged(Optional.ofNullable(source), selection));
- }
- return this;
- }
-
- /**
- * Retrieves the selected tree item. In the context of a single tree item, this method returns the
- * current tree item itself.
- *
- * @return The selected tree item.
- */
- @Override
- public TreeItem getSelection() {
- return this;
- }
-
- /**
- * A private inner class representing the original state of a tree item. This state includes
- * whether the tree item was expanded or collapsed.
- */
- private static class OriginalState {
- private boolean expanded;
-
- /**
- * Constructs an instance of OriginalState with the specified expanded state.
- *
- * @param expanded True if the tree item was expanded, false if it was collapsed.
- */
- public OriginalState(boolean expanded) {
- this.expanded = expanded;
- }
- }
}
diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeNode.java b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeNode.java
new file mode 100644
index 000000000..0f06ed48d
--- /dev/null
+++ b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeNode.java
@@ -0,0 +1,895 @@
+/*
+ * Copyright © 2019 Dominokit
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.dominokit.domino.ui.tree;
+
+import static java.util.Objects.isNull;
+import static java.util.Objects.nonNull;
+import static org.dominokit.domino.ui.style.DisplayCss.dui_hidden;
+import static org.dominokit.domino.ui.style.GenericCss.dui_active;
+import static org.dominokit.domino.ui.style.GenericCss.dui_transition_none;
+import static org.dominokit.domino.ui.utils.Domino.a;
+import static org.dominokit.domino.ui.utils.Domino.div;
+import static org.dominokit.domino.ui.utils.Domino.li;
+import static org.dominokit.domino.ui.utils.Domino.span;
+import static org.dominokit.domino.ui.utils.Domino.ul;
+
+import elemental2.dom.Element;
+import elemental2.dom.EventListener;
+import elemental2.dom.HTMLAnchorElement;
+import elemental2.dom.HTMLLIElement;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import org.dominokit.domino.ui.collapsible.Collapsible;
+import org.dominokit.domino.ui.config.HasComponentConfig;
+import org.dominokit.domino.ui.config.TreeConfig;
+import org.dominokit.domino.ui.elements.AnchorElement;
+import org.dominokit.domino.ui.elements.DivElement;
+import org.dominokit.domino.ui.elements.LIElement;
+import org.dominokit.domino.ui.elements.SpanElement;
+import org.dominokit.domino.ui.elements.UListElement;
+import org.dominokit.domino.ui.icons.Icon;
+import org.dominokit.domino.ui.icons.StateChangeIcon;
+import org.dominokit.domino.ui.icons.ToggleIcon;
+import org.dominokit.domino.ui.style.WaveStyle;
+import org.dominokit.domino.ui.utils.BaseDominoElement;
+import org.dominokit.domino.ui.utils.HasSelectionListeners;
+import org.dominokit.domino.ui.utils.LazyChild;
+import org.dominokit.domino.ui.utils.PostfixElement;
+import org.dominokit.domino.ui.utils.PrefixElement;
+import org.dominokit.domino.ui.utils.Separator;
+
+public abstract class TreeNode, S>
+ extends BaseDominoElement
+ implements IsParentNode,
+ TreeStyles,
+ HasComponentConfig,
+ HasSelectionListeners {
+
+ protected IsParentNode parent;
+ private ToggleTarget toggleTarget = ToggleTarget.ANY;
+ private LazyChild> nodeIcon;
+ private final List subNodes = new LinkedList<>();
+ protected N activeNode;
+
+ private String title;
+ private LIElement element;
+ private final AnchorElement anchorElement;
+ private final DivElement contentElement;
+ private final LazyChild textElement;
+ private final UListElement subTree;
+
+ private V value;
+ private OriginalState originalState;
+
+ private boolean selectionListenersPaused = false;
+ private Set> selectionListeners = new HashSet<>();
+ private Set> deselectionListeners = new HashSet<>();
+
+ private final EventListener anchorListener =
+ evt -> {
+ if (ToggleTarget.ANY.equals(this.toggleTarget)) {
+ evt.stopPropagation();
+ if (isParent()) {
+ toggle();
+ }
+ activateNode();
+ }
+ };
+
+ private final EventListener iconListener =
+ evt -> {
+ if (ToggleTarget.ICON.equals(this.toggleTarget)) {
+ evt.stopPropagation();
+ if (isParent()) {
+ toggle();
+ }
+ activateNode();
+ }
+ };
+
+ private TreeNode() {
+ this.element =
+ li().addCss(dui_tree_item)
+ .appendChild(
+ anchorElement =
+ a().removeHref()
+ .addCss(dui_tree_anchor)
+ .appendChild(contentElement = div().addCss(dui_tree_item_content)))
+ .appendChild(subTree = ul().addCss(dui_tree_nav).hide());
+ this.textElement = LazyChild.of(span().addCss(dui_tree_item_text), contentElement);
+ init((N) this);
+
+ setCollapseStrategy(getConfig().getTreeDefaultCollapseStrategy(this).get());
+ setAttribute(Collapsible.DUI_COLLAPSED, "true");
+ }
+
+ /**
+ * Constructs a new TreeItem instance with an icon and title.
+ *
+ * @param icon The icon to be displayed for the tree item.
+ * @param title The title of the tree item.
+ */
+ public TreeNode(Icon> icon, String title) {
+ this();
+ setIcon(icon);
+ setTitle(title);
+ init();
+ }
+
+ /**
+ * Constructs a new TreeItem instance with a title.
+ *
+ * @param title The title of the tree item.
+ */
+ public TreeNode(String title) {
+ this();
+ setTitle(title);
+ init();
+ }
+
+ /**
+ * Constructs a new TreeItem instance with an icon.
+ *
+ * @param icon The icon to be displayed for the tree item.
+ */
+ public TreeNode(Icon> icon) {
+ this();
+ setIcon(icon);
+ init();
+ }
+
+ /**
+ * Constructs a new TreeItem instance with a title and a value.
+ *
+ * @param title The title of the tree item.
+ * @param value The value associated with the tree item.
+ */
+ public TreeNode(String title, V value) {
+ this(title);
+ this.value = value;
+ }
+
+ /**
+ * Constructs a new TreeItem instance with an icon, title, and a value.
+ *
+ * @param icon The icon to be displayed for the tree item.
+ * @param title The title of the tree item.
+ * @param value The value associated with the tree item.
+ */
+ public TreeNode(Icon> icon, String title, V value) {
+ this(icon, title);
+ this.value = value;
+ }
+
+ /**
+ * Constructs a new TreeItem instance with an icon and a value.
+ *
+ * @param icon The icon to be displayed for the tree item.
+ * @param value The value associated with the tree item.
+ */
+ public TreeNode(Icon> icon, V value) {
+ this(icon);
+ this.value = value;
+ }
+
+ protected void init() {
+ addBeforeCollapseListener(() -> updateIcon(true));
+ addBeforeExpandListener(() -> updateIcon(false));
+ anchorElement.addClickListener(anchorListener);
+ applyWaves();
+ }
+
+ /**
+ * Sets the title of this tree item and updates the text content of the associated text element.
+ *
+ * @param title The new title to set.
+ * @return The current TreeItem instance.
+ */
+ public N setTitle(String title) {
+ this.title = title;
+ textElement.get().setTextContent(title);
+ return (N) this;
+ }
+
+ /**
+ * Retrieves the title of this tree item.
+ *
+ * @return The title of this tree item.
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * Sets the parent tree node for this tree item.
+ *
+ * @param parentNode The parent tree node.
+ */
+ void setParent(IsParentNode parentNode) {
+ this.parent = parentNode;
+ }
+
+ protected abstract void activateNode();
+
+ /**
+ * Deactivates (deselects) this tree item and any of its sub-items if it is a parent. If
+ * auto-collapse is enabled in the tree root, it will collapse the tree item as well. This method
+ * returns void.
+ */
+ public void deactivate() {
+ dui_active.remove(this);
+ if (isParent()) {
+ subNodes.forEach(TreeNode::deactivate);
+ if (getRootNode().isAutoCollapse()) {
+ collapse();
+ }
+ }
+ updateIcon(isCollapsed());
+ }
+
+ /**
+ * Appends a child tree item to this tree item.
+ *
+ * @param node The child tree item to append.
+ * @return This tree item with the child tree item appended.
+ */
+ public N appendChild(N node) {
+ this.subNodes.add(node);
+ subTree.appendChild(node);
+ node.parent = this;
+ node.setToggleTarget(this.toggleTarget);
+ updateIcon(isCollapsed());
+ getParent()
+ .ifPresent(
+ p -> {
+ if (nonNull(p.getRootNode())) {
+ NodeIconSupplier iconSupplier = p.getRootNode().getIconSupplier();
+ if (nonNull(iconSupplier)) {
+ subNodes.forEach(item -> item.onSuppliedIconChanged(iconSupplier));
+ }
+ }
+ });
+ return (N) this;
+ }
+
+ public N appendChild(N... treeItems) {
+ Arrays.stream(treeItems).forEach(this::appendChild);
+ return (N) this;
+ }
+
+ /**
+ * Removes a sub-item from this tree item.
+ *
+ * @param item The sub-item to remove.
+ */
+ public void removeNode(N item) {
+ if (subNodes.contains(item)) {
+ subNodes.remove(item);
+ }
+
+ if (subNodes.isEmpty()) {
+ collapse();
+ }
+ }
+
+ /**
+ * Clears all sub-items from this tree item.
+ *
+ * @return The current TreeItem instance after clearing all sub-items.
+ */
+ public N clear() {
+ new ArrayList<>(subNodes).forEach(TreeNode::remove);
+ return (N) this;
+ }
+
+ /**
+ * Removes this tree item from its parent.
+ *
+ * @return The current TreeItem instance after removal.
+ */
+ @Override
+ public N remove() {
+ getParent().ifPresent(itemParent -> itemParent.removeNode((N) this));
+ return (N) super.remove();
+ }
+
+ @Override
+ public PrefixElement getPrefixElement() {
+ return PrefixElement.of(contentElement);
+ }
+
+ @Override
+ public PostfixElement getPostfixElement() {
+ return PostfixElement.of(contentElement);
+ }
+
+ private void updateIcon(boolean collapsed) {
+ if (nonNull(nodeIcon) && nodeIcon.isInitialized()) {
+ if (nodeIcon.element() instanceof ToggleIcon) {
+ ((ToggleIcon, ?>) nodeIcon.element()).toggle();
+ } else if (nodeIcon.element() instanceof StateChangeIcon) {
+ StateChangeIcon, ?> icon = (StateChangeIcon, ?>) nodeIcon.element();
+ if (isParent()) {
+ icon.setState(collapsed ? TreeItemIcon.STATE_COLLAPSED : TreeItemIcon.STATE_EXPANDED);
+ } else {
+ if (dui_active.isAppliedTo(this)) {
+ icon.setState(TreeItemIcon.STATE_ACTIVE);
+ } else {
+ icon.setState(TreeItemIcon.STATE_LEAF);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds a separator to this tree item.
+ *
+ * @return This tree item with a separator added.
+ */
+ public N appendChild(Separator separator) {
+ subTree.appendChild(separator);
+ return (N) this;
+ }
+
+ /**
+ * Returns the root of the tree structure to which this tree item belongs.
+ *
+ * @return The root Tree instance.
+ */
+ @Override
+ public RootNode getRootNode() {
+ return parent.getRootNode();
+ }
+
+ /**
+ * Returns an optional parent of this tree item. If the parent exists and is a TreeItem, it
+ * returns an Optional containing the parent; otherwise, it returns an empty Optional.
+ *
+ * @return An Optional containing the parent TreeItem or an empty Optional.
+ */
+ public Optional> getParent() {
+ if (parent instanceof TreeNode) {
+ return Optional.of(parent);
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * Sets the currently active (selected) tree item within the tree structure.
+ *
+ * @param activeNode The TreeItem to set as the active item.
+ */
+ public void setActiveNode(N activeNode) {
+ setActiveNode(activeNode, isSelectionListenersPaused());
+ }
+
+ /**
+ * Returns the path to this tree item.
+ *
+ * @return A list of tree items representing the path to this item, starting from the root.
+ */
+ public List getPath() {
+ List items = new ArrayList<>();
+ items.add((N) this);
+ Optional> parent = getParent();
+
+ while (parent.isPresent()) {
+ items.add((N) parent.get());
+ parent = parent.get().getParent();
+ }
+
+ Collections.reverse(items);
+
+ return items;
+ }
+
+ /**
+ * Returns the values associated with the path to this tree item.
+ *
+ * @return A list of values representing the path to this item, starting from the root.
+ */
+ public List getPathValues() {
+ List values = new ArrayList<>();
+ values.add(this.getValue());
+ Optional> parent = getParent();
+
+ while (parent.isPresent()) {
+ values.add(((N) parent.get()).getValue());
+ parent = parent.get().getParent();
+ }
+
+ Collections.reverse(values);
+
+ return values;
+ }
+
+ /**
+ * Gets the list of sub-items under this tree item.
+ *
+ * @return The list of sub-items.
+ */
+ public List getSubNodes() {
+ return subNodes;
+ }
+
+ /**
+ * Expands (shows and activates) this tree item. This method expands the item and activates it.
+ */
+ public void expandAndActivate() {
+ this.show(true).activate(true);
+ }
+
+ /**
+ * Activates (selects) this tree item without activating its parent items. This method returns
+ * void.
+ */
+ public void activate() {
+ activate(false);
+ }
+
+ /**
+ * Activates (selects) this tree item. If 'activateParent' is true, it also activates its parent
+ * items in the tree structure. This method returns void.
+ *
+ * @param activateParent True to activate parent items, false to activate only this item.
+ */
+ public void activate(boolean activateParent) {
+ addCss(dui_active);
+ if (activateParent) {
+ getParent().ifPresent(parent -> parent.setActiveNode((N) this));
+ }
+ updateIcon(isCollapsed());
+ }
+
+ /**
+ * Returns the currently active (selected) tree item within the tree structure.
+ *
+ * @return The currently active TreeItem instance.
+ */
+ public N getActiveNode() {
+ return activeNode;
+ }
+
+ /**
+ * Sets the toggle target for this tree item.
+ *
+ * @param toggleTarget The toggle target to set.
+ * @return This tree item with the toggle target set.
+ */
+ public TreeNode setToggleTarget(ToggleTarget toggleTarget) {
+ if (nonNull(toggleTarget)) {
+
+ this.toggleTarget = toggleTarget;
+ if (ToggleTarget.ICON.equals(toggleTarget)) {
+ removeWaves();
+ if (nonNull(nodeIcon) && nodeIcon.isInitialized()) {
+ nodeIcon.get().setClickable(true).addEventListener("click", iconListener, true);
+ }
+ } else {
+ applyWaves();
+ if (nonNull(nodeIcon) && nodeIcon.isInitialized()) {
+ nodeIcon.get().setClickable(false).removeEventListener("click", iconListener);
+ }
+ }
+
+ subNodes.forEach(item -> item.setToggleTarget(toggleTarget));
+ }
+ return this;
+ }
+
+ private void toggle() {
+ if (isParent()) {
+ toggleCollapse();
+ }
+ }
+
+ /**
+ * Checks if this tree item is a parent (has sub-items).
+ *
+ * @return True if this tree item is a parent, false otherwise.
+ */
+ boolean isParent() {
+ return !subNodes.isEmpty();
+ }
+
+ /**
+ * Checks if this tree item is a leaf (has no sub-items).
+ *
+ * @return True if this tree item is a leaf, false otherwise.
+ */
+ public boolean isLeaf() {
+ return subNodes.isEmpty();
+ }
+
+ protected void applyWaves() {
+ withWaves((item, waves) -> waves.setWaveStyle(WaveStyle.BLOCK));
+ }
+
+ /**
+ * Handles changes in the supplied icon for this tree item and its sub-items.
+ *
+ * @param iconSupplier The icon supplier responsible for creating icons.
+ */
+ void onSuppliedIconChanged(NodeIconSupplier iconSupplier) {
+ Icon> icon = iconSupplier.createIcon((N) this);
+ if (nonNull(icon) && isNull(nodeIcon)) {
+ setIcon(icon);
+ subNodes.forEach(item -> item.onSuppliedIconChanged(iconSupplier));
+ }
+ }
+
+ /**
+ * Sets the icon for this tree item. If the 'icon' parameter is not null, it replaces the existing
+ * icon with the new one. This method returns the current TreeItem instance.
+ *
+ * @param icon The new icon to set.
+ * @return The current TreeItem instance.
+ */
+ public N setIcon(Icon> icon) {
+ if (nonNull(nodeIcon)) {
+ if (nodeIcon.isInitialized()) {
+ nodeIcon.remove();
+ }
+ }
+ nodeIcon = LazyChild.of(icon.addCss(dui_tree_item_icon), contentElement);
+ nodeIcon.whenInitialized(
+ () -> {
+ nodeIcon.element().forEachChild(i -> i.addCss(dui_tree_item_icon));
+ });
+ nodeIcon.whenInitialized(
+ () -> {
+ if (ToggleTarget.ICON.equals(this.toggleTarget)) {
+ nodeIcon.element().clickable();
+ }
+ nodeIcon
+ .element()
+ .addClickListener(
+ evt -> {
+ if (ToggleTarget.ICON.equals(this.toggleTarget)) {
+ evt.stopPropagation();
+ toggle();
+ }
+ activateNode();
+ });
+ });
+ nodeIcon.get();
+ updateIcon(isCollapsed());
+
+ return (N) this;
+ }
+
+ /**
+ * Returns the clickable HTML anchor element associated with this tree item.
+ *
+ * @return The clickable HTML anchor element.
+ */
+ @Override
+ public HTMLAnchorElement getClickableElement() {
+ return anchorElement.element();
+ }
+
+ /** Collapses all sub-items under this tree item recursively. */
+ public void collapseAll() {
+ if (isParent() && !isCollapsed()) {
+ addCss(dui_transition_none);
+ subNodes.forEach(TreeNode::collapseAll);
+ collapse();
+ dui_transition_none.remove(this);
+ }
+ }
+
+ /** Expands all sub-items under this tree item recursively. */
+ public void expandAll() {
+ if (isParent() && isCollapsed()) {
+ addCss(dui_transition_none);
+ this.expandNode();
+ subNodes.forEach(TreeNode::expandAll);
+ dui_transition_none.remove(this);
+ }
+ }
+
+ /**
+ * Expands the tree node represented by this tree item.
+ *
+ * @return This tree item with the node expanded.
+ */
+ @Override
+ public N expandNode() {
+ return show(false);
+ }
+
+ /**
+ * Expands the tree node represented by this tree item. If the 'expandParent' parameter is true,
+ * parent nodes will also be expanded. This method returns the current TreeItem instance.
+ *
+ * @param expandParent True to expand parent nodes, false to expand only the current node.
+ * @return The current TreeItem instance.
+ */
+ @Override
+ public N expandNode(boolean expandParent) {
+ return show(expandParent);
+ }
+
+ /**
+ * Shows the tree node represented by this tree item.
+ *
+ * @param expandParent {@code true} to expand the parent nodes, {@code false} otherwise.
+ * @return This tree item with the node shown.
+ */
+ public N show(boolean expandParent) {
+ if (isParent()) {
+ super.expand();
+ }
+ if (expandParent) {
+ getParent().ifPresent(itemParent -> itemParent.expandNode(true));
+ }
+ return (N) this;
+ }
+
+ /**
+ * Clears the filter applied to this tree item and its children, restoring their original state.
+ */
+ public void clearFilter() {
+ if (nonNull(originalState)) {
+ if (isExpanded() != originalState.expanded) {
+ if (this.equals(this.getRootNode().getActiveNode())) {
+ this.expandNode();
+ } else {
+ toggleCollapse(originalState.expanded);
+ }
+ }
+ this.originalState = null;
+ }
+ dui_hidden.remove(this);
+ subNodes.forEach(TreeNode::clearFilter);
+ }
+
+ /**
+ * Filters this tree item and its children based on a search token.
+ *
+ * @param searchToken The search token to filter by.
+ * @return True if this tree item or any of its children match the search token, false otherwise.
+ */
+ public boolean filter(String searchToken) {
+ boolean found;
+ if (isNull(this.originalState)) {
+ this.originalState = new TreeNode.OriginalState(isExpanded());
+ }
+
+ if (isParent()) {
+ found = getFilter().filter((N) this, searchToken) | filterChildren(searchToken);
+ } else {
+ found = getFilter().filter((N) this, searchToken);
+ }
+
+ if (found) {
+ dui_hidden.remove(this);
+ if (isParent() && isAutoExpandFound() && isCollapsed()) {
+ this.expandNode();
+ }
+ return true;
+ } else {
+ addCss(dui_hidden);
+ return false;
+ }
+ }
+
+ /**
+ * Filters the children of this tree item based on a search token.
+ *
+ * @param searchToken The search token to filter children by.
+ * @return True if any of the children match the search token, false otherwise.
+ */
+ public boolean filterChildren(String searchToken) {
+ // We use the noneMatch here instead of anyMatch to make sure we are looping all children
+ // instead of early exit on first matching one
+ return subNodes.stream().filter(treeItem -> treeItem.filter(searchToken)).count() > 0;
+ }
+
+ /**
+ * Checks if this tree item should be automatically expanded when it's found during a search
+ * operation.
+ *
+ * @return True if this tree item should be automatically expanded when found, false otherwise.
+ */
+ public boolean isAutoExpandFound() {
+ return getRootNode().isAutoExpandFound();
+ }
+
+ /**
+ * Retrieves the filter associated with this tree item, which is typically inherited from its
+ * parent.
+ *
+ * @return The filter associated with this tree item.
+ */
+ public TreeItemFilter getFilter() {
+ return parent.getFilter();
+ }
+
+ /**
+ * Retrieves the value associated with this tree item.
+ *
+ * @return The value associated with this tree item.
+ */
+ public V getValue() {
+ return value;
+ }
+
+ /**
+ * Sets the value associated with this tree item.
+ *
+ * @param value The new value to set.
+ */
+ public void setValue(V value) {
+ this.value = value;
+ }
+
+ /**
+ * Retrieves the Waves effect element associated with this tree item.
+ *
+ * @return The Waves effect element.
+ */
+ @Override
+ public Element getWavesElement() {
+ return anchorElement.element();
+ }
+
+ /**
+ * Retrieves the text element associated with this tree item.
+ *
+ * @return The text element.
+ */
+ public SpanElement getTextElement() {
+ return textElement.get();
+ }
+
+ /**
+ * Retrieves the UListElement` that represents the sub-tree of this tree item.
+ *
+ * @return The `UListElement` representing the sub-tree.
+ */
+ public UListElement getSubTree() {
+ return subTree;
+ }
+
+ /**
+ * Pauses selection listeners for this tree item.
+ *
+ * @return The current TreeItem instance after pausing selection listeners.
+ */
+ @Override
+ public N pauseSelectionListeners() {
+ this.selectionListenersPaused = true;
+ return (N) this;
+ }
+
+ /**
+ * Resumes selection listeners for this tree item.
+ *
+ * @return The current TreeItem instance after resuming selection listeners.
+ */
+ @Override
+ public N resumeSelectionListeners() {
+ this.selectionListenersPaused = false;
+ return (N) this;
+ }
+
+ /**
+ * Toggles the pause state of selection listeners.
+ *
+ * @param toggle True to pause selection listeners, false to resume them.
+ * @return The current TreeItem instance after toggling selection listeners.
+ */
+ @Override
+ public N togglePauseSelectionListeners(boolean toggle) {
+ this.selectionListenersPaused = toggle;
+ return (N) this;
+ }
+
+ /**
+ * Retrieves the set of selection listeners associated with this tree item.
+ *
+ * @return The set of selection listeners.
+ */
+ @Override
+ public Set> getSelectionListeners() {
+ return this.selectionListeners;
+ }
+
+ /**
+ * Retrieves the set of deselection listeners associated with this tree item.
+ *
+ * @return The set of deselection listeners.
+ */
+ @Override
+ public Set> getDeselectionListeners() {
+ return this.deselectionListeners;
+ }
+
+ /**
+ * Checks if selection listeners for this tree item are currently paused.
+ *
+ * @return True if selection listeners are paused, false otherwise.
+ */
+ @Override
+ public boolean isSelectionListenersPaused() {
+ return this.selectionListenersPaused;
+ }
+
+ /**
+ * Triggers selection listeners for this tree item when a selection event occurs.
+ *
+ * @param source The source tree item that triggered the event.
+ * @param selection The selected tree item.
+ * @return The current TreeItem instance after triggering selection listeners.
+ */
+ @Override
+ public N triggerSelectionListeners(N source, S selection) {
+ if (!isSelectionListenersPaused()) {
+ this.selectionListeners.forEach(
+ selectionListener ->
+ selectionListener.onSelectionChanged(Optional.ofNullable(source), selection));
+ }
+ return (N) this;
+ }
+
+ /**
+ * Triggers deselection listeners for this tree item when a deselection event occurs.
+ *
+ * @param source The source tree item that triggered the event.
+ * @param selection The deselected tree item.
+ * @return The current TreeItem instance after triggering deselection listeners.
+ */
+ @Override
+ public N triggerDeselectionListeners(N source, S selection) {
+ if (!isSelectionListenersPaused()) {
+ this.deselectionListeners.forEach(
+ selectionListener ->
+ selectionListener.onSelectionChanged(Optional.ofNullable(source), selection));
+ }
+ return (N) this;
+ }
+
+ @Override
+ public HTMLLIElement element() {
+ return this.element.element();
+ }
+
+ /**
+ * A private inner class representing the original state of a tree item. This state includes
+ * whether the tree item was expanded or collapsed.
+ */
+ private static class OriginalState {
+ private boolean expanded;
+
+ /**
+ * Constructs an instance of OriginalState with the specified expanded state.
+ *
+ * @param expanded True if the tree item was expanded, false if it was collapsed.
+ */
+ public OriginalState(boolean expanded) {
+ this.expanded = expanded;
+ }
+ }
+}
diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeRoot.java b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeRoot.java
new file mode 100644
index 000000000..51c352d5f
--- /dev/null
+++ b/domino-ui/src/main/java/org/dominokit/domino/ui/tree/TreeRoot.java
@@ -0,0 +1,796 @@
+/*
+ * Copyright © 2019 Dominokit
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.dominokit.domino.ui.tree;
+
+import static java.util.Objects.isNull;
+import static java.util.Objects.nonNull;
+import static org.dominokit.domino.ui.style.DisplayCss.dui_hidden;
+import static org.dominokit.domino.ui.utils.Domino.div;
+import static org.dominokit.domino.ui.utils.Domino.ul;
+
+import elemental2.dom.HTMLDivElement;
+import elemental2.dom.HTMLElement;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import org.dominokit.domino.ui.collapsible.CollapseStrategy;
+import org.dominokit.domino.ui.elements.DivElement;
+import org.dominokit.domino.ui.elements.SpanElement;
+import org.dominokit.domino.ui.elements.UListElement;
+import org.dominokit.domino.ui.icons.Icon;
+import org.dominokit.domino.ui.icons.ToggleMdiIcon;
+import org.dominokit.domino.ui.icons.lib.Icons;
+import org.dominokit.domino.ui.search.Search;
+import org.dominokit.domino.ui.utils.BaseDominoElement;
+import org.dominokit.domino.ui.utils.ChildHandler;
+import org.dominokit.domino.ui.utils.HasSelectionListeners;
+import org.dominokit.domino.ui.utils.LazyChild;
+import org.dominokit.domino.ui.utils.PostfixAddOn;
+import org.dominokit.domino.ui.utils.Separator;
+
+public abstract class TreeRoot, C extends TreeRoot, S>
+ extends BaseDominoElement
+ implements TreeStyles,
+ IsParentNode,
+ RootNode,
+ HasSelectionListeners {
+
+ private final List subNodes = new ArrayList<>();
+ private final DivElement rootElement;
+ private final DivElement bodyElement;
+ private final UListElement subTree;
+ private final LazyChild headerElement;
+ private ToggleTarget toggleTarget = ToggleTarget.ANY;
+ private CollapseStrategy collapseStrategy;
+ private boolean autoCollapse = true;
+ private boolean autoExpandFound;
+ private LazyChild> collapseExpandAllIcon;
+ private NodeIconSupplier iconSupplier;
+ protected N activeNode;
+ private V value;
+
+ private LazyChild> searchIcon;
+ private LazyChild search;
+ private TreeItemFilter filter =
+ (treeItem, searchToken) ->
+ treeItem.getTitle().toLowerCase().contains(searchToken.toLowerCase());
+
+ private boolean selectionListenersPaused;
+ private final Set