1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:js_interop';
6import 'dart:ui_web' as ui_web;
7
8import 'package:flutter/rendering.dart';
9
10import '../web.dart' as web;
11import 'basic.dart';
12import 'framework.dart';
13import 'platform_view.dart';
14import 'selection_container.dart';
15
16const String _viewType = 'Browser__WebContextMenuViewType__';
17const String _kClassName = 'web-selectable-region-context-menu';
18// These css rules hides the dom element with the class name.
19const String _kClassSelectionRule = '.$_kClassName::selection { background: transparent; }';
20const String _kClassRule = '''
21.$_kClassName {
22 color: transparent;
23 user-select: text;
24 -webkit-user-select: text; /* Safari */
25 -moz-user-select: text; /* Firefox */
26 -ms-user-select: text; /* IE10+ */
27}
28''';
29const int _kRightClickButton = 2;
30
31typedef _WebSelectionCallBack = void Function(web.HTMLElement, web.MouseEvent);
32
33/// Function signature for `ui_web.platformViewRegistry.registerViewFactory`.
34@visibleForTesting
35typedef RegisterViewFactory = void Function(String, Object Function(int viewId), {bool isVisible});
36
37/// See `_platform_selectable_region_context_menu_io.dart` for full
38/// documentation.
39class PlatformSelectableRegionContextMenu extends StatelessWidget {
40 /// See `_platform_selectable_region_context_menu_io.dart`.
41 PlatformSelectableRegionContextMenu({required this.child, super.key}) {
42 if (_registeredViewType == null) {
43 _register();
44 }
45 }
46
47 /// See `_platform_selectable_region_context_menu_io.dart`.
48 final Widget child;
49
50 /// See `_platform_selectable_region_context_menu_io.dart`.
51 // ignore: use_setters_to_change_properties
52 static void attach(SelectionContainerDelegate client) {
53 _activeClient = client;
54 }
55
56 /// See `_platform_selectable_region_context_menu_io.dart`.
57 static void detach(SelectionContainerDelegate client) {
58 if (_activeClient != client) {
59 _activeClient = null;
60 }
61 }
62
63 static SelectionContainerDelegate? _activeClient;
64
65 // Keeps track if this widget has already registered its view factories or not.
66 static String? _registeredViewType;
67
68 static RegisterViewFactory get _registerViewFactory =>
69 debugOverrideRegisterViewFactory ?? ui_web.platformViewRegistry.registerViewFactory;
70
71 /// Override this to provide a custom implementation of [ui_web.platformViewRegistry.registerViewFactory].
72 ///
73 /// This should only be used for testing.
74 // See `_platform_selectable_region_context_menu_io.dart`.
75 @visibleForTesting
76 static RegisterViewFactory? debugOverrideRegisterViewFactory;
77
78 /// Resets the view factory registration to its initial state.
79 @visibleForTesting
80 static void debugResetRegistry() {
81 _registeredViewType = null;
82 }
83
84 // Registers the view factories for the interceptor widgets.
85 static void _register() {
86 assert(_registeredViewType == null);
87 _registeredViewType = _registerWebSelectionCallback((
88 web.HTMLElement element,
89 web.MouseEvent event,
90 ) {
91 final SelectionContainerDelegate? client = _activeClient;
92 if (client != null) {
93 // Converts the html right click event to flutter coordinate.
94 final Offset localOffset = Offset(event.offsetX.toDouble(), event.offsetY.toDouble());
95 final Matrix4 transform = client.getTransformTo(null);
96 final Offset globalOffset = MatrixUtils.transformPoint(transform, localOffset);
97 client.dispatchSelectionEvent(SelectWordSelectionEvent(globalPosition: globalOffset));
98 // The innerText must contain the text in order to be selected by
99 // the browser.
100 element.innerText = client.getSelectedContent()?.plainText ?? '';
101
102 // Programmatically select the dom element in browser.
103 final web.Range range = web.document.createRange()..selectNode(element);
104
105 web.window.getSelection()
106 ?..removeAllRanges()
107 ..addRange(range);
108 }
109 });
110 }
111
112 static String _registerWebSelectionCallback(_WebSelectionCallBack callback) {
113 // Create css style for _kClassName.
114 final web.HTMLStyleElement styleElement =
115 web.document.createElement('style') as web.HTMLStyleElement;
116 web.document.head!.append(styleElement as JSAny);
117 final web.CSSStyleSheet sheet = styleElement.sheet!;
118 sheet.insertRule(_kClassRule, 0);
119 sheet.insertRule(_kClassSelectionRule, 1);
120
121 _registerViewFactory(_viewType, (int viewId, {Object? params}) {
122 final web.HTMLElement htmlElement = web.document.createElement('div') as web.HTMLElement;
123 htmlElement
124 ..style.width = '100%'
125 ..style.height = '100%'
126 ..classList.add(_kClassName);
127
128 htmlElement.addEventListener(
129 'mousedown',
130 (web.Event event) {
131 final web.MouseEvent mouseEvent = event as web.MouseEvent;
132 if (mouseEvent.button != _kRightClickButton) {
133 return;
134 }
135 callback(htmlElement, mouseEvent);
136 }.toJS,
137 );
138 return htmlElement;
139 }, isVisible: false);
140 return _viewType;
141 }
142
143 @override
144 Widget build(BuildContext context) {
145 return Stack(
146 children: <Widget>[const Positioned.fill(child: HtmlElementView(viewType: _viewType)), child],
147 );
148 }
149}
150

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com