diff --git a/packages/pointer_interceptor/pointer_interceptor_web/CHANGELOG.md b/packages/pointer_interceptor/pointer_interceptor_web/CHANGELOG.md index f8b01ae32a54..2b4eaa81e07b 100644 --- a/packages/pointer_interceptor/pointer_interceptor_web/CHANGELOG.md +++ b/packages/pointer_interceptor/pointer_interceptor_web/CHANGELOG.md @@ -1,4 +1,9 @@ +## 0.10.1 + +* Uses `HtmlElementView.fromTagName` instead of custom factories. +* Migrates package and tests to `platform:web`. +* Updates minimum supported SDK version to Flutter 3.16.0/Dart 3.2.0. + ## 0.10.0 * Moves web implementation to its own package. - diff --git a/packages/pointer_interceptor/pointer_interceptor_web/example/integration_test/widget_test.dart b/packages/pointer_interceptor/pointer_interceptor_web/example/integration_test/widget_test.dart index 22a2528f7d15..d712f6fbfa00 100644 --- a/packages/pointer_interceptor/pointer_interceptor_web/example/integration_test/widget_test.dart +++ b/packages/pointer_interceptor/pointer_interceptor_web/example/integration_test/widget_test.dart @@ -4,22 +4,19 @@ // ignore_for_file: avoid_print -import 'dart:html' as html; - // Imports the Flutter Driver API. import 'package:flutter/src/widgets/framework.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; - import 'package:pointer_interceptor_web_example/main.dart' as app; +import 'package:web/web.dart' as web; final Finder nonClickableButtonFinder = find.byKey(const Key('transparent-button')); final Finder clickableWrappedButtonFinder = find.byKey(const Key('wrapped-transparent-button')); final Finder clickableButtonFinder = find.byKey(const Key('clickable-button')); -final Finder backgroundFinder = - find.byKey(const ValueKey('background-widget')); +final Finder backgroundFinder = find.byKey(const Key('background-widget')); void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -28,10 +25,9 @@ void main() { testWidgets( 'on wrapped elements, the browser does not hit the background-html-view', (WidgetTester tester) async { - app.main(); - await tester.pumpAndSettle(); + await _fullyRenderApp(tester); - final html.Element element = + final web.Element element = _getHtmlElementAtCenter(clickableButtonFinder, tester); expect(element.id, isNot('background-html-view')); @@ -40,10 +36,9 @@ void main() { testWidgets( 'on wrapped elements with intercepting set to false, the browser hits the background-html-view', (WidgetTester tester) async { - app.main(); - await tester.pumpAndSettle(); + await _fullyRenderApp(tester); - final html.Element element = + final web.Element element = _getHtmlElementAtCenter(clickableWrappedButtonFinder, tester); expect(element.id, 'background-html-view'); @@ -52,20 +47,18 @@ void main() { testWidgets( 'on unwrapped elements, the browser hits the background-html-view', (WidgetTester tester) async { - app.main(); - await tester.pumpAndSettle(); + await _fullyRenderApp(tester); - final html.Element element = + final web.Element element = _getHtmlElementAtCenter(nonClickableButtonFinder, tester); expect(element.id, 'background-html-view'); }, semanticsEnabled: false); testWidgets('on background directly', (WidgetTester tester) async { - app.main(); - await tester.pumpAndSettle(); + await _fullyRenderApp(tester); - final html.Element element = + final web.Element element = _getHtmlElementAt(tester.getTopLeft(backgroundFinder)); expect(element.id, 'background-html-view'); @@ -75,15 +68,9 @@ void main() { group('With semantics', () { testWidgets('finds semantics of wrapped widgets', (WidgetTester tester) async { - app.main(); - await tester.pumpAndSettle(); + await _fullyRenderApp(tester); - if (!_newSemanticsAvailable()) { - print('Skipping test: Needs flutter > 2.10'); - return; - } - - final html.Element element = + final web.Element element = _getHtmlElementAtCenter(clickableButtonFinder, tester); expect(element.tagName.toLowerCase(), 'flt-semantics'); @@ -93,15 +80,9 @@ void main() { testWidgets( 'finds semantics of wrapped widgets with intercepting set to false', (WidgetTester tester) async { - app.main(); - await tester.pumpAndSettle(); - - if (!_newSemanticsAvailable()) { - print('Skipping test: Needs flutter > 2.10'); - return; - } + await _fullyRenderApp(tester); - final html.Element element = + final web.Element element = _getHtmlElementAtCenter(clickableWrappedButtonFinder, tester); expect(element.tagName.toLowerCase(), 'flt-semantics'); @@ -111,15 +92,9 @@ void main() { testWidgets('finds semantics of unwrapped elements', (WidgetTester tester) async { - app.main(); - await tester.pumpAndSettle(); + await _fullyRenderApp(tester); - if (!_newSemanticsAvailable()) { - print('Skipping test: Needs flutter > 2.10'); - return; - } - - final html.Element element = + final web.Element element = _getHtmlElementAtCenter(nonClickableButtonFinder, tester); expect(element.tagName.toLowerCase(), 'flt-semantics'); @@ -134,10 +109,9 @@ void main() { // simply allows the hit test to land on the platform view by making itself // hit test transparent. testWidgets('on background directly', (WidgetTester tester) async { - app.main(); - await tester.pumpAndSettle(); + await _fullyRenderApp(tester); - final html.Element element = + final web.Element element = _getHtmlElementAt(tester.getTopLeft(backgroundFinder)); expect(element.id, 'background-html-view'); @@ -145,9 +119,16 @@ void main() { }); } +Future _fullyRenderApp(WidgetTester tester) async { + await tester.pumpWidget(const app.MyApp()); + // Pump 2 frames so the framework injects the platform view into the DOM. + await tester.pump(); + await tester.pump(); +} + // Calls [_getHtmlElementAt] passing it the center of the widget identified by // the `finder`. -html.Element _getHtmlElementAtCenter(Finder finder, WidgetTester tester) { +web.Element _getHtmlElementAtCenter(Finder finder, WidgetTester tester) { final Offset point = tester.getCenter(finder); return _getHtmlElementAt(point); } @@ -158,22 +139,20 @@ html.Element _getHtmlElementAtCenter(Finder finder, WidgetTester tester) { // sensitive to the presence of shadow roots and browser quirks (not all // browsers agree on what it should return in all situations). Since this test // runs only in Chromium, it relies on Chromium's behavior. -html.Element _getHtmlElementAt(Offset point) { +web.Element _getHtmlElementAt(Offset point) { // Probe at the shadow so the browser reports semantics nodes in addition to // platform view elements. If probed from `html.document` the browser hides // the contents of as an implementation detail. - final html.ShadowRoot glassPaneShadow = - html.document.querySelector('flt-glass-pane')!.shadowRoot!; - return glassPaneShadow.elementFromPoint(point.dx.toInt(), point.dy.toInt())!; + final web.ShadowRoot glassPaneShadow = + web.document.querySelector('flt-glass-pane')!.shadowRoot!; + // Use `round` below to ensure clicks always fall *inside* the located + // element, rather than truncating the decimals. + // Truncating decimals makes some tests fail when a centered element (in high + // DPI) is not exactly aligned to the pixel grid (because the browser *rounds*) + return glassPaneShadow.elementFromPoint(point.dx.round(), point.dy.round()); } -// TODO(dit): Remove this after flutter master (2.13) lands into stable. -// This detects that we can do new semantics assertions by looking at the 'id' -// attribute on flt-semantics elements (it is now set in 2.13 and up). -bool _newSemanticsAvailable() { - final html.ShadowRoot glassPaneShadow = - html.document.querySelector('flt-glass-pane')!.shadowRoot!; - final List elements = - glassPaneShadow.querySelectorAll('flt-semantics[id]'); - return elements.isNotEmpty; +/// Shady API: https://github.com/w3c/csswg-drafts/issues/556 +extension ElementFromPointInShadowRoot on web.ShadowRoot { + external web.Element elementFromPoint(int x, int y); } diff --git a/packages/pointer_interceptor/pointer_interceptor_web/example/lib/main.dart b/packages/pointer_interceptor/pointer_interceptor_web/example/lib/main.dart index 5a3723a011d0..d25e8913f419 100644 --- a/packages/pointer_interceptor/pointer_interceptor_web/example/lib/main.dart +++ b/packages/pointer_interceptor/pointer_interceptor_web/example/lib/main.dart @@ -2,39 +2,44 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore: avoid_web_libraries_in_flutter -import 'dart:html' as html; +import 'dart:js_interop'; import 'dart:ui_web' as ui_web; import 'package:flutter/material.dart'; import 'package:pointer_interceptor_platform_interface/pointer_interceptor_platform_interface.dart'; import 'package:pointer_interceptor_web/pointer_interceptor_web.dart'; +import 'package:web/web.dart' as web; const String _htmlElementViewType = '_htmlElementViewType'; -const double _videoWidth = 640; -const double _videoHeight = 480; +const double _containerWidth = 640; +const double _containerHeight = 480; /// The html.Element that will be rendered underneath the flutter UI. -html.Element htmlElement = html.DivElement() - ..style.width = '100%' - ..style.height = '100%' - ..style.backgroundColor = '#fabada' - ..style.cursor = 'auto' - ..id = 'background-html-view'; +final web.Element _htmlElement = + (web.document.createElement('div') as web.HTMLDivElement) + ..style.width = '100%' + ..style.height = '100%' + ..style.backgroundColor = '#fabada' + ..style.cursor = 'auto' + ..id = 'background-html-view'; // See other examples commented out below... -// html.Element htmlElement = html.VideoElement() -// ..style.width = '100%' -// ..style.height = '100%' -// ..style.cursor = 'auto' -// ..style.backgroundColor = 'black' -// ..id = 'background-html-view' -// ..src = 'https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4' -// ..poster = 'https://peach.blender.org/wp-content/uploads/title_anouncement.jpg?x11217' -// ..controls = true; - -// html.Element htmlElement = html.IFrameElement() +// final web.Element _htmlElement = +// (web.document.createElement('video') as web.HTMLVideoElement) +// ..style.width = '100%' +// ..style.height = '100%' +// ..style.cursor = 'auto' +// ..style.backgroundColor = 'black' +// ..id = 'background-html-view' +// ..src = +// 'https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4' +// ..poster = +// 'https://peach.blender.org/wp-content/uploads/title_anouncement.jpg?x11217' +// ..controls = true; + +// final web.Element _htmlElement = +// (web.document.createElement('video') as web.HTMLIFrameElement) // ..width = '100%' // ..height = '100%' // ..id = 'background-html-view' @@ -42,10 +47,6 @@ html.Element htmlElement = html.DivElement() // ..style.border = 'none'; void main() { - ui_web.platformViewRegistry.registerViewFactory( - _htmlElementViewType, - (int viewId) => htmlElement, - ); runApp(const MyApp()); } @@ -81,6 +82,15 @@ class _MyHomePageState extends State { }); } + @override + void initState() { + super.initState(); + ui_web.platformViewRegistry.registerViewFactory( + _htmlElementViewType, + (int viewId) => _htmlElement, + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -109,13 +119,13 @@ class _MyHomePageState extends State { ), Container( color: Colors.black, - width: _videoWidth, - height: _videoHeight, + width: _containerWidth, + height: _containerHeight, child: Stack( alignment: Alignment.center, children: [ HtmlElement( - key: const ValueKey('background-widget'), + key: const Key('background-widget'), onClick: () { _clickedOn('html-element'); }, @@ -207,9 +217,12 @@ class HtmlElement extends StatelessWidget { @override Widget build(BuildContext context) { - htmlElement.onClick.listen((_) { - onClick(); - }); + _htmlElement.addEventListener( + 'click', + (JSAny? _) { + onClick(); + }.toJS, + ); return const HtmlElementView( viewType: _htmlElementViewType, diff --git a/packages/pointer_interceptor/pointer_interceptor_web/example/pubspec.yaml b/packages/pointer_interceptor/pointer_interceptor_web/example/pubspec.yaml index e3f7b95066e7..2132641ca791 100644 --- a/packages/pointer_interceptor/pointer_interceptor_web/example/pubspec.yaml +++ b/packages/pointer_interceptor/pointer_interceptor_web/example/pubspec.yaml @@ -3,8 +3,8 @@ description: "Demonstrates how to use the pointer_interceptor_web plugin." publish_to: 'none' environment: - sdk: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + sdk: ^3.2.0 + flutter: '>=3.16.0' dependencies: cupertino_icons: ^1.0.2 @@ -13,6 +13,7 @@ dependencies: pointer_interceptor_platform_interface: ^0.10.0 pointer_interceptor_web: path: ../../pointer_interceptor_web + web: '>=0.3.0 <0.5.0' dev_dependencies: flutter_test: diff --git a/packages/pointer_interceptor/pointer_interceptor_web/example/web/icons/Icon-maskable-192.png b/packages/pointer_interceptor/pointer_interceptor_web/example/web/icons/Icon-maskable-192.png new file mode 100644 index 000000000000..eb9b4d76e525 Binary files /dev/null and b/packages/pointer_interceptor/pointer_interceptor_web/example/web/icons/Icon-maskable-192.png differ diff --git a/packages/pointer_interceptor/pointer_interceptor_web/example/web/icons/Icon-maskable-512.png b/packages/pointer_interceptor/pointer_interceptor_web/example/web/icons/Icon-maskable-512.png new file mode 100644 index 000000000000..d69c56691fbd Binary files /dev/null and b/packages/pointer_interceptor/pointer_interceptor_web/example/web/icons/Icon-maskable-512.png differ diff --git a/packages/pointer_interceptor/pointer_interceptor_web/example/web/index.html b/packages/pointer_interceptor/pointer_interceptor_web/example/web/index.html index a53f5677b573..2720a1eaad86 100644 --- a/packages/pointer_interceptor/pointer_interceptor_web/example/web/index.html +++ b/packages/pointer_interceptor/pointer_interceptor_web/example/web/index.html @@ -11,10 +11,13 @@ The path provided below has to start and end with a slash "/" in order for it to work correctly. - Fore more details: + For more details: * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base + + This is a placeholder for base href that will be replaced by the value of + the `--base-href` argument provided to `flutter build`. --> - + @@ -31,18 +34,21 @@ example + + + - - diff --git a/packages/pointer_interceptor/pointer_interceptor_web/example/web/manifest.json b/packages/pointer_interceptor/pointer_interceptor_web/example/web/manifest.json index 8c012917dab7..096edf8fe4cd 100644 --- a/packages/pointer_interceptor/pointer_interceptor_web/example/web/manifest.json +++ b/packages/pointer_interceptor/pointer_interceptor_web/example/web/manifest.json @@ -18,6 +18,18 @@ "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" } ] } diff --git a/packages/pointer_interceptor/pointer_interceptor_web/lib/pointer_interceptor_web.dart b/packages/pointer_interceptor/pointer_interceptor_web/lib/pointer_interceptor_web.dart index 33c705fc3f30..2ae4bf5f505b 100644 --- a/packages/pointer_interceptor/pointer_interceptor_web/lib/pointer_interceptor_web.dart +++ b/packages/pointer_interceptor/pointer_interceptor_web/lib/pointer_interceptor_web.dart @@ -2,55 +2,23 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore: avoid_web_libraries_in_flutter -import 'dart:html' as html; -import 'dart:ui_web' as ui_web; - import 'package:flutter/widgets.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; - import 'package:pointer_interceptor_platform_interface/pointer_interceptor_platform_interface.dart'; - -const String _viewType = '__webPointerInterceptorViewType__'; -const String _debug = 'debug__'; - -// Computes a "view type" for different configurations of the widget. -String _getViewType({bool debug = false}) { - return debug ? _viewType + _debug : _viewType; -} - -// Registers a viewFactory for this widget. -void _registerFactory({bool debug = false}) { - final String viewType = _getViewType(debug: debug); - ui_web.platformViewRegistry.registerViewFactory(viewType, (int viewId) { - final html.Element htmlElement = html.DivElement() - ..style.width = '100%' - ..style.height = '100%'; - if (debug) { - htmlElement.style.backgroundColor = 'rgba(255, 0, 0, .5)'; - } - return htmlElement; - }, isVisible: false); -} +import 'package:web/web.dart' as web; /// The web implementation of the `PointerInterceptor` widget. /// /// A `Widget` that prevents clicks from being swallowed by [HtmlElementView]s. class PointerInterceptorWeb extends PointerInterceptorPlatform { - static bool _registered = false; - /// Register the plugin static void registerWith(Registrar? registrar) { PointerInterceptorPlatform.instance = PointerInterceptorWeb(); } - static void _register() { - assert(!_registered); - - _registerFactory(); - _registerFactory(debug: true); - - _registered = true; + // Slightly modify the created `element` (for `debug` mode). + void _onElementCreated(Object element) { + (element as web.HTMLElement).style.backgroundColor = 'rgba(255, 0, 0, .5)'; } @override @@ -60,11 +28,6 @@ class PointerInterceptorWeb extends PointerInterceptorPlatform { bool intercepting = true, Key? key, }) { - final String viewType = _getViewType(debug: debug); - - if (!_registered) { - _register(); - } if (!intercepting) { return child; } @@ -72,8 +35,10 @@ class PointerInterceptorWeb extends PointerInterceptorPlatform { alignment: Alignment.center, children: [ Positioned.fill( - child: HtmlElementView( - viewType: viewType, + child: HtmlElementView.fromTagName( + tagName: 'div', + isVisible: false, + onElementCreated: debug ? _onElementCreated : null, ), ), child, diff --git a/packages/pointer_interceptor/pointer_interceptor_web/pubspec.yaml b/packages/pointer_interceptor/pointer_interceptor_web/pubspec.yaml index cebf7f9daabd..67dcf2585cba 100644 --- a/packages/pointer_interceptor/pointer_interceptor_web/pubspec.yaml +++ b/packages/pointer_interceptor/pointer_interceptor_web/pubspec.yaml @@ -2,11 +2,11 @@ name: pointer_interceptor_web description: Web implementation of the pointer_interceptor plugin. repository: https://github.com/flutter/packages/tree/main/packages/pointer_interceptor/pointer_interceptor_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3Apointer_interceptor -version: 0.10.0 +version: 0.10.1 environment: - sdk: '>=3.1.0 <4.0.0' - flutter: '>=3.13.0' + sdk: ^3.2.0 + flutter: '>=3.16.0' flutter: plugin: @@ -23,6 +23,7 @@ dependencies: sdk: flutter plugin_platform_interface: ^2.1.6 pointer_interceptor_platform_interface: ^0.10.0 + web: '>=0.3.0 <0.5.0' dev_dependencies: flutter_test: