Added IconsHelper and updated Keyboard to support require_inside flag

This commit is contained in:
2026-02-20 20:35:09 +01:00
parent b09763b1eb
commit 13f292fc9d
12 changed files with 219 additions and 76 deletions

View File

@@ -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"
)