super_tree_field 1.0.2
super_tree_field: ^1.0.2 copied to clipboard
Super Tree — a GeniusLink design-system Flutter package providing a recursive, generic, keyboard-first hierarchy component for arbitrary typed data. Supports search, expansion, selection, inline editi [...]
super_tree_field #
A generic, typed, keyboard-first tree widget for Flutter.
super_tree_field renders arbitrary hierarchical data through
TreeNode<T>, manages interaction state with SuperTreeController<T>, and
keeps domain-specific models and presentation outside the package API.
Features #
- Generic
TreeNode<T>payloads for any application domain. - Recursive hierarchies with unlimited practical depth.
- Expand, collapse, expand-all, collapse-all, and subtree expansion.
- Ancestor-preserving, case-insensitive search with inline highlighting.
- Keyboard navigation with LTR- and RTL-aware horizontal behavior.
- Single and multi checkbox selection with derived tristate groups.
- Readable and editable modes.
- Inline rename, add, delete, and drag-and-drop tree mutations.
- Custom leading and trailing row content.
- Optional Arabic secondary labels.
- Optional responsive
SuperTreeControls<T>rendered outside the tree. - Configurable internal
ListViewscrolling and externalScrollController. - Light and dark design-system theming through
super_core. - Text input integration through
super_form_field.
Getting started #
Requirements #
| Requirement | Version |
|---|---|
| Dart | >=3.8.0 <4.0.0 |
| Flutter | >=3.32.0 |
super_core |
>=3.3.0 <4.0.0 |
super_form_field |
^1.8.2 |
Install #
Add the package to pubspec.yaml:
dependencies:
super_tree_field: ^1.0.0
Then run:
flutter pub get
Import the public barrel:
import 'package:super_tree_field/super_tree.dart';
The barrel also re-exports the super_core API used by the package.
Configure the theme #
super_tree_field reads colors, spacing, typography, radii, and motion from
SuperMaterialThemeData. With super_core >=3.3.0, provide both
textTheme and primaryTextTheme explicitly:
final textTheme = SuperTextTheme(isDesktop: true);
MaterialApp(
theme: SuperMaterialThemeData.light(
mode: SuperDeviceMode.desktop,
textTheme: textTheme,
primaryTextTheme: textTheme,
),
darkTheme: SuperMaterialThemeData.dark(
mode: SuperDeviceMode.desktop,
textTheme: textTheme,
primaryTextTheme: textTheme,
),
home: const ProjectTreePage(),
);
For adaptive applications, resolve the device mode from the available width and rebuild the application theme when the active breakpoint changes:
final mode = SuperDeviceMode.forWidth(width);
Use context.superTextTheme to read typography. Do not use the removed
SuperThemeData.textTheme API from older super_core releases.
Usage #
1. Define typed nodes #
Every node has a stable code, a primary name, an optional Arabic label,
an optional typed value, and optional children:
@immutable
class FileInfo {
const FileInfo({required this.isDirectory, this.sizeLabel});
final bool isDirectory;
final String? sizeLabel;
}
const projectTree = <TreeNode<FileInfo>>[
TreeNode<FileInfo>(
code: 'lib',
name: 'lib',
value: FileInfo(isDirectory: true),
children: [
TreeNode<FileInfo>(
code: 'lib/main.dart',
name: 'main.dart',
value: FileInfo(isDirectory: false, sizeLabel: '2.4 KB'),
),
],
),
];
TreeNode.code must be unique across the complete tree. Expansion, focus,
selection, editing, and drag-and-drop identity all depend on it.
Useful node APIs include hasChildren, isLeaf, withChildren, copyWith,
and renamed.
2. Create and dispose the controllers #
Create controllers once in State, not in build(), and dispose every
controller that the host owns:
class ProjectTreePage extends StatefulWidget {
const ProjectTreePage({super.key});
@override
State<ProjectTreePage> createState() => _ProjectTreePageState();
}
class _ProjectTreePageState extends State<ProjectTreePage> {
late final SuperTreeController<FileInfo> _treeController;
late final SuperTreeControlsController _controlsController;
@override
void initState() {
super.initState();
_treeController = SuperTreeController<FileInfo>(
roots: projectTree,
defaultExpandDepth: 0,
searchText: (node) => '${node.code} ${node.name}',
onOpenLeaf: (node) => debugPrint('Open ${node.code}'),
);
_controlsController = SuperTreeControlsController();
}
@override
void dispose() {
_controlsController.dispose();
_treeController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
SuperTreeControls<FileInfo>(
controller: _treeController,
controlsController: _controlsController,
placeholder: 'Search project files',
samples: const ['lib', 'main.dart'],
),
const SizedBox(height: 12),
Expanded(
child: SuperTree<FileInfo>(
controller: _treeController,
title: 'Project files',
nameColumnLabel: 'Name',
trailingColumnLabel: 'Size',
unit: 'files',
showArabic: false,
onSearchRequested:
_controlsController.requestSearchFocus,
onShortcutsRequested: () => showShortcutsHelp(context),
leadingBuilder: (context, node, info) {
final isDirectory =
node.value?.isDirectory ?? node.hasChildren;
return Icon(
isDirectory
? (info.open ? Icons.folder_open : Icons.folder)
: Icons.description_outlined,
size: 18,
);
},
trailingBuilder: (context, node, info) {
final size = node.value?.sizeLabel;
return size == null ? null : Text(size);
},
),
),
],
),
),
),
);
}
}
SuperTree renders the hierarchy only. SuperTreeControls is an optional,
separate toolbar that provides responsive search, quick-query chips, editing
controls, keyboard help, and expand/collapse actions.
Build custom controls #
You do not have to use SuperTreeControls. Any host UI can drive the public
controller API directly:
controller.setQuery(query);
controller.clearQuery();
controller.expandAll();
controller.collapseAll();
controller.setMode(SuperTreeMode.editable);
controller.addRoot();
Connect host-owned search and help UI to keyboard shortcuts with
onSearchRequested and onShortcutsRequested.
Controller state #
SuperTreeController<T> extends ChangeNotifier and owns the hierarchy's
interaction state.
Common reads:
controller.roots;
controller.visible;
controller.query;
controller.matchCount;
controller.focusId;
controller.selected;
controller.mode;
controller.editingId;
controller.checked;
controller.checkedCount;
Common updates:
controller.setRoots(updatedRoots);
controller.setQuery('settings');
controller.clearQuery();
controller.setMode(SuperTreeMode.editable);
controller.toggleMode();
controller.expandAll();
controller.collapseAll();
controller.expandSubtree('root.tools');
Supply onTreeChanged when structural edits must be persisted, and
onSelectionChanged when checkbox selection must be synchronized with
application state.
Search #
Search text is application-defined:
final controller = SuperTreeController<MyItem>(
roots: roots,
searchText: (node) => [
node.code,
node.name,
node.ar,
node.value?.searchLabel,
].whereType<String>().join(' '),
);
Search is case-insensitive and ancestor-preserving. A matching descendant keeps its path visible, and filtered branches render expanded for the duration of the query.
Selection #
Configure selection when the controller is created:
final controller = SuperTreeController<Permission>(
roots: permissions,
searchText: (node) => node.name,
selectionMode: SuperTreeSelectionMode.multi,
initialChecked: const {'permission.read'},
onSelectionChanged: persistSelection,
);
| Mode | Behavior |
|---|---|
SuperTreeSelectionMode.none |
No checkboxes. This is the default. |
SuperTreeSelectionMode.single |
At most one node code is checked. |
SuperTreeSelectionMode.multi |
Groups derive checked, partial, or unchecked state from descendant leaves. |
Useful APIs include checkState, isChecked, rootCheckState,
toggleChecked, toggleCheckedFocused, checkAll, clearChecked,
toggleCheckAll, and setChecked.
Editing #
Editing is controlled by SuperTreeController.mode; the tree does not render a
built-in Read/Edit switch or Add button.
Provide newNodeBuilder when newly created nodes require a non-null typed
payload:
final controller = SuperTreeController<Category>(
roots: categories,
searchText: (node) => node.name,
newNodeBuilder: (code) => TreeNode<Category>(
code: code,
name: 'New category',
value: const Category(),
),
onTreeChanged: saveTree,
);
Editable mode supports:
- Inline rename.
- Add root, child, sibling before, and sibling after.
- Delete a node and its subtree.
- Drag-and-drop before, inside, or after another node.
- Context-menu editing actions.
Programmatic editing APIs include beginRename, commitRename,
cancelRename, addRoot, addChild, addSiblingBefore, addSiblingAfter,
deleteNode, moveNode, and canDrop.
Structural editing is intentionally unavailable while a search projection is active. Clear the query before restructuring the hierarchy.
Custom row content #
leadingBuilder is required. trailingBuilder is optional. Both receive a
TreeRowInfo describing the rendered row:
SuperTree<MyItem>(
controller: controller,
leadingBuilder: (context, node, info) {
return Icon(info.hasChildren ? Icons.folder : Icons.description);
},
trailingBuilder: (context, node, info) {
return Text(node.value?.statusLabel ?? '');
},
);
TreeRowInfo exposes depth, open, and hasChildren.
Scrolling #
There are two different controller roles:
controlleris the requiredSuperTreeController<T>for hierarchy state.scrollControlleris an optional FlutterScrollControllerfor the tree's internal verticalListView.
Let the tree own scrolling #
Give the tree bounded height through Expanded, Flexible, or SizedBox:
Expanded(
child: SuperTree<MyItem>(
controller: controller,
scrollController: scrollController,
physics: const BouncingScrollPhysics(),
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
restorationId: 'project-tree',
leadingBuilder: buildLeading,
),
);
The host owns and disposes an explicitly created ScrollController.
Let a parent own scrolling #
When SuperTree is inside another vertical scroll view, make ownership
explicit:
SingleChildScrollView(
child: SuperTree<MyItem>(
controller: controller,
shrinkWrap: true,
primary: false,
physics: const NeverScrollableScrollPhysics(),
leadingBuilder: buildLeading,
),
);
SuperTree also falls back to shrink-wrapping when it receives unbounded
vertical constraints.
The 1.0.0 scroll pass-through API is:
| Parameter | Default |
|---|---|
reverse |
false |
scrollController |
null |
primary |
null |
physics |
null |
shrinkWrap |
false |
cacheExtent |
null |
semanticChildCount |
null |
dragStartBehavior |
DragStartBehavior.start |
keyboardDismissBehavior |
null → ScrollViewKeyboardDismissBehavior.manual |
restorationId |
null |
clipBehavior |
Clip.hardEdge |
hitTestBehavior |
HitTestBehavior.opaque |
Do not combine primary: true with an explicit scrollController.
Keyboard shortcuts #
The tree must have focus before tree-navigation shortcuts are handled.
| Shortcut | Action |
|---|---|
↑ / ↓ |
Move between visible rows. |
← / → |
Collapse or step out / expand or step in. Direction is RTL-aware. |
Home / End |
Jump to the first or last visible row. |
Enter |
Open a leaf or toggle a group. |
Space |
Toggle the focused checkbox in selection mode; otherwise activate the row. |
/ |
Calls onSearchRequested when provided. |
* |
Expand all groups. |
\ |
Collapse all groups. |
? |
Calls onShortcutsRequested when provided. |
| Right-click / long-press | Open the node context menu. |
When SuperTreeControls owns the search field, Esc clears the active search
while that field has focus.
Tree algorithms #
TreeLogic contains pure, widget-free hierarchy operations. Useful methods
include:
filterandcountMatchesfor search.flattenVisibleandparentOffor navigation.leafCount,leafCodes, andgroupCodesfor derived state.rollupfor generic numeric aggregation over leaves.findNode,mapNode,removeNode,insertChild, andinsertSiblingfor immutable edits.moveNodeandisWithinfor safe drag-and-drop transforms.
Examples #
The runnable example/ application demonstrates different payloads and
interaction models without adding those domains to the package API:
file_tree_demo.dart— editable file hierarchy.org_tree_demo.dart— organization hierarchy.permission_tree_demo.dart— single and multi checkbox selection.product_tree_demo.dart— bilingual product/catalog hierarchy.scroll_tree_demo.dart— bounded scrolling andScrollControllerusage.
Run the gallery from the package root:
cd example
flutter pub get
flutter run
Additional information #
Architecture #
The package keeps the reusable hierarchy engine under
lib/src/features/super_tree/:
domain/
entities/tree_node.dart
usecases/tree_logic.dart
presentation/
controllers/super_tree_controller.dart
widgets/...
Domain-specific payloads, datasets, summaries, filters, and composed pages
belong in the consuming application or example/lib/.
Public API documentation #
Public APIs use Dart documentation comments and the package enables the
public_member_api_docs lint. New exported classes, constructors, fields,
methods, typedefs, and top-level functions should be documented before release.
Issues and changes #
- Repository: https://github.com/GeniusSystems24/super_tree_field
- Issues: https://github.com/GeniusSystems24/super_tree_field/issues
- Release history: CHANGELOG.md
License #
This package is distributed under the terms in LICENSE.