mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[web] API to customize semantics placeholder message (#178309)
To customize the accessibility placeholder message:
```dart
import 'dart:ui_web' as ui_web;
void main() {
ui_web.accessibilityPlaceholderMessage = 'My Custom Accessibility Message!';
// ....
}
```
Fixes https://github.com/flutter/flutter/issues/178172
This commit is contained in:
parent
d8a99e9a9a
commit
e4d2b8743f
@ -60,8 +60,8 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
static final EnginePlatformDispatcher _instance = EnginePlatformDispatcher();
|
||||
|
||||
@visibleForTesting
|
||||
final DomElement accessibilityPlaceholder = EngineSemantics.instance.semanticsHelper
|
||||
.prepareAccessibilityPlaceholder();
|
||||
DomElement get accessibilityPlaceholder =>
|
||||
EngineSemantics.instance.semanticsHelper.accessibilityPlaceholder;
|
||||
|
||||
PlatformConfiguration configuration = PlatformConfiguration(
|
||||
locales: parseBrowserLanguages(),
|
||||
|
||||
@ -24,13 +24,6 @@ const int kMaxSemanticsActivationAttempts = 20;
|
||||
/// Otherwise these events can cause unintended gestures on the framework side.
|
||||
const Duration _periodToConsumeEvents = Duration(milliseconds: 300);
|
||||
|
||||
/// The message in the label for the placeholder element used to enable
|
||||
/// accessibility.
|
||||
///
|
||||
/// This uses US English as the default message. Set this value prior to
|
||||
/// calling `runApp` to translate to another language.
|
||||
String placeholderMessage = 'Enable accessibility';
|
||||
|
||||
/// A helper for [EngineSemanticsOwner].
|
||||
///
|
||||
/// [SemanticsHelper] prepares and placeholder to enable semantics.
|
||||
@ -56,8 +49,10 @@ class SemanticsHelper {
|
||||
return _semanticsEnabler.shouldEnableSemantics(event);
|
||||
}
|
||||
|
||||
DomElement prepareAccessibilityPlaceholder() {
|
||||
return _semanticsEnabler.prepareAccessibilityPlaceholder();
|
||||
DomElement get accessibilityPlaceholder => _semanticsEnabler.accessibilityPlaceholder;
|
||||
|
||||
void updatePlaceholderLabel(String message) {
|
||||
_semanticsEnabler.updatePlaceholderLabel(message);
|
||||
}
|
||||
|
||||
/// Stops waiting for the user to enable semantics and removes the
|
||||
@ -103,13 +98,16 @@ abstract class SemanticsEnabler {
|
||||
/// should be forwarded to the framework.
|
||||
bool tryEnableSemantics(DomEvent event);
|
||||
|
||||
/// Creates the placeholder for accessibility.
|
||||
///
|
||||
/// Puts it inside the glasspane.
|
||||
/// The placeholder element for enabling accessibility.
|
||||
///
|
||||
/// On focus the element announces that accessibility can be enabled by
|
||||
/// tapping/clicking. (Announcement depends on the assistive technology)
|
||||
DomElement prepareAccessibilityPlaceholder();
|
||||
late final DomElement accessibilityPlaceholder = _prepareAccessibilityPlaceholder();
|
||||
|
||||
DomElement _prepareAccessibilityPlaceholder();
|
||||
|
||||
/// Updates the placeholder's label to the given [message].
|
||||
void updatePlaceholderLabel(String message);
|
||||
|
||||
/// Whether platform is still considering enabling semantics.
|
||||
///
|
||||
@ -184,7 +182,7 @@ class DesktopSemanticsEnabler extends SemanticsEnabler {
|
||||
}
|
||||
|
||||
@override
|
||||
DomElement prepareAccessibilityPlaceholder() {
|
||||
DomElement _prepareAccessibilityPlaceholder() {
|
||||
final DomElement placeholder = _semanticsPlaceholder = createDomElement(
|
||||
'flt-semantics-placeholder',
|
||||
);
|
||||
@ -207,8 +205,9 @@ class DesktopSemanticsEnabler extends SemanticsEnabler {
|
||||
placeholder
|
||||
..setAttribute('role', 'button')
|
||||
..setAttribute('aria-live', 'polite')
|
||||
..setAttribute('tabindex', '0')
|
||||
..setAttribute('aria-label', placeholderMessage);
|
||||
..setAttribute('tabindex', '0');
|
||||
|
||||
updatePlaceholderLabel(ui_web.accessibilityPlaceholderMessage);
|
||||
|
||||
// The placeholder sits just outside the window so only AT can reach it.
|
||||
placeholder.style
|
||||
@ -220,6 +219,11 @@ class DesktopSemanticsEnabler extends SemanticsEnabler {
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
@override
|
||||
void updatePlaceholderLabel(String message) {
|
||||
_semanticsPlaceholder?.setAttribute('aria-label', message);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_semanticsPlaceholder?.remove();
|
||||
@ -383,7 +387,7 @@ class MobileSemanticsEnabler extends SemanticsEnabler {
|
||||
}
|
||||
|
||||
@override
|
||||
DomElement prepareAccessibilityPlaceholder() {
|
||||
DomElement _prepareAccessibilityPlaceholder() {
|
||||
final DomElement placeholder = _semanticsPlaceholder = createDomElement(
|
||||
'flt-semantics-placeholder',
|
||||
);
|
||||
@ -398,9 +402,8 @@ class MobileSemanticsEnabler extends SemanticsEnabler {
|
||||
true.toJS,
|
||||
);
|
||||
|
||||
placeholder
|
||||
..setAttribute('role', 'button')
|
||||
..setAttribute('aria-label', placeholderMessage);
|
||||
placeholder.setAttribute('role', 'button');
|
||||
updatePlaceholderLabel(ui_web.accessibilityPlaceholderMessage);
|
||||
placeholder.style
|
||||
..position = 'absolute'
|
||||
..left = '0'
|
||||
@ -411,6 +414,11 @@ class MobileSemanticsEnabler extends SemanticsEnabler {
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
@override
|
||||
void updatePlaceholderLabel(String message) {
|
||||
_semanticsPlaceholder?.setAttribute('aria-label', message);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_semanticsPlaceholder?.remove();
|
||||
|
||||
@ -18,4 +18,5 @@ export 'ui_web/navigation/platform_location.dart';
|
||||
export 'ui_web/navigation/url_strategy.dart';
|
||||
export 'ui_web/platform_view_registry.dart';
|
||||
export 'ui_web/plugins.dart';
|
||||
export 'ui_web/semantics.dart';
|
||||
export 'ui_web/testing.dart';
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:ui/src/engine.dart';
|
||||
|
||||
String _accessibilityPlaceholderMessage = 'Enable accessibility';
|
||||
|
||||
/// The message in the label for the placeholder element used to enable accessibility.
|
||||
///
|
||||
/// This uses US English as the default message. Set this value at any time to update the label
|
||||
/// in the placeholder element.
|
||||
String get accessibilityPlaceholderMessage => _accessibilityPlaceholderMessage;
|
||||
set accessibilityPlaceholderMessage(String message) {
|
||||
if (message == _accessibilityPlaceholderMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
_accessibilityPlaceholderMessage = message;
|
||||
EngineSemantics.instance.semanticsHelper.updatePlaceholderLabel(message);
|
||||
}
|
||||
@ -481,6 +481,17 @@ void testMain() {
|
||||
expect(dispatcher.accessibilityPlaceholder.isConnected, isFalse);
|
||||
});
|
||||
|
||||
test('accessibility placeholder label can be updated', () {
|
||||
final placeholder = domDocument.querySelector('flt-semantics-placeholder')!;
|
||||
|
||||
const String testLabel = 'Test accessibility label';
|
||||
ui_web.accessibilityPlaceholderMessage = testLabel;
|
||||
expect(placeholder.getAttribute('aria-label'), testLabel);
|
||||
|
||||
ui_web.accessibilityPlaceholderMessage = 'Enable accessibility';
|
||||
expect(placeholder.getAttribute('aria-label'), 'Enable accessibility');
|
||||
});
|
||||
|
||||
test('scheduleWarmupFrame should call both callbacks', () async {
|
||||
bool beginFrameCalled = false;
|
||||
final Completer<void> drawFrameCalled = Completer<void>();
|
||||
|
||||
@ -21,7 +21,7 @@ void testMain() {
|
||||
setUp(() {
|
||||
EngineSemantics.instance.semanticsEnabled = false;
|
||||
desktopSemanticsEnabler = DesktopSemanticsEnabler();
|
||||
placeholder = desktopSemanticsEnabler.prepareAccessibilityPlaceholder();
|
||||
placeholder = desktopSemanticsEnabler.accessibilityPlaceholder;
|
||||
domDocument.body!.append(placeholder!);
|
||||
});
|
||||
|
||||
@ -94,6 +94,16 @@ void testMain() {
|
||||
},
|
||||
);
|
||||
|
||||
test('Can update placeholder label', () {
|
||||
const String testLabel = 'Test label for placeholder';
|
||||
desktopSemanticsEnabler.updatePlaceholderLabel(testLabel);
|
||||
expect(placeholder!.getAttribute('aria-label'), testLabel);
|
||||
|
||||
const String anotherLabel = 'Another label for placeholder';
|
||||
desktopSemanticsEnabler.dispose();
|
||||
expect(() => desktopSemanticsEnabler.updatePlaceholderLabel(anotherLabel), returnsNormally);
|
||||
});
|
||||
|
||||
test('disposes of the placeholder', () {
|
||||
domDocument.body!.append(placeholder!);
|
||||
|
||||
@ -112,7 +122,7 @@ void testMain() {
|
||||
setUp(() {
|
||||
EngineSemantics.instance.semanticsEnabled = false;
|
||||
mobileSemanticsEnabler = MobileSemanticsEnabler();
|
||||
placeholder = mobileSemanticsEnabler.prepareAccessibilityPlaceholder();
|
||||
placeholder = mobileSemanticsEnabler.accessibilityPlaceholder;
|
||||
domDocument.body!.append(placeholder!);
|
||||
});
|
||||
|
||||
@ -140,6 +150,16 @@ void testMain() {
|
||||
expect(shouldForwardToFramework, isTrue);
|
||||
});
|
||||
|
||||
test('Can update placeholder label', () {
|
||||
const String testLabel = 'Test label for placeholder';
|
||||
mobileSemanticsEnabler.updatePlaceholderLabel(testLabel);
|
||||
expect(placeholder!.getAttribute('aria-label'), testLabel);
|
||||
|
||||
const String anotherLabel = 'Another label for placeholder';
|
||||
mobileSemanticsEnabler.dispose();
|
||||
expect(() => mobileSemanticsEnabler.updatePlaceholderLabel(anotherLabel), returnsNormally);
|
||||
});
|
||||
|
||||
test('Enables semantics when receiving a relevant event', () {
|
||||
expect(mobileSemanticsEnabler.semanticsActivationTimer, isNull);
|
||||
|
||||
@ -183,7 +203,7 @@ class FakeSemanticsEnabler extends SemanticsEnabler {
|
||||
bool get isWaitingToEnableSemantics => true;
|
||||
|
||||
@override
|
||||
DomElement prepareAccessibilityPlaceholder() {
|
||||
void updatePlaceholderLabel(String message) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
|
||||
@ -850,7 +850,10 @@ class MockSemanticsEnabler implements SemanticsEnabler {
|
||||
bool get isWaitingToEnableSemantics => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
DomElement prepareAccessibilityPlaceholder() {
|
||||
DomElement get accessibilityPlaceholder => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
void updatePlaceholderLabel(String message) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user