- 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>
99 lines
2.8 KiB
Dart
99 lines
2.8 KiB
Dart
import 'package:flutter/foundation.dart' show kIsWeb;
|
|
|
|
// Conditional import for web-only functionality
|
|
import 'url_state_stub.dart' if (dart.library.js_interop) 'url_state_web.dart'
|
|
as platform;
|
|
|
|
/// Parses and builds URL query parameters for page state.
|
|
///
|
|
/// Used to enable deep-linking for DataGrid and filter panel state.
|
|
/// Query params are read once on page load and updated via browser
|
|
/// replaceState to avoid GoRouter rebuild loops.
|
|
class PageUrlState {
|
|
const PageUrlState({
|
|
this.id,
|
|
this.filter,
|
|
this.search,
|
|
this.sortColumn,
|
|
this.sortDescending = false,
|
|
});
|
|
|
|
/// Opened document ID (view/edit mode).
|
|
final String? id;
|
|
|
|
/// Filter panel search value.
|
|
final String? filter;
|
|
|
|
/// DataGrid search value.
|
|
final String? search;
|
|
|
|
/// Column ID for sorting.
|
|
final String? sortColumn;
|
|
|
|
/// Sort direction (true = descending).
|
|
final bool sortDescending;
|
|
|
|
/// Parse from query parameters map.
|
|
factory PageUrlState.fromQueryParams(Map<String, String> params) {
|
|
return PageUrlState(
|
|
id: params['id'],
|
|
filter: params['filter'],
|
|
search: params['search'],
|
|
sortColumn: params['sort'],
|
|
sortDescending: params['order'] == 'desc',
|
|
);
|
|
}
|
|
|
|
/// Convert to query parameter map (omits empty/default values).
|
|
Map<String, String> toQueryParams() {
|
|
return {
|
|
if (id != null && id!.isNotEmpty) 'id': id!,
|
|
if (filter != null && filter!.isNotEmpty) 'filter': filter!,
|
|
if (search != null && search!.isNotEmpty) 'search': search!,
|
|
if (sortColumn != null && sortColumn!.isNotEmpty) 'sort': sortColumn!,
|
|
if (sortDescending) 'order': 'desc',
|
|
};
|
|
}
|
|
|
|
/// Create a copy with modified values.
|
|
PageUrlState copyWith({
|
|
String? id,
|
|
String? filter,
|
|
String? search,
|
|
String? sortColumn,
|
|
bool? sortDescending,
|
|
bool clearId = false,
|
|
}) {
|
|
return PageUrlState(
|
|
id: clearId ? null : (id ?? this.id),
|
|
filter: filter ?? this.filter,
|
|
search: search ?? this.search,
|
|
sortColumn: sortColumn ?? this.sortColumn,
|
|
sortDescending: sortDescending ?? this.sortDescending,
|
|
);
|
|
}
|
|
|
|
/// Whether any state is present.
|
|
bool get isEmpty =>
|
|
id == null &&
|
|
(filter == null || filter!.isEmpty) &&
|
|
(search == null || search!.isEmpty) &&
|
|
sortColumn == null;
|
|
|
|
@override
|
|
String toString() =>
|
|
'PageUrlState(id: $id, filter: $filter, search: $search, '
|
|
'sort: $sortColumn, desc: $sortDescending)';
|
|
}
|
|
|
|
/// Updates browser URL with query parameters without triggering navigation.
|
|
///
|
|
/// Uses browser's replaceState API on web, no-op on other platforms.
|
|
/// This allows the URL to stay in sync for bookmarking/sharing without
|
|
/// causing Flutter to rebuild.
|
|
void updateBrowserUrlParams(Map<String, String> params) {
|
|
if (kIsWeb) {
|
|
platform.updateBrowserUrlParams(params);
|
|
}
|
|
}
|