mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-06 07:06:11 +00:00
feat: introduce hitSlop prop (#2407)
# Summary Explain the **motivation** for making this change: here are some points to help you: * What issues does the pull request solve? Please tag them so that they will get automatically closed once the PR is merged * What is the feature? (if applicable) * How did you implement the solution? * What areas of the library does it impact? ## Test Plan Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI. ### What's required for testing (prerequisites)? ### What are the steps to reproduce (after prerequisites)? ## Compatibility | OS | Implemented | | ------- | :---------: | | iOS | ✅ | | MacOS | ✅❌ | | Android | ✅ | | Web | ✅❌ |
This commit is contained in:
@@ -10,8 +10,10 @@ package com.horcrux.svg;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.util.SparseArray;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.bridge.Dynamic;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.PointerEvents;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
@@ -285,24 +287,37 @@ class SvgViewManager extends ReactViewManager
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHitSlop(SvgView view, @Nullable ReadableMap hitSlopMap) {
|
||||
public void setHitSlop(SvgView view, Dynamic hitSlop) {
|
||||
// we don't call super here since its signature changed in RN 0.69 and we want backwards
|
||||
// compatibility
|
||||
if (hitSlopMap != null) {
|
||||
view.setHitSlopRect(
|
||||
new Rect(
|
||||
hitSlopMap.hasKey("left")
|
||||
? (int) PixelUtil.toPixelFromDIP(hitSlopMap.getDouble("left"))
|
||||
: 0,
|
||||
hitSlopMap.hasKey("top")
|
||||
? (int) PixelUtil.toPixelFromDIP(hitSlopMap.getDouble("top"))
|
||||
: 0,
|
||||
hitSlopMap.hasKey("right")
|
||||
? (int) PixelUtil.toPixelFromDIP(hitSlopMap.getDouble("right"))
|
||||
: 0,
|
||||
hitSlopMap.hasKey("bottom")
|
||||
? (int) PixelUtil.toPixelFromDIP(hitSlopMap.getDouble("bottom"))
|
||||
: 0));
|
||||
switch (hitSlop.getType()) {
|
||||
case Map:
|
||||
ReadableMap hitSlopMap = hitSlop.asMap();
|
||||
view.setHitSlopRect(
|
||||
new Rect(
|
||||
hitSlopMap.hasKey("left")
|
||||
? (int) PixelUtil.toPixelFromDIP(hitSlopMap.getDouble("left"))
|
||||
: 0,
|
||||
hitSlopMap.hasKey("top")
|
||||
? (int) PixelUtil.toPixelFromDIP(hitSlopMap.getDouble("top"))
|
||||
: 0,
|
||||
hitSlopMap.hasKey("right")
|
||||
? (int) PixelUtil.toPixelFromDIP(hitSlopMap.getDouble("right"))
|
||||
: 0,
|
||||
hitSlopMap.hasKey("bottom")
|
||||
? (int) PixelUtil.toPixelFromDIP(hitSlopMap.getDouble("bottom"))
|
||||
: 0));
|
||||
break;
|
||||
case Number:
|
||||
int hitSlopValue = (int) PixelUtil.toPixelFromDIP(hitSlop.asDouble());
|
||||
view.setHitSlopRect(new Rect(hitSlopValue, hitSlopValue, hitSlopValue, hitSlopValue));
|
||||
break;
|
||||
default:
|
||||
FLog.w(ReactConstants.TAG, "Invalid type for 'hitSlop' value " + hitSlop.getType());
|
||||
/* falls through */
|
||||
case Null:
|
||||
view.setHitSlopRect(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ public class RNSVGSvgViewAndroidManagerDelegate<T extends View, U extends BaseVi
|
||||
mViewManager.setNeedsOffscreenAlphaCompositing(view, value == null ? false : (boolean) value);
|
||||
break;
|
||||
case "hitSlop":
|
||||
mViewManager.setHitSlop(view, (ReadableMap) value);
|
||||
mViewManager.setHitSlop(view, new DynamicFromObject(value));
|
||||
break;
|
||||
case "borderTopColor":
|
||||
mViewManager.setBorderTopColor(view, ColorPropConverter.getColor(value, view.getContext()));
|
||||
|
||||
@@ -49,7 +49,7 @@ public interface RNSVGSvgViewAndroidManagerInterface<T extends View> {
|
||||
void setBackfaceVisibility(T view, @Nullable String value);
|
||||
void setBorderStyle(T view, @Nullable String value);
|
||||
void setNeedsOffscreenAlphaCompositing(T view, boolean value);
|
||||
void setHitSlop(T view, @Nullable ReadableMap value);
|
||||
void setHitSlop(T view, Dynamic value);
|
||||
void setBorderTopColor(T view, @Nullable Integer value);
|
||||
void setNextFocusLeft(T view, int value);
|
||||
void setBorderTopRightRadius(T view, double value);
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
@property (nonatomic, assign) CGAffineTransform initialCTM;
|
||||
@property (nonatomic, assign) CGAffineTransform invInitialCTM;
|
||||
@property (nonatomic, assign) CGAffineTransform viewBoxTransform;
|
||||
@property (nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;
|
||||
|
||||
/**
|
||||
* define <ClipPath></ClipPath> content as clipPath template.
|
||||
|
||||
@@ -317,6 +317,15 @@ using namespace facebook::react;
|
||||
[self drawToContext:context withRect:[self bounds]];
|
||||
}
|
||||
|
||||
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
|
||||
{
|
||||
if (UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero)) {
|
||||
return [super pointInside:point withEvent:event];
|
||||
}
|
||||
CGRect hitFrame = UIEdgeInsetsInsetRect(self.bounds, self.hitTestEdgeInsets);
|
||||
return CGRectContainsPoint(hitFrame, point);
|
||||
}
|
||||
|
||||
- (RNSVGPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
|
||||
{
|
||||
CGPoint transformed = point;
|
||||
@@ -339,7 +348,8 @@ using namespace facebook::react;
|
||||
return (node.responsible || (node != hitChild)) ? hitChild : self;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
BOOL isPointInside = [self pointInside:point withEvent:event];
|
||||
return isPointInside ? self : nil;
|
||||
}
|
||||
|
||||
- (NSString *)getDataURLWithBounds:(CGRect)bounds
|
||||
|
||||
@@ -28,5 +28,20 @@ RCT_EXPORT_VIEW_PROPERTY(align, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(meetOrSlice, RNSVGVBMOS)
|
||||
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
|
||||
RCT_REMAP_VIEW_PROPERTY(color, tintColor, UIColor)
|
||||
RCT_CUSTOM_VIEW_PROPERTY(hitSlop, UIEdgeInsets, RNSVGSvgView)
|
||||
{
|
||||
if ([view respondsToSelector:@selector(setHitTestEdgeInsets:)]) {
|
||||
if (json) {
|
||||
UIEdgeInsets hitSlopInsets = [RCTConvert UIEdgeInsets:json];
|
||||
[view setHitTestEdgeInsets:UIEdgeInsetsMake(
|
||||
-hitSlopInsets.top,
|
||||
-hitSlopInsets.left,
|
||||
-hitSlopInsets.bottom,
|
||||
-hitSlopInsets.right)];
|
||||
} else {
|
||||
view.hitTestEdgeInsets = defaultView.hitTestEdgeInsets;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -26,6 +26,7 @@ import Test2327 from './src/Test2327';
|
||||
import Test2233 from './src/Test2233';
|
||||
import Test2366 from './src/Test2366';
|
||||
import Test2397 from './src/Test2397';
|
||||
import Test2407 from './src/Test2407';
|
||||
|
||||
export default function App() {
|
||||
return <ColorTest />;
|
||||
|
||||
34
apps/test-examples/src/Test2407.tsx
Normal file
34
apps/test-examples/src/Test2407.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import {SafeAreaView, StyleSheet} from 'react-native';
|
||||
import Svg, {Rect} from 'react-native-svg';
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<Svg
|
||||
width={200}
|
||||
height={200}
|
||||
viewBox="0 0 200 200"
|
||||
hitSlop={50}
|
||||
// hitSlop={{top: 50, left: 50, right: 50, bottom: 50}}
|
||||
onPress={() => console.log('press')}>
|
||||
<Rect
|
||||
width={200}
|
||||
height={200}
|
||||
x={0}
|
||||
y={0}
|
||||
fill="red"
|
||||
onPress={() => console.log('rect press')}
|
||||
/>
|
||||
</Svg>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
@@ -7,12 +7,12 @@ import type {
|
||||
MeasureOnSuccessCallback,
|
||||
NativeMethods,
|
||||
StyleProp,
|
||||
ViewProps,
|
||||
ViewStyle,
|
||||
} from 'react-native';
|
||||
import { findNodeHandle, Platform, StyleSheet } from 'react-native';
|
||||
import type {
|
||||
extractedProps,
|
||||
HitSlop,
|
||||
NumberProp,
|
||||
ResponderInstanceProps,
|
||||
} from '../lib/extract/types';
|
||||
@@ -25,6 +25,7 @@ import RNSVGSvgAndroid from '../fabric/AndroidSvgViewNativeComponent';
|
||||
import RNSVGSvgIOS from '../fabric/IOSSvgViewNativeComponent';
|
||||
import type { Spec } from '../fabric/NativeSvgViewModule';
|
||||
import extractOpacity from '../lib/extract/extractOpacity';
|
||||
import { ViewProps } from '../fabric/utils';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
svg: {
|
||||
@@ -34,7 +35,7 @@ const styles = StyleSheet.create({
|
||||
});
|
||||
const defaultStyle = styles.svg;
|
||||
|
||||
export interface SvgProps extends GProps, ViewProps {
|
||||
export interface SvgProps extends GProps, ViewProps, HitSlop {
|
||||
width?: NumberProp;
|
||||
height?: NumberProp;
|
||||
viewBox?: string;
|
||||
|
||||
@@ -61,7 +61,7 @@ interface NativeProps extends ViewProps {
|
||||
backfaceVisibility?: string;
|
||||
borderStyle?: string;
|
||||
needsOffscreenAlphaCompositing?: boolean;
|
||||
hitSlop?: HitSlop;
|
||||
hitSlop?: UnsafeMixed<HitSlop | null | number | undefined>;
|
||||
borderTopColor?: ColorValue;
|
||||
nextFocusLeft?: Int32;
|
||||
// TODO: those props are present in the `ReactPropGroup` but are not supported
|
||||
|
||||
@@ -6,6 +6,13 @@ import type { ViewProps } from './utils';
|
||||
import type { UnsafeMixed } from './codegenUtils';
|
||||
import { NumberProp } from '../lib/extract/types';
|
||||
|
||||
type HitSlop = Readonly<{
|
||||
left?: Float;
|
||||
top?: Float;
|
||||
right?: Float;
|
||||
bottom?: Float;
|
||||
}>;
|
||||
|
||||
interface NativeProps extends ViewProps {
|
||||
bbWidth?: UnsafeMixed<NumberProp>;
|
||||
bbHeight?: UnsafeMixed<NumberProp>;
|
||||
@@ -18,6 +25,7 @@ interface NativeProps extends ViewProps {
|
||||
tintColor?: ColorValue;
|
||||
color?: ColorValue;
|
||||
pointerEvents?: string;
|
||||
hitSlop?: UnsafeMixed<HitSlop | null | number | undefined>;
|
||||
}
|
||||
|
||||
export default codegenNativeComponent<NativeProps>('RNSVGSvgView', {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ViewProps as VP } from 'react-native';
|
||||
|
||||
type ViewProps = Omit<VP, 'pointerEvents'>;
|
||||
type ViewProps = Omit<VP, 'pointerEvents' | 'hitSlop'>;
|
||||
|
||||
export type { ViewProps };
|
||||
|
||||
@@ -2,6 +2,7 @@ import type {
|
||||
ColorValue,
|
||||
GestureResponderEvent,
|
||||
GestureResponderHandlers,
|
||||
Insets,
|
||||
LayoutChangeEvent,
|
||||
TransformsStyle,
|
||||
} from 'react-native';
|
||||
@@ -272,6 +273,10 @@ export interface CommonPathProps
|
||||
NativeProps,
|
||||
AccessibilityProps {}
|
||||
|
||||
export interface HitSlop {
|
||||
hitSlop?: Insets | number | undefined;
|
||||
}
|
||||
|
||||
export type ResponderInstanceProps = {
|
||||
touchableHandleResponderMove?: (e: GestureResponderEvent) => void;
|
||||
touchableHandleResponderGrant?: (e: GestureResponderEvent) => void;
|
||||
|
||||
Reference in New Issue
Block a user