Skip to content

Commit

Permalink
feat(iOS): Suppress menu items (react-native-webview#3082)
Browse files Browse the repository at this point in the history
  • Loading branch information
Arjan-Zuidema committed Aug 25, 2023
1 parent 5676b94 commit 5cd324c
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ public void setMenuItems(RNCWebView view, @Nullable ReadableArray items) {
mRNCWebViewManagerImpl.setMenuCustomItems(view, items);
}

@Override
@ReactProp(name = "suppressMenuItems ")
public void setSuppressMenuItems(RNCWebView view, @Nullable ReadableArray items) {}

@Override
@ReactProp(name = "messagingEnabled")
public void setMessagingEnabled(RNCWebView view, boolean value) {
Expand Down
9 changes: 9 additions & 0 deletions apple/RNCWebView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,15 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
}
[_view setMenuItems:newMenuItems];
}
if(oldViewProps.suppressMenuItems != newViewProps.suppressMenuItems) {
NSMutableArray *suppressMenuItems = [NSMutableArray array];

for (const auto &menuItem: newViewProps.suppressMenuItems) {
[suppressMenuItems addObject: RCTNSStringFromString(menuItem)];
}

[_view setSuppressMenuItems:suppressMenuItems];
}
if (oldViewProps.hasOnFileDownload != newViewProps.hasOnFileDownload) {
if (newViewProps.hasOnFileDownload) {
_view.onFileDownload = [self](NSDictionary* dictionary) {
Expand Down
1 change: 1 addition & 0 deletions apple/RNCWebViewImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
@property (nonatomic, assign) BOOL pullToRefreshEnabled;
@property (nonatomic, assign) BOOL enableApplePay;
@property (nonatomic, copy) NSArray<NSDictionary *> * _Nullable menuItems;
@property (nonatomic, copy) NSArray<NSString *> * _Nullable suppressMenuItems;
@property (nonatomic, copy) RCTDirectEventBlock onCustomMenuSelection;
#if !TARGET_OS_OSX
@property (nonatomic, assign) WKDataDetectorTypes dataDetectorTypes;
Expand Down
37 changes: 36 additions & 1 deletion apple/RNCWebViewImpl.m
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,42 @@ -(id)inputAccessoryView
@interface RNCWKWebView : WKWebView
#if !TARGET_OS_OSX
@property (nonatomic, copy) NSArray<NSDictionary *> * _Nullable menuItems;
@property (nonatomic, copy) NSArray<NSString *> * _Nullable suppressMenuItems;
#endif // !TARGET_OS_OSX
@end
@implementation RNCWKWebView
#if !TARGET_OS_OSX
- (NSString *)stringFromAction:(SEL) action {
NSString *sel = NSStringFromSelector(action);

NSDictionary *map = @{
@"cut:": @"cut",
@"copy:": @"copy",
@"paste:": @"paste",
@"delete:": @"delete",
@"select:": @"select",
@"selectAll:": @"selectAll",
@"_promptForReplace:": @"replace",
@"_define:": @"lookup",
@"_translate:": @"translate",
@"toggleBoldface:": @"bold",
@"toggleItalics:": @"italic",
@"toggleUnderline:": @"underline",
@"_share:": @"share",
};

return map[sel] ?: sel;
}

- (BOOL)canPerformAction:(SEL)action
withSender:(id)sender{

if(self.suppressMenuItems) {
NSString * sel = [self stringFromAction:action];
if ([self.suppressMenuItems containsObject: sel]) {
return NO;
}
}

if (!self.menuItems) {
return [super canPerformAction:action withSender:sender];
}
Expand Down Expand Up @@ -452,6 +481,7 @@ - (void)didMoveToWindow
[self setBackgroundColor: _savedBackgroundColor];
#if !TARGET_OS_OSX
_webView.menuItems = _menuItems;
_webView.suppressMenuItems = _suppressMenuItems;
_webView.scrollView.delegate = self;
#endif // !TARGET_OS_OSX
_webView.UIDelegate = self;
Expand Down Expand Up @@ -799,6 +829,11 @@ -(void)setMenuItems:(NSArray<NSDictionary *> *)menuItems {
_webView.menuItems = menuItems;
}

-(void)setSuppressMenuItems:(NSArray<NSString *> *)suppressMenuItems {
_suppressMenuItems = suppressMenuItems;
_webView.suppressMenuItems = suppressMenuItems;
}

-(void)setKeyboardDisplayRequiresUserAction:(BOOL)keyboardDisplayRequiresUserAction
{
if (_webView == nil) {
Expand Down
1 change: 1 addition & 0 deletions apple/RNCWebViewManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ - (RNCView *)view
RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(enableApplePay, BOOL)
RCT_EXPORT_VIEW_PROPERTY(menuItems, NSArray);
RCT_EXPORT_VIEW_PROPERTY(suppressMenuItems, NSArray);

// New arch only
RCT_CUSTOM_VIEW_PROPERTY(hasOnFileDownload, BOOL, RNCWebViewImpl) {}
Expand Down
25 changes: 25 additions & 0 deletions docs/Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ This document lays out the current public properties and methods for the React N
- [`onFileDownload`](Reference.md#onFileDownload)
- [`limitsNavigationsToAppBoundDomains`](Reference.md#limitsNavigationsToAppBoundDomains)
- [`textInteractionEnabled`](Reference.md#textInteractionEnabled)
- [`suppressMenuItems`](Reference.md#suppressMenuItems)
- [`mediaCapturePermissionGrantType`](Reference.md#mediaCapturePermissionGrantType)
- [`autoManageStatusBarEnabled`](Reference.md#autoManageStatusBarEnabled)
- [`setSupportMultipleWindows`](Reference.md#setSupportMultipleWindows)
Expand Down Expand Up @@ -1420,6 +1421,30 @@ Example:

---

### `suppressMenuItems`[](#props-index)

Allows to suppress menu item from the default context menu.

Possible values are:

- `cut`
- `copy`
- `paste`
- `delete`
- `select`
- `selectAll`
- `replace`
- `lookup`
- `translate`
- `bold`
- `italic`
- `underline`
- `share`

| Type | Required | Default | Platform |
| ---------------- | -------- | ------------ | -------- |
| array of strings | No | [] | iOS |

### `mediaCapturePermissionGrantType`[](#props-index)

This property specifies how to handle media capture permission requests. Defaults to `prompt`, resulting in the user being prompted repeatedly. Available on iOS 15 and later.
Expand Down
14 changes: 14 additions & 0 deletions example/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import NativeWebpage from './examples/NativeWebpage';
import ApplePay from './examples/ApplePay';
import CustomMenu from './examples/CustomMenu';
import OpenWindow from './examples/OpenWindow';
import SuppressMenuItems from './examples/Suppress';

const TESTS = {
Messaging: {
Expand Down Expand Up @@ -119,6 +120,14 @@ const TESTS = {
render() {
return <OpenWindow />;
},
},
SuppressMenuItems: {
title: 'SuppressMenuItems',
testId: 'SuppressMenuItems',
description: 'SuppressMenuItems in editable content',
render() {
return <SuppressMenuItems />;
}
}
};

Expand Down Expand Up @@ -222,6 +231,11 @@ export default class App extends Component<Props, State> {
title="OpenWindow"
onPress={() => this._changeTest('OpenWindow')}
/>
<Button
testID="testType_suppressMenuItems"
title="SuppressMenuItems"
onPress={() => this._changeTest('SuppressMenuItems')}
/>
</View>

{restarting ? null : (
Expand Down
46 changes: 46 additions & 0 deletions example/examples/Suppress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { Component } from 'react';
import { View } from 'react-native';

import WebView from 'react-native-webview';

interface Props {}
interface State {}

const HTML = `
<!DOCTYPE html>
<html>
<head>
<title>Context Menu</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=320, user-scalable=no">
</head>
<body>
<p contenteditable autofocus>
Select text to see the context menu.
</p>
</body>
</html>
`

export default class NativeWebpage extends Component<Props, State> {
state = {};

render() {
return (
<View style={{ height: 400 }}>
<WebView
source={{ html: HTML }}
style={{ width: '100%', height: '100%' }}
onShouldStartLoadWithRequest={(event) => {
console.log("onShouldStartLoadWithRequest", event);
return true;
}}
onLoadStart={(event) => {
console.log("onLoadStart", event.nativeEvent);
}}
suppressMenuItems={["cut", "copy", "paste", "replace", "bold", "italic", "underline", "select", "share", "lookup"]}
/>
</View>
);
}
}
1 change: 1 addition & 0 deletions src/RNCWebViewNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ export interface NativeProps extends ViewProps {
onFileDownload?: DirectEventHandler<WebViewDownloadEvent>;
// eslint-disable-next-line @typescript-eslint/array-type
menuItems?: ReadonlyArray<Readonly<{label: string, key: string}>>;
suppressMenuItems?: Readonly<string>[];
// Workaround to watch if listener if defined
hasOnFileDownload?: boolean;
fraudulentWebsiteWarningEnabled?: boolean;
Expand Down
20 changes: 20 additions & 0 deletions src/WebViewTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,20 @@ export interface WebViewCustomMenuItems {
label: string;
}

export declare type SuppressMenuItem =
| "cut"
| "copy"
| "paste"
| "replace"
| "bold"
| "italic"
| "underline"
| "select"
| "selectAll"
| "translate"
| "lookup"
| "share";

export type WebViewSource = WebViewSourceUri | WebViewSourceHtml;

export interface ViewManager {
Expand Down Expand Up @@ -683,6 +697,12 @@ export interface IOSWebViewProps extends WebViewSharedProps {
*/
menuItems?: WebViewCustomMenuItems[];

/**
* An array of strings which will be suppressed from the menu.
* @platform ios
*/
suppressMenuItems?: SuppressMenuItem[];

/**
* The function fired when selecting a custom menu item created by `menuItems`.
* It passes a WebViewEvent with a `nativeEvent`, where custom keys are passed:
Expand Down

0 comments on commit 5cd324c

Please sign in to comment.