From 9ca449b951d2aec092802e7071513856bcee6e87 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Mon, 2 Feb 2026 11:49:23 +0100 Subject: [PATCH 1/6] fix: enable parent notification for CircleMarker updates --- .../flet-map/src/flutter/flet_map/lib/src/circle_layer.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/circle_layer.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/circle_layer.dart index 8f138df69f..be2fbfd009 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/circle_layer.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/circle_layer.dart @@ -18,6 +18,7 @@ class CircleLayerControl extends StatelessWidget with FletStoreMixin { .children("circles") .where((c) => c.type == "CircleMarker") .map((circle) { + circle.notifyParent = true; return CircleMarker( point: parseLatLng(circle.get("coordinates"))!, color: circle.getColor("color", context, const Color(0xFF00FF00))!, From 85d6311931ac26a926e665182c47e8494762b741 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Mon, 2 Feb 2026 11:49:36 +0100 Subject: [PATCH 2/6] fix: enable parent notification for Marker updates --- .../flet-map/src/flutter/flet_map/lib/src/marker_layer.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/marker_layer.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/marker_layer.dart index 32b3bafa60..cc8c19dafb 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/marker_layer.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/marker_layer.dart @@ -17,6 +17,7 @@ class MarkerLayerControl extends StatelessWidget with FletStoreMixin { .children("markers") .where((c) => c.type == "Marker") .map((marker) { + marker.notifyParent = true; return AnimatedMarker( point: parseLatLng(marker.get("coordinates"))!, rotate: marker.getBool("rotate"), From ce5907be525f9367df76bd8013ff7ec8d0465c84 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Mon, 2 Feb 2026 11:49:46 +0100 Subject: [PATCH 3/6] fix: enable parent notification for PolygonMarker updates --- .../flet-map/src/flutter/flet_map/lib/src/polygon_layer.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polygon_layer.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polygon_layer.dart index db8f310073..cb8eb9c069 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polygon_layer.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polygon_layer.dart @@ -17,6 +17,7 @@ class PolygonLayerControl extends StatelessWidget with FletStoreMixin { .children("polygons") .where((c) => c.type == "PolygonMarker") .map((polygon) { + polygon.notifyParent = true; return Polygon( borderStrokeWidth: polygon.getDouble("border_stroke_width", 0)!, borderColor: polygon.getColor("border_color", context, Colors.green)!, From cb950c848bd6459e2505e15eafb0b4cea10d6608 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Mon, 2 Feb 2026 11:49:54 +0100 Subject: [PATCH 4/6] fix: enable parent notification for PolylineMarker updates --- .../flet-map/src/flutter/flet_map/lib/src/polyline_layer.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polyline_layer.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polyline_layer.dart index 4a6885b535..c604c31a12 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polyline_layer.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polyline_layer.dart @@ -17,6 +17,7 @@ class PolylineLayerControl extends StatelessWidget with FletStoreMixin { .children("polylines") .where((c) => c.type == "PolylineMarker") .map((polyline) { + polyline.notifyParent = true; return Polyline( borderStrokeWidth: polyline.getDouble("border_stroke_width", 0)!, borderColor: From 597554d904f1b230eebde7d2528462d4b817dbba Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Mon, 2 Feb 2026 12:41:47 +0100 Subject: [PATCH 5/6] fix: enable parent notification for various control items --- .../lib/src/controls/cupertino_navigation_bar.dart | 1 + packages/flet/lib/src/controls/dropdown.dart | 1 + packages/flet/lib/src/controls/dropdownm2.dart | 1 + packages/flet/lib/src/controls/segmented_button.dart | 1 + .../src/flutter/flet_charts/lib/src/utils/charts.dart | 11 ++++++++--- .../flutter/flet_map/lib/src/rich_attribution.dart | 1 + 6 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/flet/lib/src/controls/cupertino_navigation_bar.dart b/packages/flet/lib/src/controls/cupertino_navigation_bar.dart index f3f549c83a..008c8a776f 100644 --- a/packages/flet/lib/src/controls/cupertino_navigation_bar.dart +++ b/packages/flet/lib/src/controls/cupertino_navigation_bar.dart @@ -51,6 +51,7 @@ class _CupertinoNavigationBarControlState border: widget.control.getBorder("border", Theme.of(context)), onTap: widget.control.disabled ? null : _onTap, items: widget.control.children("destinations").map((dest) { + dest.notifyParent = true; return BottomNavigationBarItem( tooltip: !dest.disabled ? dest.getString("tooltip") : null, backgroundColor: dest.getColor("bgcolor", context), diff --git a/packages/flet/lib/src/controls/dropdown.dart b/packages/flet/lib/src/controls/dropdown.dart index abaa6511ac..88fe62e08a 100644 --- a/packages/flet/lib/src/controls/dropdown.dart +++ b/packages/flet/lib/src/controls/dropdown.dart @@ -210,6 +210,7 @@ class _DropdownControlState extends State { var options = widget.control .children("options") .map?>((Control itemCtrl) { + itemCtrl.notifyParent = true; bool itemDisabled = widget.control.disabled || itemCtrl.disabled; ButtonStyle? style = itemCtrl.getButtonStyle("style", theme); diff --git a/packages/flet/lib/src/controls/dropdownm2.dart b/packages/flet/lib/src/controls/dropdownm2.dart index 718d41997a..b1437a9b3a 100644 --- a/packages/flet/lib/src/controls/dropdownm2.dart +++ b/packages/flet/lib/src/controls/dropdownm2.dart @@ -92,6 +92,7 @@ class _DropdownM2ControlState extends State { var items = widget.control .children("options") .map>((Control item) { + item.notifyParent = true; var textStyle = item.getTextStyle("text_style", Theme.of(context)); if (item.disabled && textStyle != null) { textStyle = textStyle.apply(color: Theme.of(context).disabledColor); diff --git a/packages/flet/lib/src/controls/segmented_button.dart b/packages/flet/lib/src/controls/segmented_button.dart index b4bf29d572..570eed51ac 100644 --- a/packages/flet/lib/src/controls/segmented_button.dart +++ b/packages/flet/lib/src/controls/segmented_button.dart @@ -91,6 +91,7 @@ class _SegmentedButtonControlState extends State direction: widget.control.getAxis("direction", Axis.horizontal)!, expandedInsets: widget.control.getPadding("padding"), segments: segments.map((segment) { + segment.notifyParent = true; return ButtonSegment( value: segment.getString("value")!, enabled: !segment.disabled, diff --git a/sdk/python/packages/flet-charts/src/flutter/flet_charts/lib/src/utils/charts.dart b/sdk/python/packages/flet-charts/src/flutter/flet_charts/lib/src/utils/charts.dart index 32595e0783..e7dd69b926 100644 --- a/sdk/python/packages/flet-charts/src/flutter/flet_charts/lib/src/utils/charts.dart +++ b/sdk/python/packages/flet-charts/src/flutter/flet_charts/lib/src/utils/charts.dart @@ -138,6 +138,12 @@ AxisTitles parseAxisTitles(Control? control) { return const AxisTitles(sideTitles: SideTitles(showTitles: false)); } + control.notifyParent = true; + final labels = control.children("labels"); + for (final label in labels) { + label.notifyParent = true; + } + return AxisTitles( axisNameWidget: control.buildWidget("title"), axisNameSize: control.getDouble("title_size", 16)!, @@ -147,11 +153,10 @@ AxisTitles parseAxisTitles(Control? control) { interval: control.getDouble("label_spacing"), minIncluded: control.getBool("show_min", true)!, maxIncluded: control.getBool("show_max", true)!, - getTitlesWidget: control.children("labels").isEmpty + getTitlesWidget: labels.isEmpty ? defaultGetTitle : (double value, TitleMeta meta) { - var label = control - .children("labels") + var label = labels .firstWhereOrNull((l) => l.getDouble("value") == value); return label?.buildTextOrWidget("label") ?? const SizedBox.shrink(); diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/rich_attribution.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/rich_attribution.dart index 2cfc87d473..18bd4be9e8 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/rich_attribution.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/rich_attribution.dart @@ -22,6 +22,7 @@ class _RichAttributionControlState extends State var attributions = widget.control .children("attributions") .map((Control c) { + c.notifyParent = true; if (c.type == "TextSourceAttribution") { return TextSourceAttribution( c.getString("text", "Placeholder Text")!, From 28458ce74e4eb279af07c602950f909340662750 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Tue, 3 Feb 2026 22:07:52 +0100 Subject: [PATCH 6/6] improve Webview --- packages/flet/lib/src/models/control.dart | 19 +++- .../flet-webview/src/flet_webview/webview.py | 2 +- .../flet_webview/lib/src/utils/webview.dart | 12 +-- .../lib/src/webview_mobile_and_mac.dart | 92 +++++++++++++------ .../src/flutter/flet_webview/pubspec.yaml | 2 +- 5 files changed, 86 insertions(+), 41 deletions(-) diff --git a/packages/flet/lib/src/models/control.dart b/packages/flet/lib/src/models/control.dart index 6c707ba483..f63b3b4f40 100644 --- a/packages/flet/lib/src/models/control.dart +++ b/packages/flet/lib/src/models/control.dart @@ -149,6 +149,16 @@ class Control extends ChangeNotifier { return backend.triggerControlEvent(this, eventName, data); } + /// Whether this control currently has a handler subscribed to [eventName]. + /// + /// Accepts both `"event_name"` and `"on_event_name"` forms. + /// Internally this checks the boolean `on_` property. + bool hasEventHandler(String eventName) { + final String propName = + eventName.startsWith("on_") ? eventName : "on_$eventName"; + return get(propName, false)!; + } + /// Triggers a control event without checking for subscribers. /// /// This method directly triggers the event for the control identified by its @@ -374,7 +384,8 @@ class Control extends ChangeNotifier { if (node.obj is Map) { node.obj[index] = _transformIfControl(value, node.control, backend); } else if (node.obj is List) { - node.obj.insert(index, _transformIfControl(value, node.control, backend)); + node.obj + .insert(index, _transformIfControl(value, node.control, backend)); } else { throw Exception("Add operation can be applied to lists or maps: $op"); } @@ -388,7 +399,8 @@ class Control extends ChangeNotifier { } else if (node.obj is Map) { node.obj.remove(index); } else { - throw Exception("Remove operation can be applied to lists or maps: $op"); + throw Exception( + "Remove operation can be applied to lists or maps: $op"); } if (shouldNotify) node.control.notify(); } else if (opType == OperationType.move) { @@ -402,7 +414,8 @@ class Control extends ChangeNotifier { } else if (fromNode.obj is Map && toNode.obj is Map) { toNode.obj[toIndex] = fromNode.obj.remove(fromIndex); } else { - throw Exception("Move operation can only be applied to lists or maps: $op"); + throw Exception( + "Move operation can only be applied to lists or maps: $op"); } if (shouldNotify) { if (fromNode.control.id != toNode.control.id) { diff --git a/sdk/python/packages/flet-webview/src/flet_webview/webview.py b/sdk/python/packages/flet-webview/src/flet_webview/webview.py index 18bdb86557..ee03cd06c4 100644 --- a/sdk/python/packages/flet-webview/src/flet_webview/webview.py +++ b/sdk/python/packages/flet-webview/src/flet_webview/webview.py @@ -94,7 +94,7 @@ class WebView(ft.LayoutControl): Fires when the web page's scroll position changes. Note: - Works only on the following platforms: iOS, Android and macOS. + Works only on the following platforms: iOS and Android. """ on_console_message: Optional[ft.EventHandler[WebViewConsoleMessageEvent]] = None diff --git a/sdk/python/packages/flet-webview/src/flutter/flet_webview/lib/src/utils/webview.dart b/sdk/python/packages/flet-webview/src/flutter/flet_webview/lib/src/utils/webview.dart index 60919e2f5a..39ab466238 100644 --- a/sdk/python/packages/flet-webview/src/flutter/flet_webview/lib/src/utils/webview.dart +++ b/sdk/python/packages/flet-webview/src/flutter/flet_webview/lib/src/utils/webview.dart @@ -1,18 +1,12 @@ -import 'package:collection/collection.dart'; +import 'package:flet/flet.dart'; import 'package:webview_flutter/webview_flutter.dart'; LoadRequestMethod? parseLoadRequestMethod(String? value, [LoadRequestMethod? defaultValue]) { - if (value == null) return defaultValue; - return LoadRequestMethod.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defaultValue; + return parseEnum(LoadRequestMethod.values, value); } JavaScriptMode? parseJavaScriptMode(String? value, [JavaScriptMode? defaultValue]) { - if (value == null) return defaultValue; - return JavaScriptMode.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defaultValue; + return parseEnum(JavaScriptMode.values, value); } diff --git a/sdk/python/packages/flet-webview/src/flutter/flet_webview/lib/src/webview_mobile_and_mac.dart b/sdk/python/packages/flet-webview/src/flutter/flet_webview/lib/src/webview_mobile_and_mac.dart index 5378215db7..1044193f59 100644 --- a/sdk/python/packages/flet-webview/src/flutter/flet_webview/lib/src/webview_mobile_and_mac.dart +++ b/sdk/python/packages/flet-webview/src/flutter/flet_webview/lib/src/webview_mobile_and_mac.dart @@ -14,6 +14,65 @@ class WebviewMobileAndMac extends StatefulWidget { class _WebviewMobileAndMacState extends State { late WebViewController controller; + bool _scrollHandlerRegistered = false; + bool _consoleHandlerRegistered = false; + bool _alertHandlerRegistered = false; + + bool _shouldPreventNavigation(String url) { + final links = widget.control.get("prevent_links"); + if (links == null || links.isEmpty) return false; + return links.any((link) => link is String && url.startsWith(link)); + } + + void _setOptionalEventHandlers() { + // macOS currently does not surface scroll callbacks via webview_flutter. + if (!isMacOSDesktop() && + !_scrollHandlerRegistered && + widget.control.hasEventHandler("scroll")) { + try { + controller.setOnScrollPositionChange((ScrollPositionChange position) { + widget.control + .triggerEvent("scroll", {"x": position.x, "y": position.y}); + }); + _scrollHandlerRegistered = true; + } catch (e) { + debugPrint("WebView.on_scroll is not available on this platform: $e"); + } + } + + if (!_consoleHandlerRegistered && + widget.control.hasEventHandler("console_message")) { + try { + controller.setOnConsoleMessage((JavaScriptConsoleMessage message) { + widget.control.triggerEvent("console_message", { + "message": message.message, + "severity_level": message.level.name, + }); + }); + _consoleHandlerRegistered = true; + } catch (e) { + debugPrint( + "WebView.on_console_message is not available on this platform: $e"); + } + } + + if (!_alertHandlerRegistered && + widget.control.hasEventHandler("javascript_alert_dialog")) { + try { + controller.setOnJavaScriptAlertDialog( + (JavaScriptAlertDialogRequest request) async { + widget.control.triggerEvent( + "javascript_alert_dialog", + {"message": request.message, "url": request.url}, + ); + }); + _alertHandlerRegistered = true; + } catch (e) { + debugPrint( + "WebView.on_javascript_alert_dialog is not available on this platform: $e"); + } + } + } @override void initState() { @@ -41,11 +100,7 @@ class _WebviewMobileAndMacState extends State { widget.control.triggerEvent("web_resource_error", error.description); }, onNavigationRequest: (NavigationRequest request) { - var links = widget.control.get("prevent_link"); - var prevent = links is List && - links.isNotEmpty && - links.any((l) => request.url.startsWith(l)); - return prevent + return _shouldPreventNavigation(request.url) ? NavigationDecision.prevent : NavigationDecision.navigate; }, @@ -58,26 +113,7 @@ class _WebviewMobileAndMacState extends State { method: parseLoadRequestMethod( widget.control.getString("method"), LoadRequestMethod.get)!); - // scroll - if (!isMacOSDesktop()) { - controller.setOnScrollPositionChange((ScrollPositionChange position) { - widget.control - .triggerEvent("scroll", {"x": position.x, "y": position.y}); - }); - } - - // console - controller.setOnConsoleMessage((JavaScriptConsoleMessage message) { - widget.control.triggerEvent("console_message", - {"message": message.message, "level": message.level.name}); - }); - - // alert - controller.setOnJavaScriptAlertDialog( - (JavaScriptAlertDialogRequest request) async { - widget.control.triggerEvent("javascript_alert_dialog", - {"message": request.message, "url": request.url}); - }); + _setOptionalEventHandlers(); } Future _invokeMethod(String name, dynamic args) async { @@ -87,9 +123,9 @@ class _WebviewMobileAndMacState extends State { await controller.reload(); break; case "can_go_back": - return controller.canGoBack().toString(); + return controller.canGoBack(); case "can_go_forward": - return controller.canGoForward().toString(); + return controller.canGoForward(); case "go_back": if (await controller.canGoBack()) { await controller.goBack(); @@ -175,6 +211,8 @@ class _WebviewMobileAndMacState extends State { Widget build(BuildContext context) { debugPrint("WebViewControl build: ${widget.control.id}"); + _setOptionalEventHandlers(); + var bgcolor = widget.control.getColor("bgcolor", context); if (bgcolor != null) { diff --git a/sdk/python/packages/flet-webview/src/flutter/flet_webview/pubspec.yaml b/sdk/python/packages/flet-webview/src/flutter/flet_webview/pubspec.yaml index afe367d6e6..0e86b38b7c 100644 --- a/sdk/python/packages/flet-webview/src/flutter/flet_webview/pubspec.yaml +++ b/sdk/python/packages/flet-webview/src/flutter/flet_webview/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: sdk: flutter collection: ^1.16.0 - webview_flutter: ^4.13.0 + webview_flutter: ^4.13.1 webview_flutter_web: 0.2.3+4 webview_flutter_platform_interface: 2.14.0