Skip to content

OverlayPortal doesn't interleave overlay in the widget semantics #134456

@marcianx

Description

@marcianx

Is there an existing issue for this?

Steps to reproduce

This is toward making non-modal floating widgets accessible. Use-cases:

  1. A widget opens a tooltip with focusable content within it. I'd like screen reader navigation to end up within the content.
  2. A math widget opens a custom floating on-screen keyboard. I'd like the screen reader's virtual cursor to navigate directly from the widget onto this on-screen keyboard.

For both of these, the SemanticsNode tree needs to interleave the overlay right after the widget on which it's anchored.

OverlayPortal seems perfect for this and does automatically set the focus traversal order correctly, but it seems to not order the semantics tree to match. Here's a repro on DartPad (see Console for semantics output).

Expected semantics order: "button1", "floating-pause", "button2"

Observed semantics order: "floating-pause", "button1", "button2"

Code sample

Code sample

https://dartpad.dev/?id=6d2c070bd7321ce52c697d6085a32365

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() async {
  runApp(
    MaterialApp(
      theme: ThemeData(useMaterial3: true),
      title: 'Test',
      home: const Scaffold(
        body: MyWidget(),
      ),
    ),
  );

  SemanticsBinding.instance.ensureSemantics();
  WidgetsBinding.instance.addPostFrameCallback((_) {
    debugDumpSemanticsTree();
  });
}

class MyWidget extends StatefulWidget {
  const MyWidget();

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  final OverlayPortalController _controller = OverlayPortalController();

  @override
  Widget build(BuildContext context) {
    _controller.show();
    return FocusTraversalGroup(
      // Necessary to ensure that floating-pause is between button1 and button2
      // in the focus traversal order. Does not help semantics order one bit.
      policy: WidgetOrderTraversalPolicy(),
      child: Row(
        children: [
          OverlayPortal(
            controller: _controller,
            overlayChildBuilder: (context) => Positioned(
                left: 100,
                top: 20,
                child: button(Icons.pause, "floating-pause")),
            child: button(Icons.play_arrow, "button1"),
          ),
          button(Icons.stop, "button2"),
        ],
      ),
    );
  }
}

Widget button(IconData icon, String label, [VoidCallback? onPressed]) {
  return IconButton(
    icon: Semantics(
      label: label,
      child: Icon(icon),
    ),
    onPressed: onPressed ?? () {},
  );
}

Metadata

Metadata

Labels

P1High-priority issues at the top of the work listcustomer: samehereframeworkflutter/packages/flutter repository. See also f: labels.team-frameworkOwned by Framework team

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions