Added IconsHelper and updated Keyboard to support require_inside flag
This commit is contained in:
@@ -4,6 +4,7 @@ TreeView component for hierarchical data visualization with inline editing.
|
||||
This component provides an interactive tree structure with expand/collapse,
|
||||
selection, and inline editing capabilities.
|
||||
"""
|
||||
import logging
|
||||
import uuid
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
@@ -19,6 +20,19 @@ from myfasthtml.core.instances import MultipleInstance
|
||||
from myfasthtml.icons.fluent_p1 import chevron_right20_regular, edit20_regular
|
||||
from myfasthtml.icons.fluent_p2 import chevron_down20_regular, add_circle20_regular, delete20_regular
|
||||
|
||||
logger = logging.getLogger("TreeView")
|
||||
|
||||
|
||||
@dataclass
|
||||
class TreeViewConf:
|
||||
edit_node: bool = True
|
||||
add_node: bool = True
|
||||
delete_node: bool = True
|
||||
edit_leaf: bool = True
|
||||
add_leaf: bool = True
|
||||
delete_leaf: bool = True
|
||||
icons: dict = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class TreeNode:
|
||||
@@ -157,7 +171,7 @@ class TreeView(MultipleInstance):
|
||||
- Node selection
|
||||
"""
|
||||
|
||||
def __init__(self, parent, items: Optional[dict] = None, _id: Optional[str] = None):
|
||||
def __init__(self, parent, items: Optional[dict] = None, conf: TreeViewConf = None, _id: Optional[str] = None):
|
||||
"""
|
||||
Initialize TreeView component.
|
||||
|
||||
@@ -168,10 +182,14 @@ class TreeView(MultipleInstance):
|
||||
"""
|
||||
super().__init__(parent, _id=_id)
|
||||
self._state = TreeViewState(self)
|
||||
self.conf = conf or TreeViewConf()
|
||||
self.commands = Commands(self)
|
||||
|
||||
|
||||
if items:
|
||||
self._state.items = items
|
||||
|
||||
if self.conf.icons:
|
||||
self._state.icon_config = self.conf.icons
|
||||
|
||||
def set_icon_config(self, config: dict[str, str]):
|
||||
"""
|
||||
@@ -207,7 +225,7 @@ class TreeView(MultipleInstance):
|
||||
else:
|
||||
parent.children.append(node.id)
|
||||
|
||||
def ensure_path(self, path: str):
|
||||
def ensure_path(self, path: str, node_type="folder"):
|
||||
"""Add a node to the tree based on a path string.
|
||||
|
||||
Args:
|
||||
@@ -237,7 +255,7 @@ class TreeView(MultipleInstance):
|
||||
node = [node for node in current_nodes if node.label == part]
|
||||
if len(node) == 0:
|
||||
# create the node
|
||||
node = TreeNode(label=part, type="folder")
|
||||
node = TreeNode(label=part, type=node_type)
|
||||
self.add_node(node, parent_id=parent_id)
|
||||
else:
|
||||
node = node[0]
|
||||
@@ -341,6 +359,7 @@ class TreeView(MultipleInstance):
|
||||
|
||||
def _cancel_rename(self):
|
||||
"""Cancel renaming operation."""
|
||||
logger.debug("_cancel_rename")
|
||||
self._state.editing = None
|
||||
return self
|
||||
|
||||
@@ -380,12 +399,22 @@ class TreeView(MultipleInstance):
|
||||
|
||||
def _render_action_buttons(self, node_id: str):
|
||||
"""Render action buttons for a node (visible on hover)."""
|
||||
return Div(
|
||||
mk.icon(add_circle20_regular, command=self.commands.add_child(node_id)),
|
||||
mk.icon(edit20_regular, command=self.commands.start_rename(node_id)),
|
||||
mk.icon(delete20_regular, command=self.commands.delete_node(node_id)),
|
||||
cls="mf-treenode-actions"
|
||||
)
|
||||
is_leaf = len(self._state.items[node_id].children) == 0
|
||||
conf = self.conf
|
||||
|
||||
add_visible = conf.add_leaf if is_leaf else conf.add_node
|
||||
edit_visible = conf.edit_leaf if is_leaf else conf.edit_node
|
||||
delete_visible = conf.delete_leaf if is_leaf else conf.delete_node
|
||||
|
||||
buttons = []
|
||||
if add_visible:
|
||||
buttons.append(mk.icon(add_circle20_regular, command=self.commands.add_child(node_id)))
|
||||
if edit_visible:
|
||||
buttons.append(mk.icon(edit20_regular, command=self.commands.start_rename(node_id)))
|
||||
if delete_visible:
|
||||
buttons.append(mk.icon(delete20_regular, command=self.commands.delete_node(node_id)))
|
||||
|
||||
return Div(*buttons, cls="mf-treenode-actions")
|
||||
|
||||
def _render_node(self, node_id: str, level: int = 0):
|
||||
"""
|
||||
@@ -404,10 +433,13 @@ class TreeView(MultipleInstance):
|
||||
is_editing = node_id == self._state.editing
|
||||
has_children = len(node.children) > 0
|
||||
|
||||
# Toggle icon
|
||||
toggle = mk.icon(
|
||||
chevron_down20_regular if is_expanded else chevron_right20_regular if has_children else None,
|
||||
command=self.commands.toggle_node(node_id))
|
||||
# Toggle icon (only for nodes with children)
|
||||
if has_children:
|
||||
toggle = mk.icon(
|
||||
chevron_down20_regular if is_expanded else chevron_right20_regular,
|
||||
command=self.commands.toggle_node(node_id))
|
||||
else:
|
||||
toggle = None
|
||||
|
||||
# Label or input for editing
|
||||
if is_editing:
|
||||
@@ -426,7 +458,7 @@ class TreeView(MultipleInstance):
|
||||
node_element = Div(
|
||||
toggle,
|
||||
label_element,
|
||||
self._render_action_buttons(node_id),
|
||||
*([self._render_action_buttons(node_id)] if not is_editing else []),
|
||||
cls=f"mf-treenode flex {'selected' if is_selected and not is_editing else ''}",
|
||||
style=f"padding-left: {level * 20}px"
|
||||
)
|
||||
@@ -461,7 +493,7 @@ class TreeView(MultipleInstance):
|
||||
|
||||
return Div(
|
||||
*[self._render_node(node_id) for node_id in root_nodes],
|
||||
Keyboard(self, {"esc": self.commands.cancel_rename()}, _id="-keyboard"),
|
||||
Keyboard(self, {"esc": {"command": self.commands.cancel_rename(), "require_inside": False}}, _id="-keyboard"),
|
||||
id=self._id,
|
||||
cls="mf-treeview"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user