mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-27 10:32:58 +02:00
Mobile: Add long-press tooltips (#6758)
This commit is contained in:
parent
8ef9804cab
commit
a5e6491cda
@ -842,6 +842,9 @@ packages/app-mobile/components/BackButtonDialogBox.js.map
|
||||
packages/app-mobile/components/CameraView.d.ts
|
||||
packages/app-mobile/components/CameraView.js
|
||||
packages/app-mobile/components/CameraView.js.map
|
||||
packages/app-mobile/components/CustomButton.d.ts
|
||||
packages/app-mobile/components/CustomButton.js
|
||||
packages/app-mobile/components/CustomButton.js.map
|
||||
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.d.ts
|
||||
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
|
||||
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js.map
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -831,6 +831,9 @@ packages/app-mobile/components/BackButtonDialogBox.js.map
|
||||
packages/app-mobile/components/CameraView.d.ts
|
||||
packages/app-mobile/components/CameraView.js
|
||||
packages/app-mobile/components/CameraView.js.map
|
||||
packages/app-mobile/components/CustomButton.d.ts
|
||||
packages/app-mobile/components/CustomButton.js
|
||||
packages/app-mobile/components/CustomButton.js.map
|
||||
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.d.ts
|
||||
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
|
||||
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js.map
|
||||
|
190
packages/app-mobile/components/CustomButton.tsx
Normal file
190
packages/app-mobile/components/CustomButton.tsx
Normal file
@ -0,0 +1,190 @@
|
||||
//
|
||||
// A button with a long-press action. Long-pressing the button displays a tooltip
|
||||
//
|
||||
|
||||
const React = require('react');
|
||||
import { ReactNode } from 'react';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import { Theme } from '@joplin/lib/themes/type';
|
||||
import { useState, useMemo, useCallback, useRef } from 'react';
|
||||
import { View, Text, Pressable, ViewStyle, PressableStateCallbackType, StyleProp, StyleSheet, LayoutChangeEvent, LayoutRectangle, Animated, AccessibilityState, AccessibilityRole } from 'react-native';
|
||||
import { Menu, MenuOptions, MenuTrigger, renderers } from 'react-native-popup-menu';
|
||||
|
||||
type ButtonClickListener = ()=> void;
|
||||
interface ButtonProps {
|
||||
onPress: ButtonClickListener;
|
||||
|
||||
// Accessibility label and text shown in a tooltip
|
||||
description?: string;
|
||||
|
||||
children: ReactNode;
|
||||
|
||||
themeId: number;
|
||||
|
||||
style?: ViewStyle;
|
||||
pressedStyle?: ViewStyle;
|
||||
contentStyle?: ViewStyle;
|
||||
|
||||
// Additional accessibility information. See View.accessibilityHint
|
||||
accessibilityHint?: string;
|
||||
|
||||
// Role of the button. Defaults to 'button'.
|
||||
accessibilityRole?: AccessibilityRole;
|
||||
accessibilityState?: AccessibilityState;
|
||||
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const CustomButton = (props: ButtonProps) => {
|
||||
const [tooltipVisible, setTooltipVisible] = useState(false);
|
||||
const [buttonLayout, setButtonLayout] = useState<LayoutRectangle|null>(null);
|
||||
const tooltipStyles = useTooltipStyles(props.themeId);
|
||||
|
||||
// See https://blog.logrocket.com/react-native-touchable-vs-pressable-components/
|
||||
// for more about animating Pressable buttons.
|
||||
const fadeAnim = useRef(new Animated.Value(1)).current;
|
||||
|
||||
const animationDuration = 100; // ms
|
||||
const onPressIn = useCallback(() => {
|
||||
// Fade out.
|
||||
Animated.timing(fadeAnim, {
|
||||
toValue: 0.5,
|
||||
duration: animationDuration,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
}, [fadeAnim]);
|
||||
const onPressOut = useCallback(() => {
|
||||
// Fade in.
|
||||
Animated.timing(fadeAnim, {
|
||||
toValue: 1,
|
||||
duration: animationDuration,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
|
||||
setTooltipVisible(false);
|
||||
}, [fadeAnim]);
|
||||
const onLongPress = useCallback(() => {
|
||||
setTooltipVisible(true);
|
||||
}, []);
|
||||
|
||||
// Select different user-specified styles if selected/unselected.
|
||||
const onStyleChange = useCallback((state: PressableStateCallbackType): StyleProp<ViewStyle> => {
|
||||
let result = { ...props.style };
|
||||
|
||||
if (state.pressed) {
|
||||
result = {
|
||||
...result,
|
||||
...props.pressedStyle,
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}, [props.pressedStyle, props.style]);
|
||||
|
||||
const onButtonLayout = useCallback((event: LayoutChangeEvent) => {
|
||||
const layoutEvt = event.nativeEvent.layout;
|
||||
|
||||
// Copy the layout event
|
||||
setButtonLayout({ ...layoutEvt });
|
||||
}, []);
|
||||
|
||||
|
||||
const button = (
|
||||
<Pressable
|
||||
onPress={props.onPress}
|
||||
onLongPress={onLongPress}
|
||||
onPressIn={onPressIn}
|
||||
onPressOut={onPressOut}
|
||||
|
||||
style={ onStyleChange }
|
||||
|
||||
disabled={ props.disabled ?? false }
|
||||
onLayout={ onButtonLayout }
|
||||
|
||||
accessibilityLabel={props.description}
|
||||
accessibilityHint={props.accessibilityHint}
|
||||
accessibilityRole={props.accessibilityRole ?? 'button'}
|
||||
accessibilityState={props.accessibilityState}
|
||||
>
|
||||
<Animated.View style={{
|
||||
opacity: fadeAnim,
|
||||
...props.contentStyle,
|
||||
}}>
|
||||
{ props.children }
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
);
|
||||
|
||||
const tooltip = (
|
||||
<View
|
||||
// Any information given by the tooltip should also be provided via
|
||||
// [accessibilityLabel]/[accessibilityHint]. As such, we can hide the tooltip
|
||||
// from the screen reader.
|
||||
// On Android:
|
||||
importantForAccessibility='no-hide-descendants'
|
||||
// On iOS:
|
||||
accessibilityElementsHidden={true}
|
||||
|
||||
// Position the menu beneath the button so the tooltip appears in the
|
||||
// correct location.
|
||||
style={{
|
||||
left: buttonLayout?.x,
|
||||
top: buttonLayout?.y,
|
||||
position: 'absolute',
|
||||
zIndex: -1,
|
||||
}}
|
||||
>
|
||||
<Menu
|
||||
opened={tooltipVisible}
|
||||
renderer={renderers.Popover}
|
||||
rendererProps={{
|
||||
preferredPlacement: 'bottom',
|
||||
anchorStyle: tooltipStyles.anchor,
|
||||
}}>
|
||||
<MenuTrigger
|
||||
// Don't show/hide when pressed (let the Pressable handle opening/closing)
|
||||
disabled={true}
|
||||
style={{
|
||||
// Ensure that the trigger region has the same size as the button.
|
||||
width: buttonLayout?.width ?? 0,
|
||||
height: buttonLayout?.height ?? 0,
|
||||
}}
|
||||
/>
|
||||
<MenuOptions
|
||||
customStyles={{ optionsContainer: tooltipStyles.optionsContainer }}
|
||||
>
|
||||
<Text style={tooltipStyles.text}>
|
||||
{props.description}
|
||||
</Text>
|
||||
</MenuOptions>
|
||||
</Menu>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.description ? tooltip : null}
|
||||
{button}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const useTooltipStyles = (themeId: number) => {
|
||||
return useMemo(() => {
|
||||
const themeData: Theme = themeStyle(themeId);
|
||||
|
||||
return StyleSheet.create({
|
||||
text: {
|
||||
color: themeData.raisedColor,
|
||||
padding: 4,
|
||||
},
|
||||
anchor: {
|
||||
backgroundColor: themeData.raisedBackgroundColor,
|
||||
},
|
||||
optionsContainer: {
|
||||
backgroundColor: themeData.raisedBackgroundColor,
|
||||
},
|
||||
});
|
||||
}, [themeId]);
|
||||
};
|
||||
|
||||
export default CustomButton;
|
@ -115,6 +115,7 @@ function NoteEditor(props: Props, ref: any) {
|
||||
` : '';
|
||||
|
||||
const editorSettings: EditorSettings = {
|
||||
themeId: props.themeId,
|
||||
themeData: editorTheme(props.themeId),
|
||||
katexEnabled: Setting.value('markdown.plugin.katex') as boolean,
|
||||
};
|
||||
|
@ -1,15 +1,14 @@
|
||||
// Displays a find/replace dialog
|
||||
|
||||
const React = require('react');
|
||||
const { StyleSheet } = require('react-native');
|
||||
const { TextInput, View, Text, TouchableOpacity } = require('react-native');
|
||||
const { useMemo, useState, useEffect } = require('react');
|
||||
const MaterialCommunityIcon = require('react-native-vector-icons/MaterialCommunityIcons').default;
|
||||
|
||||
import { SearchControl, SearchState, EditorSettings } from './types';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { BackHandler } from 'react-native';
|
||||
import { BackHandler, TextInput, View, Text, StyleSheet, ViewStyle } from 'react-native';
|
||||
import { Theme } from '@joplin/lib/themes/type';
|
||||
import CustomButton from '../CustomButton';
|
||||
|
||||
const buttonSize = 48;
|
||||
|
||||
@ -33,6 +32,7 @@ export interface SearchPanelProps {
|
||||
|
||||
interface ActionButtonProps {
|
||||
styles: any;
|
||||
themeId: number;
|
||||
iconName: string;
|
||||
title: string;
|
||||
onPress: Callback;
|
||||
@ -42,30 +42,32 @@ const ActionButton = (
|
||||
props: ActionButtonProps
|
||||
) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
<CustomButton
|
||||
themeId={props.themeId}
|
||||
style={props.styles.button}
|
||||
onPress={props.onPress}
|
||||
|
||||
accessibilityLabel={props.title}
|
||||
accessibilityRole='button'
|
||||
description={props.title}
|
||||
>
|
||||
<MaterialCommunityIcon name={props.iconName} style={props.styles.buttonText}/>
|
||||
</TouchableOpacity>
|
||||
</CustomButton>
|
||||
);
|
||||
};
|
||||
|
||||
interface ToggleButtonProps {
|
||||
styles: any;
|
||||
themeId: number;
|
||||
iconName: string;
|
||||
title: string;
|
||||
active: boolean;
|
||||
onToggle: Callback;
|
||||
}
|
||||
|
||||
const ToggleButton = (props: ToggleButtonProps) => {
|
||||
const active = props.active;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
<CustomButton
|
||||
themeId={props.themeId}
|
||||
style={{
|
||||
...props.styles.toggleButton,
|
||||
...(active ? props.styles.toggleButtonActive : {}),
|
||||
@ -75,20 +77,20 @@ const ToggleButton = (props: ToggleButtonProps) => {
|
||||
accessibilityState={{
|
||||
checked: props.active,
|
||||
}}
|
||||
accessibilityLabel={props.title}
|
||||
description={props.title}
|
||||
accessibilityRole='switch'
|
||||
>
|
||||
<MaterialCommunityIcon name={props.iconName} style={
|
||||
active ? props.styles.activeButtonText : props.styles.buttonText
|
||||
}/>
|
||||
</TouchableOpacity>
|
||||
</CustomButton>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const useStyles = (theme: Theme) => {
|
||||
return useMemo(() => {
|
||||
const buttonStyle = {
|
||||
const buttonStyle: ViewStyle = {
|
||||
width: buttonSize,
|
||||
height: buttonSize,
|
||||
backgroundColor: theme.backgroundColor4,
|
||||
@ -136,8 +138,9 @@ const useStyles = (theme: Theme) => {
|
||||
};
|
||||
|
||||
export const SearchPanel = (props: SearchPanelProps) => {
|
||||
const placeholderColor = props.editorSettings.themeData.color3;
|
||||
const styles = useStyles(props.editorSettings.themeData);
|
||||
const theme = props.editorSettings.themeData;
|
||||
const placeholderColor = theme.color3;
|
||||
const styles = useStyles(theme);
|
||||
|
||||
const [showingAdvanced, setShowAdvanced] = useState(false);
|
||||
|
||||
@ -185,9 +188,10 @@ export const SearchPanel = (props: SearchPanelProps) => {
|
||||
}, [state.dialogVisible]);
|
||||
|
||||
|
||||
|
||||
const themeId = props.editorSettings.themeId;
|
||||
const closeButton = (
|
||||
<ActionButton
|
||||
themeId={themeId}
|
||||
styles={styles}
|
||||
iconName="close"
|
||||
onPress={control.hideSearch}
|
||||
@ -197,6 +201,7 @@ export const SearchPanel = (props: SearchPanelProps) => {
|
||||
|
||||
const showDetailsButton = (
|
||||
<ActionButton
|
||||
themeId={themeId}
|
||||
styles={styles}
|
||||
iconName="menu-down"
|
||||
onPress={() => setShowAdvanced(true)}
|
||||
@ -206,6 +211,7 @@ export const SearchPanel = (props: SearchPanelProps) => {
|
||||
|
||||
const hideDetailsButton = (
|
||||
<ActionButton
|
||||
themeId={themeId}
|
||||
styles={styles}
|
||||
iconName="menu-up"
|
||||
onPress={() => setShowAdvanced(false)}
|
||||
@ -255,6 +261,7 @@ export const SearchPanel = (props: SearchPanelProps) => {
|
||||
|
||||
const toNextButton = (
|
||||
<ActionButton
|
||||
themeId={themeId}
|
||||
styles={styles}
|
||||
iconName="menu-right"
|
||||
onPress={control.findNext}
|
||||
@ -264,6 +271,7 @@ export const SearchPanel = (props: SearchPanelProps) => {
|
||||
|
||||
const toPrevButton = (
|
||||
<ActionButton
|
||||
themeId={themeId}
|
||||
styles={styles}
|
||||
iconName="menu-left"
|
||||
onPress={control.findPrevious}
|
||||
@ -273,6 +281,7 @@ export const SearchPanel = (props: SearchPanelProps) => {
|
||||
|
||||
const replaceButton = (
|
||||
<ActionButton
|
||||
themeId={themeId}
|
||||
styles={styles}
|
||||
iconName="swap-horizontal"
|
||||
onPress={control.replaceCurrent}
|
||||
@ -282,6 +291,7 @@ export const SearchPanel = (props: SearchPanelProps) => {
|
||||
|
||||
const replaceAllButton = (
|
||||
<ActionButton
|
||||
themeId={themeId}
|
||||
styles={styles}
|
||||
iconName="reply-all"
|
||||
onPress={control.replaceAll}
|
||||
@ -291,6 +301,7 @@ export const SearchPanel = (props: SearchPanelProps) => {
|
||||
|
||||
const regexpButton = (
|
||||
<ToggleButton
|
||||
themeId={themeId}
|
||||
styles={styles}
|
||||
iconName="regex"
|
||||
onToggle={() => {
|
||||
@ -305,6 +316,7 @@ export const SearchPanel = (props: SearchPanelProps) => {
|
||||
|
||||
const caseSensitiveButton = (
|
||||
<ToggleButton
|
||||
themeId={themeId}
|
||||
styles={styles}
|
||||
iconName="format-letter-case"
|
||||
onToggle={() => {
|
||||
|
@ -1,5 +1,6 @@
|
||||
// Types related to the NoteEditor
|
||||
|
||||
import { Theme } from '@joplin/lib/themes/type';
|
||||
import { CodeMirrorControl } from './CodeMirror/types';
|
||||
|
||||
// Controls for the entire editor (including dialogs)
|
||||
@ -10,7 +11,12 @@ export interface EditorControl extends CodeMirrorControl {
|
||||
}
|
||||
|
||||
export interface EditorSettings {
|
||||
themeData: any;
|
||||
// EditorSettings objects are deserialized within WebViews, where
|
||||
// [themeStyle(themeId: number)] doesn't work. As such, we need both
|
||||
// the [themeId] and [themeData].
|
||||
themeId: number;
|
||||
themeData: Theme;
|
||||
|
||||
katexEnabled: boolean;
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ const { dialogs } = require('../utils/dialogs.js');
|
||||
const DialogBox = require('react-native-dialogbox').default;
|
||||
const { localSyncInfoFromState } = require('@joplin/lib/services/synchronizer/syncInfoUtils');
|
||||
const { showMissingMasterKeyMessage } = require('@joplin/lib/services/e2ee/utils');
|
||||
import CustomButton from './CustomButton';
|
||||
|
||||
Icon.loadFont();
|
||||
|
||||
@ -223,6 +224,7 @@ class ScreenHeaderComponent extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const themeId = Setting.value('theme');
|
||||
function sideMenuButton(styles, onPress) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
@ -283,19 +285,23 @@ class ScreenHeaderComponent extends React.PureComponent {
|
||||
const viewStyle = options.disabled ? this.styles().iconButtonDisabled : this.styles().iconButton;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
<CustomButton
|
||||
onPress={options.onPress}
|
||||
style={{ padding: 0 }}
|
||||
themeId={themeId}
|
||||
disabled={!!options.disabled}
|
||||
accessibilityRole="button">
|
||||
<View style={viewStyle}>{icon}</View>
|
||||
</TouchableOpacity>
|
||||
description={options.description}
|
||||
contentStyle={viewStyle}
|
||||
>
|
||||
{icon}
|
||||
</CustomButton>
|
||||
);
|
||||
};
|
||||
|
||||
const renderUndoButton = () => {
|
||||
return renderTopButton({
|
||||
iconName: 'arrow-undo-circle-sharp',
|
||||
description: _('Undo'),
|
||||
onPress: this.props.onUndoButtonPress,
|
||||
visible: this.props.showUndoButton,
|
||||
disabled: this.props.undoButtonDisabled,
|
||||
@ -305,6 +311,7 @@ class ScreenHeaderComponent extends React.PureComponent {
|
||||
const renderRedoButton = () => {
|
||||
return renderTopButton({
|
||||
iconName: 'arrow-redo-circle-sharp',
|
||||
description: _('Redo'),
|
||||
onPress: this.props.onRedoButtonPress,
|
||||
visible: this.props.showRedoButton,
|
||||
});
|
||||
@ -312,65 +319,65 @@ class ScreenHeaderComponent extends React.PureComponent {
|
||||
|
||||
function selectAllButton(styles, onPress) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
<CustomButton
|
||||
onPress={onPress}
|
||||
|
||||
accessibilityLabel={_('Select all')}
|
||||
accessibilityRole="button">
|
||||
<View style={styles.iconButton}>
|
||||
<Icon name="md-checkmark-circle-outline" style={styles.topIcon} />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
themeId={themeId}
|
||||
description={_('Select all')}
|
||||
contentStyle={styles.iconButton}
|
||||
>
|
||||
<Icon name="md-checkmark-circle-outline" style={styles.topIcon} />
|
||||
</CustomButton>
|
||||
);
|
||||
}
|
||||
|
||||
function searchButton(styles, onPress) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
<CustomButton
|
||||
onPress={onPress}
|
||||
|
||||
accessibilityLabel={_('Search')}
|
||||
accessibilityRole="button">
|
||||
<View style={styles.iconButton}>
|
||||
<Icon name="md-search" style={styles.topIcon} />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
description={_('Search')}
|
||||
themeId={themeId}
|
||||
contentStyle={styles.iconButton}
|
||||
>
|
||||
<Icon name="md-search" style={styles.topIcon} />
|
||||
</CustomButton>
|
||||
);
|
||||
}
|
||||
|
||||
function deleteButton(styles, onPress, disabled) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
<CustomButton
|
||||
onPress={onPress}
|
||||
disabled={disabled}
|
||||
|
||||
accessibilityLabel={_('Delete')}
|
||||
themeId={themeId}
|
||||
description={_('Delete')}
|
||||
accessibilityHint={
|
||||
disabled ? null : _('Delete selected notes')
|
||||
}
|
||||
accessibilityRole="button">
|
||||
<View style={disabled ? styles.iconButtonDisabled : styles.iconButton}>
|
||||
<Icon name="md-trash" style={styles.topIcon} />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
contentStyle={disabled ? styles.iconButtonDisabled : styles.iconButton}
|
||||
>
|
||||
<Icon name="md-trash" style={styles.topIcon} />
|
||||
</CustomButton>
|
||||
);
|
||||
}
|
||||
|
||||
function duplicateButton(styles, onPress, disabled) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
<CustomButton
|
||||
onPress={onPress}
|
||||
disabled={disabled}
|
||||
|
||||
accessibilityLabel={_('Duplicate')}
|
||||
themeId={themeId}
|
||||
description={_('Duplicate')}
|
||||
accessibilityHint={
|
||||
disabled ? null : _('Duplicate selected notes')
|
||||
}
|
||||
accessibilityRole="button">
|
||||
<View style={disabled ? styles.iconButtonDisabled : styles.iconButton}>
|
||||
<Icon name="md-copy" style={styles.topIcon} />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
contentStyle={disabled ? styles.iconButtonDisabled : styles.iconButton}
|
||||
>
|
||||
<Icon name="md-copy" style={styles.topIcon} />
|
||||
</CustomButton>
|
||||
);
|
||||
}
|
||||
|
||||
@ -424,7 +431,6 @@ class ScreenHeaderComponent extends React.PureComponent {
|
||||
}
|
||||
|
||||
const createTitleComponent = (disabled) => {
|
||||
const themeId = Setting.value('theme');
|
||||
const theme = themeStyle(themeId);
|
||||
const folderPickerOptions = this.props.folderPickerOptions;
|
||||
|
||||
|
@ -50,7 +50,7 @@
|
||||
"react-native-image-picker": "^2.3.4",
|
||||
"react-native-image-resizer": "^1.3.0",
|
||||
"react-native-modal-datetime-picker": "^9.0.0",
|
||||
"react-native-popup-menu": "^0.10.0",
|
||||
"react-native-popup-menu": "^0.15.13",
|
||||
"react-native-quick-actions": "^0.3.13",
|
||||
"react-native-rsa-native": "^2.0.4",
|
||||
"react-native-securerandom": "^1.0.0-rc.0",
|
||||
|
10
yarn.lock
10
yarn.lock
@ -4077,7 +4077,7 @@ __metadata:
|
||||
react-native-image-picker: ^2.3.4
|
||||
react-native-image-resizer: ^1.3.0
|
||||
react-native-modal-datetime-picker: ^9.0.0
|
||||
react-native-popup-menu: ^0.10.0
|
||||
react-native-popup-menu: ^0.15.13
|
||||
react-native-quick-actions: ^0.3.13
|
||||
react-native-rsa-native: ^2.0.4
|
||||
react-native-securerandom: ^1.0.0-rc.0
|
||||
@ -28077,10 +28077,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-native-popup-menu@npm:^0.10.0":
|
||||
version: 0.10.0
|
||||
resolution: "react-native-popup-menu@npm:0.10.0"
|
||||
checksum: c1aeed63f012d3afa71efa9ce40846d4e67165a38160aa7db7ddbae06b426d76f9631a81798515563a946887747d88deae19430585cd1a8ab36e9374cdbccaba
|
||||
"react-native-popup-menu@npm:^0.15.13":
|
||||
version: 0.15.13
|
||||
resolution: "react-native-popup-menu@npm:0.15.13"
|
||||
checksum: a251cf0336e607ad23eff6e71c08a3fafca9d0a91a910f958b902373d2a1aa49a437ecb9554c9bac1f605511d5dd53669a8903584a6c1cf742f913e2f4b9bd0b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user