diff --git a/super_editor/example/lib/demos/in_the_lab/feature_ios_native_context_menu.dart b/super_editor/example/lib/demos/in_the_lab/feature_ios_native_context_menu.dart index a6f8f96b7..a6f184019 100644 --- a/super_editor/example/lib/demos/in_the_lab/feature_ios_native_context_menu.dart +++ b/super_editor/example/lib/demos/in_the_lab/feature_ios_native_context_menu.dart @@ -123,6 +123,10 @@ class _NativeIosContextMenuFeatureDemoState extends State { _systemContextMenuController = SystemContextMenuController( onSystemHide: widget.onSystemHide, ); - _systemContextMenuController.show(widget.anchor); + widget.leaderLink.addListener(_onLeaderChanged); + onNextFrame((_) => _positionSystemMenu()); } @override void didUpdateWidget(IOSSystemContextMenu oldWidget) { super.didUpdateWidget(oldWidget); - if (widget.anchor != oldWidget.anchor) { - _systemContextMenuController.show(widget.anchor); + if (widget.leaderLink != oldWidget.leaderLink) { + oldWidget.leaderLink.removeListener(_onLeaderChanged); + widget.leaderLink.addListener(_onLeaderChanged); + onNextFrame((_) => _positionSystemMenu()); } } @override void dispose() { + widget.leaderLink.removeListener(_onLeaderChanged); _systemContextMenuController.dispose(); super.dispose(); } + void _onLeaderChanged() { + if (widget.leaderLink.offset == null || widget.leaderLink.leaderSize == null) { + return; + } + + onNextFrame((_) { + _positionSystemMenu(); + }); + } + + void _positionSystemMenu() { + _systemContextMenuController.show(widget.leaderLink.offset! & widget.leaderLink.leaderSize!); + } + @override Widget build(BuildContext context) { assert(IOSSystemContextMenu.isSupported(context)); diff --git a/super_editor/lib/src/super_textfield/ios/ios_textfield.dart b/super_editor/lib/src/super_textfield/ios/ios_textfield.dart index 60b43fdbe..35fb18afd 100644 --- a/super_editor/lib/src/super_textfield/ios/ios_textfield.dart +++ b/super_editor/lib/src/super_textfield/ios/ios_textfield.dart @@ -704,8 +704,8 @@ typedef IOSPopoverToolbarBuilder = Widget Function(BuildContext, IOSEditingOverl /// iOS is recent enough, otherwise builds [defaultIosPopoverToolbarBuilder]. Widget iOSSystemPopoverTextFieldToolbarWithFallback(BuildContext context, IOSEditingOverlayController controller) { if (IOSSystemContextMenu.isSupported(context)) { - return IOSSystemContextMenu( - anchor: controller.toolbarFocalPoint.offset! & controller.toolbarFocalPoint.leaderSize!, + return IOSSuperTextFieldSystemContextMenu( + controller: controller, ); } @@ -752,3 +752,75 @@ Widget defaultIosPopoverToolbarBuilder(BuildContext context, IOSEditingOverlayCo }, ); } + +class IOSSuperTextFieldSystemContextMenu extends StatefulWidget { + const IOSSuperTextFieldSystemContextMenu({ + super.key, + required this.controller, + }); + + final IOSEditingOverlayController controller; + + @override + State createState() => _IOSSuperTextFieldSystemContextMenuState(); +} + +class _IOSSuperTextFieldSystemContextMenuState extends State { + late final SystemContextMenuController _systemContextMenuController; + + @override + void initState() { + super.initState(); + _systemContextMenuController = SystemContextMenuController(); + widget.controller.addListener(_onControllerChanged); + onNextFrame((_) { + _positionSystemMenu(); + }); + } + + @override + void didUpdateWidget(covariant IOSSuperTextFieldSystemContextMenu oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller != oldWidget.controller) { + oldWidget.controller.removeListener(_onControllerChanged); + widget.controller.addListener(_onControllerChanged); + } + onNextFrame((_) { + _positionSystemMenu(); + }); + } + + @override + void dispose() { + widget.controller.removeListener(_onControllerChanged); + _systemContextMenuController.dispose(); + super.dispose(); + } + + void _onControllerChanged() { + onNextFrame((_) { + _positionSystemMenu(); + }); + } + + void _positionSystemMenu() { + // The size reported by the controller's toolbarFocalPoint is one frame behind. Query the information + // overlayController instead. + final topAnchor = widget.controller.overlayController.toolbarTopAnchor; + final bottomAnchor = widget.controller.overlayController.toolbarTopAnchor; + + if (topAnchor == null || bottomAnchor == null) { + // We don't expect the toolbar builder to be called without having the anchors + // defined. But, since these properties are nullable, we account for that. + return; + } + + _systemContextMenuController.show(Rect.fromLTRB(topAnchor.dx, topAnchor.dy, bottomAnchor.dx, bottomAnchor.dy)); + } + + @override + Widget build(BuildContext context) { + assert(IOSSystemContextMenu.isSupported(context)); + return const SizedBox.shrink(); + } +}