Skip to content

Commit

Permalink
fix/disable-context-menu
Browse files Browse the repository at this point in the history
  • Loading branch information
nank1ro committed Sep 20, 2024
1 parent 99fd6f6 commit 87fbdd9
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 17 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.9.6

- Fix: the browser context menu has been enabled again, and deactivated only for the `ShadContextMenu` component.

## 0.9.5

- Add text selection toolbar to `ShadInput` (thanks to @moshOntong-IT).
Expand Down
1 change: 1 addition & 0 deletions lib/shadcn_ui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export 'src/utils/provider.dart' hide ProviderReadExt, ProviderWatchExt;
export 'src/utils/responsive.dart';
export 'src/utils/states_controller.dart';
export 'src/utils/mouse_area.dart';
export 'src/utils/disable_context_menu/disable_context_menu.dart';

// External libraries
export 'package:flutter_animate/flutter_animate.dart' hide Effect;
Expand Down
5 changes: 3 additions & 2 deletions lib/src/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart'
show
Expand Down Expand Up @@ -559,9 +560,9 @@ class _ShadAppState extends State<ShadApp> {
@override
void initState() {
super.initState();
// This could be centralized in the context menu component, see https://github.com/flutter/engine/pull/53278#issuecomment-2328309843
if (kIsWeb) {
BrowserContextMenu.disableContextMenu();
// needed for disabling the native context menu on web
SemanticsBinding.instance.ensureSemantics();
}
}

Expand Down
32 changes: 18 additions & 14 deletions lib/src/components/context_menu.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:shadcn_ui/src/utils/disable_context_menu/disable_context_menu.dart';
import 'package:shadcn_ui/src/utils/provider.dart';

const kContextMenuGroupId = ValueKey('context-menu');
Expand Down Expand Up @@ -82,6 +83,7 @@ class ShadContextMenuRegion extends StatefulWidget {
}

class _ShadContextMenuRegionState extends State<ShadContextMenuRegion> {
final identifier = UniqueKey();
ShadContextMenuController? _controller;
ShadContextMenuController get controller =>
widget.controller ??
Expand Down Expand Up @@ -130,6 +132,15 @@ class _ShadContextMenuRegionState extends State<ShadContextMenuRegion> {
return ShadContextMenu(
anchor: offset == null ? null : ShadGlobalAnchor(offset!),
controller: controller,
children: widget.children,
constraints: widget.constraints,
onHoverArea: widget.onHoverArea,
padding: widget.padding,
groupId: widget.groupId,
effects: widget.effects,
shadows: widget.shadows,
decoration: widget.decoration,
filter: widget.filter,
child: ShadGestureDetector(
onTapDown: (_) => hide(),
onSecondaryTapDown: show,
Expand All @@ -141,15 +152,6 @@ class _ShadContextMenuRegionState extends State<ShadContextMenuRegion> {
onLongPress: effectiveLongPressEnabled ? onLongPress : null,
child: widget.child,
),
children: widget.children,
constraints: widget.constraints,
onHoverArea: widget.onHoverArea,
padding: widget.padding,
groupId: widget.groupId,
effects: widget.effects,
shadows: widget.shadows,
decoration: widget.decoration,
filter: widget.filter,
);
}
}
Expand Down Expand Up @@ -317,11 +319,13 @@ class ShadContextMenuState extends State<ShadContextMenu> {
),
);
},
child: ShadMouseArea(
groupId: widget.groupId,
onEnter: (_) => widget.onHoverArea?.call(true),
onExit: (_) => widget.onHoverArea?.call(false),
child: widget.child,
child: DisableWebContextMenu(
child: ShadMouseArea(
groupId: widget.groupId,
onEnter: (_) => widget.onHoverArea?.call(true),
onExit: (_) => widget.onHoverArea?.call(false),
child: widget.child,
),
),
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'non_web.dart' if (dart.library.js_interop) 'web.dart';
18 changes: 18 additions & 0 deletions lib/src/utils/disable_context_menu/non_web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:flutter/widgets.dart';

class DisableWebContextMenu extends StatelessWidget {
const DisableWebContextMenu({
super.key,
required this.child,
this.identifier,
});

final String? identifier;
final Widget child;

@override
Widget build(BuildContext context) {
// no-op on non-web platforms
return child;
}
}
89 changes: 89 additions & 0 deletions lib/src/utils/disable_context_menu/web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// ignore_for_file: avoid_web_libraries_in_flutter

import 'dart:html' as html;

import 'package:flutter/widgets.dart';

class DisableWebContextMenu extends StatefulWidget {
const DisableWebContextMenu({
super.key,
required this.child,
this.identifier,
});

final String? identifier;
final Widget child;

@override
State<DisableWebContextMenu> createState() => _DisableWebContextMenuState();
}

class _DisableWebContextMenuState extends State<DisableWebContextMenu> {
html.MutationObserver? observer;

final _identifier = UniqueKey();

String get identifier => widget.identifier ?? _identifier.toString();

@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final element = findElement();
if (element != null) {
element.setAttribute('oncontextmenu', 'return false;');
} else {
addObserver();
}
});
}

html.Element? findElement() => html.document
.querySelector('flt-semantics-host')
?.querySelector('[flt-semantics-identifier="$identifier"]');

void addObserver() {
observer = html.MutationObserver((mutations, _) {
for (final mutation in mutations) {
if (mutation is! html.MutationRecord) continue;
if (mutation.addedNodes?.isNotEmpty ?? false) {
for (final node in mutation.addedNodes!) {
if (node is html.HtmlElement) {
final id = node.attributes['flt-semantics-identifier'];
if (id == identifier) {
node.setAttribute('oncontextmenu', 'return false;');
removeObserver();
}
}
}
}
}
});

observer!.observe(
html.document,
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['flt-semantics-identifier'],
);
}

void removeObserver() {
observer?.disconnect();
}

@override
void dispose() {
removeObserver();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Semantics(
identifier: identifier,
child: widget.child,
);
}
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: shadcn_ui
description: shadcn-ui ported in Flutter. Awesome UI components for Flutter, fully customizable.
version: 0.9.5
version: 0.9.6
homepage: https://mariuti.com/shadcn-ui
repository: https://github.com/nank1ro/flutter-shadcn-ui
documentation: https://mariuti.com/shadcn-ui
Expand Down

0 comments on commit 87fbdd9

Please sign in to comment.