feat: add URL deep-linking for DataGrids
Build and Push / release (push) Successful in 3s
Build and Push / build (push) Successful in 3m5s

- Add PageUrlState utility for URL ↔ state serialization
- Add column `id` field for unique column identification in URLs
- Update idSelector to return String for URL compatibility
- All DataGrid pages now support URL params: search, sort, order, id
- Browser URL updates via replaceState (no GoRouter rebuilds)
- Add FilterPanelSemantics for filter panel semantic IDs
- Add TESTING.md documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jeroen Schweitzer
2026-01-05 13:59:08 +01:00
co-authored by Claude Opus 4.5
parent a6c51f757a
commit c494ace5d8
19 changed files with 952 additions and 28 deletions
@@ -45,6 +45,7 @@ enum DataGridColumnAlignment {
/// Column definition for DataGrid.
class DataGridColumn<T> {
const DataGridColumn({
required this.id,
required this.header,
required this.valueBuilder,
this.cellBuilder,
@@ -58,6 +59,9 @@ class DataGridColumn<T> {
this.tooltip,
});
/// Unique identifier for this column (used in URL deep-linking).
final String id;
/// Column header text.
final String header;
@@ -15,9 +15,15 @@ class DataGridController<T> extends StateNotifier<DataGridState<T>> {
required this.source,
required this.config,
required this.idSelector,
String? initialSearch,
int? initialSortColumnIndex,
bool? initialSortDescending,
}) : super(DataGridState<T>(
sortColumnIndex: config.defaultSortColumn,
sortDescending: config.defaultSortDescending,
searchQuery: initialSearch ?? '',
sortColumnIndex:
initialSortColumnIndex ?? config.defaultSortColumn,
sortDescending:
initialSortDescending ?? config.defaultSortDescending,
)) {
// Initial load
_load();
@@ -29,8 +35,13 @@ class DataGridController<T> extends StateNotifier<DataGridState<T>> {
/// Grid configuration.
final DataGridConfig<T> config;
/// Function to extract unique ID from an item.
final Object Function(T item) idSelector;
/// Function to extract unique ID from an item (used for selection and URL deep-linking).
///
/// Returns a unique string identifier for each row. Can be:
/// - A simple ID field: `(item) => item.id.toString()`
/// - A combination of fields: `(item) => '${item.name}_${item.type}'`
/// - Any unique identifier suitable for URLs
final String Function(T item) idSelector;
/// Debounce timer for search.
Timer? _searchDebounce;
@@ -185,7 +196,7 @@ class DataGridController<T> extends StateNotifier<DataGridState<T>> {
if (!config.rowsSelectable) return;
final id = idSelector(item);
final newSelection = Set<Object>.from(state.selectedIds);
final newSelection = Set<String>.from(state.selectedIds);
if (newSelection.contains(id)) {
newSelection.remove(id);
@@ -287,17 +298,35 @@ class DataGridController<T> extends StateNotifier<DataGridState<T>> {
/// idSelector: (c) => c.id,
/// );
/// ```
///
/// For URL deep-linking, pass initial state from URL params:
/// ```dart
/// final gridProvider = dataGridProvider<Container>(
/// source: source,
/// config: config,
/// idSelector: (c) => c.id,
/// initialSearch: urlState.search,
/// initialSortColumnIndex: _columnIndexForId(urlState.sortColumn),
/// initialSortDescending: urlState.sortDescending,
/// );
/// ```
StateNotifierProvider<DataGridController<T>, DataGridState<T>>
dataGridProvider<T>({
required DataGridSource<T> source,
required DataGridConfig<T> config,
required Object Function(T) idSelector,
required String Function(T) idSelector,
String? initialSearch,
int? initialSortColumnIndex,
bool? initialSortDescending,
}) {
return StateNotifierProvider<DataGridController<T>, DataGridState<T>>(
(ref) => DataGridController<T>(
source: source,
config: config,
idSelector: idSelector,
initialSearch: initialSearch,
initialSortColumnIndex: initialSortColumnIndex,
initialSortDescending: initialSortDescending,
),
);
}
@@ -31,7 +31,7 @@ sealed class DataGridState<T> with _$DataGridState<T> {
@Default(false) bool sortDescending,
/// Currently selected item IDs (if selectable).
@Default({}) Set<Object> selectedIds,
@Default({}) Set<String> selectedIds,
/// Current page (for paginated mode).
@Default(0) int currentPage,