Files
react-native/Libraries/NativeComponent/BaseViewConfig.ios.js
T
Eric Edouard 8993ffc82e Added border curve style prop ("Squircle" effect - iOS only) (#33783)
Summary:
<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->
NOTE: PR is based on https://github.com/facebook/react-native/pull/32017 which went stale for quite a long time but can now safely be closed

![](https://preview.redd.it/nuvl4746ys471.png?width=960&crop=smart&auto=webp&s=084a517a645364ac246b70b7fa8e0f2470cc7af3)

Since iOS 13+, it is possible to change the corner curve property on iOS in order to smoothen border radius and make it more "rounded" (also called "squircle")
Here's an [article](https://medium.com/arthurofbabylon/a-smooth-corner-radius-in-ios-54b80aa2d372) explaining in details what it is.
This property is also built in figma, but currently there is no way to implement this directly with react-native despite it being available natively on iOS.

Many open source react-native libraries were created in order to simulate this behaviour:
[react-native-super-ellipse-mask](https://github.com/everdrone/react-native-super-ellipse-mask)
[react-native-squircle-view](https://github.com/everdrone/react-native-squircle-view)
[react-native-figma-squircle](https://github.com/tienphaw/react-native-figma-squircle)

But they rely on creating an SVG shape with the smoothed corners and masking the view behind. This makes it not very performant (flickering on mounting was a common side-effect)

This PR aims at implementing the property natively.

PR for the docs update: https://github.com/facebook/react-native-website/pull/2785

## Changelog

<!-- Help reviewers and the release process by writing your own changelog entry. For an example, see:
https://github.com/facebook/react-native/wiki/Changelog
-->

[iOS] [Added] - Added `borderCurve` style prop for smooth border radius (squircle effect)

Pull Request resolved: https://github.com/facebook/react-native/pull/33783

Test Plan:
We used the RNTester app and added an example with `cornerCurve ` set to `'continuous'` (only on iOS).

As the difference is quite subtle, we also made some more tests to better illustrate the difference (these are not in the RN-tester app):

![IMG_0810](https://user-images.githubusercontent.com/19872411/133893536-26207c53-aade-4583-9eef-7a1739b6907b.PNG)

We overlapped two views with `position: absolute`, the one in the background has a red background and has `cornerRadius` set to `false`, and the one in the foreground is set to `true`. We can clearly see where the borders differs on the corners.

Reviewed By: sammy-SC

Differential Revision: D37883631

Pulled By: cipolleschi

fbshipit-source-id: 09f06de9628fa326323eba63875de30102c4a59e
2022-07-21 04:11:30 -07:00

349 lines
8.2 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
import {
DynamicallyInjectedByGestureHandler,
ConditionallyIgnoredEventHandlers,
} from './ViewConfigIgnore';
import ReactNativeStyleAttributes from '../Components/View/ReactNativeStyleAttributes';
import type {PartialViewConfigWithoutName} from './PlatformBaseViewConfig';
const bubblingEventTypes = {
// Generic Events
topPress: {
phasedRegistrationNames: {
bubbled: 'onPress',
captured: 'onPressCapture',
},
},
topChange: {
phasedRegistrationNames: {
bubbled: 'onChange',
captured: 'onChangeCapture',
},
},
topFocus: {
phasedRegistrationNames: {
bubbled: 'onFocus',
captured: 'onFocusCapture',
},
},
topBlur: {
phasedRegistrationNames: {
bubbled: 'onBlur',
captured: 'onBlurCapture',
},
},
topSubmitEditing: {
phasedRegistrationNames: {
bubbled: 'onSubmitEditing',
captured: 'onSubmitEditingCapture',
},
},
topEndEditing: {
phasedRegistrationNames: {
bubbled: 'onEndEditing',
captured: 'onEndEditingCapture',
},
},
topKeyPress: {
phasedRegistrationNames: {
bubbled: 'onKeyPress',
captured: 'onKeyPressCapture',
},
},
// Touch Events
topTouchStart: {
phasedRegistrationNames: {
bubbled: 'onTouchStart',
captured: 'onTouchStartCapture',
},
},
topTouchMove: {
phasedRegistrationNames: {
bubbled: 'onTouchMove',
captured: 'onTouchMoveCapture',
},
},
topTouchCancel: {
phasedRegistrationNames: {
bubbled: 'onTouchCancel',
captured: 'onTouchCancelCapture',
},
},
topTouchEnd: {
phasedRegistrationNames: {
bubbled: 'onTouchEnd',
captured: 'onTouchEndCapture',
},
},
// Experimental/Work in Progress Pointer Events (not yet ready for use)
topPointerCancel: {
phasedRegistrationNames: {
captured: 'onPointerCancelCapture',
bubbled: 'onPointerCancel',
},
},
topPointerDown: {
phasedRegistrationNames: {
captured: 'onPointerDownCapture',
bubbled: 'onPointerDown',
},
},
topPointerMove: {
phasedRegistrationNames: {
captured: 'onPointerMoveCapture',
bubbled: 'onPointerMove',
},
},
topPointerUp: {
phasedRegistrationNames: {
captured: 'onPointerUpCapture',
bubbled: 'onPointerUp',
},
},
topPointerEnter: {
phasedRegistrationNames: {
captured: 'onPointerEnterCapture',
bubbled: 'onPointerEnter',
skipBubbling: true,
},
},
topPointerLeave: {
phasedRegistrationNames: {
captured: 'onPointerLeaveCapture',
bubbled: 'onPointerLeave',
skipBubbling: true,
},
},
topPointerOver: {
phasedRegistrationNames: {
captured: 'onPointerOverCapture',
bubbled: 'onPointerOver',
},
},
topPointerOut: {
phasedRegistrationNames: {
captured: 'onPointerOutCapture',
bubbled: 'onPointerOut',
},
},
};
const directEventTypes = {
topAccessibilityAction: {
registrationName: 'onAccessibilityAction',
},
topAccessibilityTap: {
registrationName: 'onAccessibilityTap',
},
topMagicTap: {
registrationName: 'onMagicTap',
},
topAccessibilityEscape: {
registrationName: 'onAccessibilityEscape',
},
topLayout: {
registrationName: 'onLayout',
},
onGestureHandlerEvent: DynamicallyInjectedByGestureHandler({
registrationName: 'onGestureHandlerEvent',
}),
onGestureHandlerStateChange: DynamicallyInjectedByGestureHandler({
registrationName: 'onGestureHandlerStateChange',
}),
};
const validAttributesForNonEventProps = {
// View Props
accessible: true,
accessibilityActions: true,
accessibilityLabel: true,
accessibilityHint: true,
accessibilityLanguage: true,
accessibilityValue: true,
accessibilityViewIsModal: true,
accessibilityElementsHidden: true,
accessibilityIgnoresInvertColors: true,
testID: true,
backgroundColor: {process: require('../StyleSheet/processColor')},
backfaceVisibility: true,
opacity: true,
shadowColor: {process: require('../StyleSheet/processColor')},
shadowOffset: {diff: require('../Utilities/differ/sizesDiffer')},
shadowOpacity: true,
shadowRadius: true,
needsOffscreenAlphaCompositing: true,
overflow: true,
shouldRasterizeIOS: true,
transform: {diff: require('../Utilities/differ/matricesDiffer')},
accessibilityRole: true,
accessibilityState: true,
nativeID: true,
pointerEvents: true,
removeClippedSubviews: true,
borderRadius: true,
borderColor: {process: require('../StyleSheet/processColor')},
borderCurve: true,
borderWidth: true,
borderStyle: true,
hitSlop: {diff: require('../Utilities/differ/insetsDiffer')},
collapsable: true,
borderTopWidth: true,
borderTopColor: {process: require('../StyleSheet/processColor')},
borderRightWidth: true,
borderRightColor: {process: require('../StyleSheet/processColor')},
borderBottomWidth: true,
borderBottomColor: {process: require('../StyleSheet/processColor')},
borderLeftWidth: true,
borderLeftColor: {process: require('../StyleSheet/processColor')},
borderStartWidth: true,
borderStartColor: {process: require('../StyleSheet/processColor')},
borderEndWidth: true,
borderEndColor: {process: require('../StyleSheet/processColor')},
borderTopLeftRadius: true,
borderTopRightRadius: true,
borderTopStartRadius: true,
borderTopEndRadius: true,
borderBottomLeftRadius: true,
borderBottomRightRadius: true,
borderBottomStartRadius: true,
borderBottomEndRadius: true,
display: true,
zIndex: true,
// ShadowView properties
top: true,
right: true,
start: true,
end: true,
bottom: true,
left: true,
width: true,
height: true,
minWidth: true,
maxWidth: true,
minHeight: true,
maxHeight: true,
// Also declared as ViewProps
// borderTopWidth: true,
// borderRightWidth: true,
// borderBottomWidth: true,
// borderLeftWidth: true,
// borderStartWidth: true,
// borderEndWidth: true,
// borderWidth: true,
marginTop: true,
marginRight: true,
marginBottom: true,
marginLeft: true,
marginStart: true,
marginEnd: true,
marginVertical: true,
marginHorizontal: true,
margin: true,
paddingTop: true,
paddingRight: true,
paddingBottom: true,
paddingLeft: true,
paddingStart: true,
paddingEnd: true,
paddingVertical: true,
paddingHorizontal: true,
padding: true,
flex: true,
flexGrow: true,
flexShrink: true,
flexBasis: true,
flexDirection: true,
flexWrap: true,
justifyContent: true,
alignItems: true,
alignSelf: true,
alignContent: true,
position: true,
aspectRatio: true,
// Also declared as ViewProps
// overflow: true,
// display: true,
direction: true,
style: ReactNativeStyleAttributes,
};
// Props for bubbling and direct events
const validAttributesForEventProps = ConditionallyIgnoredEventHandlers({
onLayout: true,
onMagicTap: true,
// Accessibility
onAccessibilityAction: true,
onAccessibilityEscape: true,
onAccessibilityTap: true,
// PanResponder handlers
onMoveShouldSetResponder: true,
onMoveShouldSetResponderCapture: true,
onStartShouldSetResponder: true,
onStartShouldSetResponderCapture: true,
onResponderGrant: true,
onResponderReject: true,
onResponderStart: true,
onResponderEnd: true,
onResponderRelease: true,
onResponderMove: true,
onResponderTerminate: true,
onResponderTerminationRequest: true,
onShouldBlockNativeResponder: true,
// Touch events
onTouchStart: true,
onTouchMove: true,
onTouchEnd: true,
onTouchCancel: true,
// Pointer events
onPointerUp: true,
onPointerDown: true,
onPointerCancel: true,
onPointerEnter: true,
onPointerMove: true,
onPointerLeave: true,
onPointerOver: true,
onPointerOut: true,
});
/**
* On iOS, view managers define all of a component's props.
* All view managers extend RCTViewManager, and RCTViewManager declares these props.
*/
const PlatformBaseViewConfigIos: PartialViewConfigWithoutName = {
bubblingEventTypes,
directEventTypes,
validAttributes: {
...validAttributesForNonEventProps,
...validAttributesForEventProps,
},
};
export default PlatformBaseViewConfigIos;