diff --git a/apps/common/example/examples/PanResponder.tsx b/apps/common/example/examples/PanResponder.tsx index 198425920..8a78a1cf3 100644 --- a/apps/common/example/examples/PanResponder.tsx +++ b/apps/common/example/examples/PanResponder.tsx @@ -1,15 +1,8 @@ import React, {useRef} from 'react'; -import { - Animated, - PanResponder, - TouchableWithoutFeedback, - View, -} from 'react-native'; +import {Animated, PanResponder, View} from 'react-native'; import {G, Line, Path, Polyline, Svg, Text} from 'react-native-svg'; const AnimatedSvg = Animated.createAnimatedComponent(Svg); - const zeroDelta = {x: 0, y: 0}; - const PanExample = () => { const xy = useRef(new Animated.ValueXY()).current; let offset = zeroDelta; @@ -20,8 +13,7 @@ const PanExample = () => { const panResponder = useRef( PanResponder.create({ - onStartShouldSetPanResponder: () => true, - onMoveShouldSetPanResponderCapture: () => true, + onMoveShouldSetPanResponder: () => true, onPanResponderGrant: () => { xy.setOffset(offset); xy.setValue(zeroDelta); @@ -30,7 +22,7 @@ const PanExample = () => { useNativeDriver: false, }), onPanResponderRelease: () => { - xy.flattenOffset(); + xy.extractOffset(); }, }), ).current; @@ -40,32 +32,31 @@ const PanExample = () => { }; return ( - - - - - - STAR - - - - + + + + + STAR + + + ); }; diff --git a/apps/web-example/tsconfig.json b/apps/web-example/tsconfig.json deleted file mode 100644 index 0e6371f6f..000000000 --- a/apps/web-example/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "compilerOptions": {}, - "extends": "expo/tsconfig.base" -} diff --git a/package.json b/package.json index 23aef50e5..445408e79 100644 --- a/package.json +++ b/package.json @@ -114,6 +114,7 @@ "react": "^18.2.0", "react-native": "^0.72.3", "react-native-builder-bob": "^0.20.4", + "react-native-web": "^0.19.12", "react-native-windows": "^0.72.4", "react-test-renderer": "^18.2.0", "release-it": "^14.12.5", diff --git a/src/elements.web.ts b/src/elements.web.ts index d8b4e93cd..fbbc00a19 100644 --- a/src/elements.web.ts +++ b/src/elements.web.ts @@ -43,268 +43,111 @@ import type { PolylineProps } from './elements/Polyline'; import type { RadialGradientProps } from './elements/RadialGradient'; import type { RectProps } from './elements/Rect'; import type { StopProps } from './elements/Stop'; -import type { SvgProps } from './elements/Svg'; import type { SymbolProps } from './elements/Symbol'; import type { TextProps } from './elements/Text'; import type { TextPathProps } from './elements/TextPath'; import type { TSpanProps } from './elements/TSpan'; import type { UseProps } from './elements/Use'; -import type { BaseProps } from './web/types'; -import { encodeSvg, getBoundingClientRect } from './web/utils'; -import { WebShape } from './web/WebShape'; - -export class Circle extends WebShape { - tag = 'circle' as const; -} - -export class ClipPath extends WebShape { - tag = 'clipPath' as const; -} - -export class Defs extends WebShape { - tag = 'defs' as const; -} - -export class Ellipse extends WebShape { - tag = 'ellipse' as const; -} - -export class FeBlend extends WebShape { - tag = 'feBlend' as const; -} - -export class FeColorMatrix extends WebShape { - tag = 'feColorMatrix' as const; -} - -export class FeComponentTransfer extends WebShape< +import { BaseProps } from './types'; +import { createComponent } from './web/CreateWebComponent'; +import { SvgProps } from './ReactNativeSVG'; + +export const Circle = createComponent('circle'); +export const ClipPath = createComponent('clipPath'); +export const Defs = createComponent('defs'); +export const Ellipse = createComponent('ellipse'); + +const prepareGProps = (props: BaseProps & GProps) => { + const { x, y, ...rest } = props; + if ((x || y) && !rest.translate) { + rest.transform = `translate(${x || 0}, ${y || 0})`; + } + return rest; +}; + +export const FeBlend = createComponent('feBlend'); +export const FeColorMatrix = createComponent( + 'feColorMatrix' +); +export const FeComponentTransfer = createComponent< BaseProps & FeComponentTransferProps -> { - tag = 'feComponentTransfer' as const; -} - -export class FeComposite extends WebShape { - tag = 'feComposite' as const; -} - -export class FeConvolveMatrix extends WebShape< +>('feComponentTransfer'); +export const FeComposite = createComponent( + 'feComposite' +); +export const FeConvolveMatrix = createComponent< BaseProps & FeConvolveMatrixProps -> { - tag = 'feConvolveMatrix' as const; -} - -export class FeDiffuseLighting extends WebShape< +>('feConvolveMatrix'); +export const FeDiffuseLighting = createComponent< BaseProps & FeDiffuseLightingProps -> { - tag = 'feDiffuseLighting' as const; -} - -export class FeDisplacementMap extends WebShape< +>('feDiffuseLighting'); +export const FeDisplacementMap = createComponent< BaseProps & FeDisplacementMapProps -> { - tag = 'feDisplacementMap' as const; -} - -export class FeDistantLight extends WebShape { - tag = 'feDistantLight' as const; -} - -export class FeDropShadow extends WebShape { - tag = 'feDropShadow' as const; -} - -export class FeFlood extends WebShape { - tag = 'feFlood' as const; -} - -export class FeFuncA extends WebShape { - tag = 'feFuncA' as const; -} - -export class FeFuncB extends WebShape { - tag = 'feFuncB' as const; -} - -export class FeFuncG extends WebShape { - tag = 'feFuncG' as const; -} - -export class FeFuncR extends WebShape { - tag = 'feFuncR' as const; -} - -export class FeGaussianBlur extends WebShape { - tag = 'feGaussianBlur' as const; -} - -export class FeImage extends WebShape { - tag = 'feImage' as const; -} - -export class FeMerge extends WebShape { - tag = 'feMerge' as const; -} - -export class FeMergeNode extends WebShape { - tag = 'feMergeNode' as const; -} - -export class FeMorphology extends WebShape { - tag = 'feMorphology' as const; -} - -export class FeOffset extends WebShape { - tag = 'feOffset' as const; -} - -export class FePointLight extends WebShape { - tag = 'fePointLight' as const; -} - -export class FeSpecularLighting extends WebShape< +>('feDisplacementMap'); +export const FeDistantLight = createComponent( + 'feDistantLight' +); +export const FeDropShadow = createComponent( + 'feDropShadow' +); +export const FeFlood = createComponent('feFlood'); +export const FeFuncA = createComponent('feFuncA'); +export const FeFuncB = createComponent('feFuncB'); +export const FeFuncG = createComponent('feFuncG'); +export const FeFuncR = createComponent('feFuncR'); +export const FeGaussianBlur = createComponent( + 'feGaussianBlur' +); +export const FeImage = createComponent('feImage'); +export const FeMerge = createComponent('feMerge'); +export const FeMergeNode = createComponent( + 'feMergeNode' +); +export const FeMorphology = createComponent( + 'feMorphology' +); +export const FeOffset = createComponent('feOffset'); +export const FePointLight = createComponent( + 'fePointLight' +); +export const FeSpecularLighting = createComponent< BaseProps & FeSpecularLightingProps -> { - tag = 'feSpecularLighting' as const; -} - -export class FeSpotLight extends WebShape { - tag = 'feSpotLight' as const; -} - -export class FeTile extends WebShape { - tag = 'feTile' as const; -} - -export class FeTurbulence extends WebShape { - tag = 'feTurbulence' as const; -} - -export class Filter extends WebShape { - tag = 'filter' as const; -} - -export class ForeignObject extends WebShape { - tag = 'foreignObject' as const; -} - -export class G extends WebShape { - tag = 'g' as const; - prepareProps(props: BaseProps & GProps) { - const { x, y, ...rest } = props; - - if ((x || y) && !rest.translate) { - rest.translate = `${x || 0}, ${y || 0}`; - } - - return rest; - } -} - -export class Image extends WebShape { - tag = 'image' as const; -} - -export class Line extends WebShape { - tag = 'line' as const; -} - -export class LinearGradient extends WebShape { - tag = 'linearGradient' as const; -} - -export class Marker extends WebShape { - tag = 'marker' as const; -} - -export class Mask extends WebShape { - tag = 'mask' as const; -} - -export class Path extends WebShape { - tag = 'path' as const; -} - -export class Pattern extends WebShape { - tag = 'pattern' as const; -} - -export class Polygon extends WebShape { - tag = 'polygon' as const; -} - -export class Polyline extends WebShape { - tag = 'polyline' as const; -} - -export class RadialGradient extends WebShape { - tag = 'radialGradient' as const; -} - -export class Rect extends WebShape { - tag = 'rect' as const; -} - -export class Stop extends WebShape { - tag = 'stop' as const; -} - -export class Svg extends WebShape { - tag = 'svg' as const; - toDataURL( - callback: (data: string) => void, - options: { width?: number; height?: number } = {} - ) { - const ref = this.elementRef.current; - - if (ref === null) { - return; - } - - const rect = getBoundingClientRect(ref); - - const width = Number(options.width) || rect.width; - const height = Number(options.height) || rect.height; - - const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); - svg.setAttribute('viewBox', `0 0 ${rect.width} ${rect.height}`); - svg.setAttribute('width', String(width)); - svg.setAttribute('height', String(height)); - svg.appendChild(ref.cloneNode(true)); - - const img = new window.Image(); - img.onload = () => { - const canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - const context = canvas.getContext('2d'); - context?.drawImage(img, 0, 0); - callback(canvas.toDataURL().replace('data:image/png;base64,', '')); - }; - - img.src = `data:image/svg+xml;utf8,${encodeSvg( - new window.XMLSerializer().serializeToString(svg) - )}`; - } -} - -export class Symbol extends WebShape { - tag = 'symbol' as const; -} - -export class TSpan extends WebShape { - tag = 'tspan' as const; -} - -export class Text extends WebShape { - tag = 'text' as const; -} - -export class TextPath extends WebShape { - tag = 'textPath' as const; -} - -export class Use extends WebShape { - tag = 'use' as const; -} +>('feSpecularLighting'); +export const FeSpotLight = createComponent( + 'feSpotLight' +); +export const FeTile = createComponent('feTile'); +export const FeTurbulence = createComponent( + 'feTurbulence' +); +export const Filter = createComponent('filter'); +export const ForeignObject = createComponent( + 'foreignObject' +); +export const G = createComponent('g', prepareGProps); + +export const Image = createComponent('image'); +export const Line = createComponent('line'); +export const LinearGradient = createComponent( + 'linearGradient' +); +export const Mask = createComponent('mask'); +export const Marker = createComponent('marker'); +export const Path = createComponent('path'); +export const Pattern = createComponent('pattern'); +export const Polygon = createComponent('polygon'); +export const Polyline = createComponent('polyline'); +export const RadialGradient = createComponent( + 'radialGradient' +); +export const Rect = createComponent('rect'); +export const Stop = createComponent('stop'); +export const Svg = createComponent('svg'); + +export const Symbol = createComponent('symbol'); +export const Text = createComponent('text'); +export const TSpan = createComponent('tspan'); +export const TextPath = createComponent('textPath'); +export const Use = createComponent('use'); export default Svg; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 000000000..b4809d3aa --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,76 @@ +import { MutableRefObject, Ref, JSX } from 'react'; +import { ResponderConfig } from '../web/hooks/types'; +import type { ImageProps as RNImageProps } from 'react-native'; +import type { + NumberArray, + NumberProp, + TransformProps, +} from '../lib/extract/types'; + +export interface CreateComponentProps extends BaseProps { + tag: keyof JSX.IntrinsicElements; + elementRef?: MutableRefObject; + forwardedRef?: Ref | MutableRefObject; +} + +export interface Props extends ResponderConfig { + onBlur?: (e: BlurEvent) => void; + onFocus?: (e: FocusEvent) => void; + onLayout?: (event: LayoutEvent) => void | null | undefined; + onLongPress?: (event: PressEvent) => void | null | undefined; + onPressMove?: (event: PressEvent) => void | null | undefined; + onClick?: (event: PressEvent) => void | null | undefined; + onPress?: (event: PressEvent) => void | null | undefined; + onPressIn?: (event: PressEvent) => void | null | undefined; + onPressOut?: (event: PressEvent) => void | null | undefined; +} + +type BlurEvent = object; +type FocusEvent = object; +type PressEvent = object; +type LayoutEvent = object; +type EdgeInsetsProp = object; + +export interface BaseProps extends Props { + accessible?: boolean; + accessibilityLabel?: string; + accessibilityHint?: string; + accessibilityIgnoresInvertColors?: boolean; + accessibilityState?: object; + delayLongPress?: number; + delayPressIn?: number; + delaypressin?: number; + delayPressOut?: number; + disabled?: boolean; + hitSlop?: EdgeInsetsProp; + href?: RNImageProps['source'] | string | number; + nativeID?: string; + touchSoundDisabled?: boolean; + pressRetentionOffset?: EdgeInsetsProp; + rejectResponderTermination?: boolean; + + activeOpacity?: number; + transform?: TransformProps['transform']; + translate?: NumberArray; + translateX?: NumberProp; + translateY?: NumberProp; + scale?: NumberArray; + scaleX?: NumberProp; + scaleY?: NumberProp; + rotation?: NumberProp; + skewX?: NumberProp; + skewY?: NumberProp; + origin?: NumberArray; + originX?: NumberProp; + originY?: NumberProp; + + fontStyle?: string; + fontWeight?: NumberProp; + fontSize?: NumberProp; + fontFamily?: string; + style?: Iterable; + + // different transform props + gradientTransform?: TransformProps['transform']; + patternTransform?: TransformProps['transform']; +} diff --git a/src/web/CreateWebComponent.tsx b/src/web/CreateWebComponent.tsx new file mode 100644 index 000000000..f942bc9dd --- /dev/null +++ b/src/web/CreateWebComponent.tsx @@ -0,0 +1,21 @@ +import React, { memo, forwardRef, PropsWithoutRef } from 'react'; +import { WebShape } from './WebShape'; +import type { BaseProps } from '../types'; + +const CreateComponent = memo(forwardRef(WebShape)); + +export const createComponent = ( + tag: keyof JSX.IntrinsicElements, + prepareProps?: ( + props: PropsWithoutRef> + ) => PropsWithoutRef> +) => { + return forwardRef>((props, ref) => { + let createComponentProps = props; + if (prepareProps) { + createComponentProps = prepareProps(props); + } + + return ; + }); +}; diff --git a/src/web/WebShape.ts b/src/web/WebShape.ts deleted file mode 100644 index 9e00d2a99..000000000 --- a/src/web/WebShape.ts +++ /dev/null @@ -1,121 +0,0 @@ -import React from 'react'; -import { - GestureResponderEvent, - // @ts-ignore it is not seen in exports - unstable_createElement as createElement, -} from 'react-native'; - -import { BaseProps } from './types'; -import { prepare } from './utils/prepare'; -import { convertInt32ColorToRGBA } from './utils/convertInt32Color'; -import { camelCaseToDashed, hasTouchableProperty, remeasure } from './utils'; -import SvgTouchableMixin from '../lib/SvgTouchableMixin'; - -export class WebShape< - P extends BaseProps = BaseProps, -> extends React.Component

{ - [x: string]: unknown; - protected tag?: React.ElementType; - protected prepareProps(props: P) { - return props; - } - - elementRef = - React.createRef() as React.MutableRefObject; - - lastMergedProps: Partial

= {}; - - /** - * disclaimer: I am not sure why the props are wrapped in a `style` attribute here, but that's how reanimated calls it - */ - setNativeProps(props: { style: P }) { - const merged = Object.assign( - {}, - this.props, - this.lastMergedProps, - props.style - ); - this.lastMergedProps = merged; - const clean = prepare(this, this.prepareProps(merged)); - const current = this.elementRef.current; - if (current) { - for (const cleanAttribute of Object.keys(clean)) { - const cleanValue = clean[cleanAttribute as keyof typeof clean]; - switch (cleanAttribute) { - case 'ref': - case 'children': - break; - case 'style': - // style can be an object here or an array, so we convert it to an array and assign each element - for (const partialStyle of ([] as unknown[]).concat( - clean.style ?? [] - )) { - Object.assign(current.style, partialStyle); - } - break; - case 'fill': - if (cleanValue && typeof cleanValue === 'object') { - const value = cleanValue as { payload: number }; - current.setAttribute( - 'fill', - convertInt32ColorToRGBA(value.payload) - ); - } - break; - case 'stroke': - if (cleanValue && typeof cleanValue === 'object') { - const value = cleanValue as { payload: number }; - current.setAttribute( - 'stroke', - convertInt32ColorToRGBA(value.payload) - ); - } - break; - default: - // apply all other incoming prop updates as attributes on the node - // same logic as in https://github.com/software-mansion/react-native-reanimated/blob/d04720c82f5941532991b235787285d36d717247/src/reanimated2/js-reanimated/index.ts#L38-L39 - // @ts-expect-error TODO: fix this - current.setAttribute(camelCaseToDashed(cleanAttribute), cleanValue); - break; - } - } - } - } - - _remeasureMetricsOnActivation: () => void; - touchableHandleStartShouldSetResponder?: ( - e: GestureResponderEvent - ) => boolean; - - touchableHandleResponderMove?: (e: GestureResponderEvent) => void; - touchableHandleResponderGrant?: (e: GestureResponderEvent) => void; - touchableHandleResponderRelease?: (e: GestureResponderEvent) => void; - touchableHandleResponderTerminate?: (e: GestureResponderEvent) => void; - touchableHandleResponderTerminationRequest?: ( - e: GestureResponderEvent - ) => boolean; - - constructor(props: P) { - super(props); - - // Do not attach touchable mixin handlers if SVG element doesn't have a touchable prop - if (hasTouchableProperty(props)) { - SvgTouchableMixin(this); - } - - this._remeasureMetricsOnActivation = remeasure.bind(this); - } - - render(): JSX.Element { - if (!this.tag) { - throw new Error( - 'When extending `WebShape` you need to overwrite either `tag` or `render`!' - ); - } - this.lastMergedProps = {}; - return createElement( - this.tag, - prepare(this, this.prepareProps(this.props)) - ); - } -} diff --git a/src/web/WebShape.tsx b/src/web/WebShape.tsx new file mode 100644 index 000000000..edca5c4ff --- /dev/null +++ b/src/web/WebShape.tsx @@ -0,0 +1,84 @@ +import React, { useImperativeHandle, useRef } from 'react'; +import { + // @ts-ignore it is not seen in exports + unstable_createElement as createElement, +} from 'react-native'; +import { useMergeRefs } from './webUtils.web'; +import type { CreateComponentProps } from '../types'; +import { prepare } from './utils/prepare'; +import { useHandleEvents } from './hooks/useHandleEvents'; +import { setNativeProps } from './utils/setNativeProps'; +import { encodeSvg, getBoundingClientRect } from './utils'; + +export const WebShape = ( + props: CreateComponentProps, + forwardedRef: React.Ref +) => { + const { tag: Tag } = props; + const currentRef = useRef(null); + const { elementRef, rest, onClick } = useHandleEvents(currentRef, props); + const lastMergedProps = useRef>({}); + + setNativeProps( + rest, + elementRef as React.MutableRefObject, + lastMergedProps + ); + const prep = prepare({ ...rest, elementRef }); + const setRef = useMergeRefs(prep.ref, forwardedRef); + + if (Tag === 'svg') { + useImperativeHandle( + forwardedRef as React.MutableRefObject, + () => { + return { + toDataURL( + callback: (data: string) => void, + options: { width?: number; height?: number } = {} + ) { + const ref = elementRef.current as SVGElement; + + if (ref === null) { + return; + } + const rect = getBoundingClientRect(ref); + + const width = Number(options.width) || rect.width; + const height = Number(options.height) || rect.height; + + const svg = document.createElementNS( + 'http://www.w3.org/2000/svg', + 'svg' + ); + svg.setAttribute('viewBox', `0 0 ${rect.width} ${rect.height}`); + svg.setAttribute('width', String(width)); + svg.setAttribute('height', String(height)); + svg.appendChild(ref.cloneNode(true)); + + const img = new window.Image(); + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const context = canvas.getContext('2d'); + context?.drawImage(img, 0, 0); + callback( + canvas.toDataURL().replace('data:image/png;base64,', '') + ); + }; + img.src = `data:image/svg+xml;utf8,${encodeSvg( + new window.XMLSerializer().serializeToString(svg) + )}`; + }, + }; + }, + [] + ); + } + return createElement(Tag, { + ...prep, + onClick, + collapsable: undefined, + ref: setRef, + }); +}; diff --git a/src/web/hooks/types.ts b/src/web/hooks/types.ts new file mode 100644 index 000000000..1cacd6779 --- /dev/null +++ b/src/web/hooks/types.ts @@ -0,0 +1,66 @@ +type ClickEvent = any; +type ResponderEvent = any; +type KeyboardEvent = any; + +export type PressResponderConfig = Readonly<{ + // The gesture can be interrupted by a parent gesture, e.g., scroll. + // Defaults to true. + cancelable?: boolean | null | undefined; + // Whether to disable initialization of the press gesture. + disabled?: boolean | null | undefined; + // Duration (in addition to `delayPressStart`) after which a press gesture is + // considered a long press gesture. Defaults to 500 (milliseconds). + delayLongPress?: number | null | undefined; + // Duration to wait after press down before calling `onPressStart`. + delayPressStart?: number | null | undefined; + // Duration to wait after letting up before calling `onPressEnd`. + delayPressEnd?: number | null | undefined; + // Called when a long press gesture has been triggered. + onLongPress?: (event: ResponderEvent) => void | null | undefined; + // Called when a press gestute has been triggered. + onPress?: (event: ClickEvent) => void | null | undefined; + // Called when the press is activated to provide visual feedback. + onPressChange?: (event: ResponderEvent) => void | null | undefined; + // Called when the press is activated to provide visual feedback. + onPressStart?: (event: ResponderEvent) => void | null | undefined; + // Called when the press location moves. (This should rarely be used.) + onPressMove?: (event: ResponderEvent) => void | null | undefined; + // Called when the press is deactivated to undo visual feedback. + onPressEnd?: (event: ResponderEvent) => void | null | undefined; +}>; + +export type EventHandlers = Readonly<{ + onClick: (event: ClickEvent) => void; + onContextMenu: (event: ClickEvent) => void; + onKeyDown: (event: KeyboardEvent) => void; + onResponderGrant: (event: ResponderEvent) => void; + onResponderMove: (event: ResponderEvent) => void; + onResponderRelease: (event: ResponderEvent) => void; + onResponderTerminate: (event: ResponderEvent) => void; + onResponderTerminationRequest: (event: ResponderEvent) => boolean; + onStartShouldSetResponder: (event: ResponderEvent) => boolean; +}>; + +export type ResponderConfig = { + // Direct responder events dispatched directly to responder. Do not bubble. + onResponderEnd?: (e: ResponderEvent) => void; + onResponderGrant?: (e: ResponderEvent) => void | boolean; + onResponderMove?: (e: ResponderEvent) => void; + onResponderRelease?: (e: ResponderEvent) => void; + onResponderReject?: (e: ResponderEvent) => void; + onResponderStart?: (e: ResponderEvent) => void; + onResponderTerminate?: (e: ResponderEvent) => void; + onResponderTerminationRequest?: (e: ResponderEvent) => boolean; + // On pointer down, should this element become the responder? + onStartShouldSetResponder?: (e: ResponderEvent) => boolean; + onStartShouldSetResponderCapture?: (e: ResponderEvent) => boolean; + // On pointer move, should this element become the responder? + onMoveShouldSetResponder?: (e: ResponderEvent) => boolean; + onMoveShouldSetResponderCapture?: (e: ResponderEvent) => boolean; + // On scroll, should this element become the responder? Do no bubble + onScrollShouldSetResponder?: (e: ResponderEvent) => boolean; + onScrollShouldSetResponderCapture?: (e: ResponderEvent) => boolean; + // On text selection change, should this element become the responder? + onSelectionChangeShouldSetResponder?: (e: ResponderEvent) => boolean; + onSelectionChangeShouldSetResponderCapture?: (e: ResponderEvent) => boolean; +}; diff --git a/src/web/hooks/useHandleEvents.ts b/src/web/hooks/useHandleEvents.ts new file mode 100644 index 000000000..51d49fe39 --- /dev/null +++ b/src/web/hooks/useHandleEvents.ts @@ -0,0 +1,89 @@ +import { MutableRefObject, useMemo } from 'react'; +import { usePressEvents, useResponderEvents } from '../webUtils.web'; +import { hasResponderEvents, hasTouchableProperty } from '../utils/hasProperty'; +import type { CreateComponentProps } from '../../types'; +import { EventHandlers } from './types'; + +export function useHandleEvents( + elementRef: MutableRefObject, + props: CreateComponentProps +) { + const { + onMoveShouldSetResponder, + onMoveShouldSetResponderCapture, + onResponderEnd, + onResponderGrant, + onResponderMove, + onResponderReject, + onResponderRelease, + onResponderStart, + onResponderTerminate, + onResponderTerminationRequest, + onScrollShouldSetResponder, + onScrollShouldSetResponderCapture, + onSelectionChangeShouldSetResponder, + onSelectionChangeShouldSetResponderCapture, + onStartShouldSetResponder, + onStartShouldSetResponderCapture, + onPress, + onLongPress, + onPressIn, + onPressOut, + onPressMove, + delayPressIn, + delayPressOut, + delayLongPress, + ...rest + } = props; + + let pressEventHandlers: EventHandlers | undefined; + if (hasTouchableProperty(props)) { + const pressConfig = useMemo( + () => ({ + onLongPress, + onPress, + onPressMove, + delayLongPress, + delayPressStart: delayPressIn, + delayPressEnd: delayPressOut, + onPressStart: onPressIn, + onPressEnd: onPressOut, + }), + [ + delayPressIn, + delayPressOut, + onLongPress, + delayLongPress, + onPress, + onPressIn, + onPressOut, + ] + ); + pressEventHandlers = usePressEvents(elementRef, pressConfig); + useResponderEvents(elementRef, pressEventHandlers); + } else if (hasResponderEvents(props)) { + useResponderEvents(elementRef, { + onMoveShouldSetResponder, + onMoveShouldSetResponderCapture, + onResponderEnd, + onResponderGrant, + onResponderMove, + onResponderReject, + onResponderRelease, + onResponderStart, + onResponderTerminate, + onResponderTerminationRequest, + onScrollShouldSetResponder, + onScrollShouldSetResponderCapture, + onSelectionChangeShouldSetResponder, + onSelectionChangeShouldSetResponderCapture, + onStartShouldSetResponder, + onStartShouldSetResponderCapture, + }); + } + return { + elementRef, + rest, + onClick: pressEventHandlers?.onClick, + }; +} diff --git a/src/web/react-native-web.d.ts b/src/web/react-native-web.d.ts new file mode 100644 index 000000000..b94085cf6 --- /dev/null +++ b/src/web/react-native-web.d.ts @@ -0,0 +1,4 @@ +'use strict'; +declare module 'react-native-web/dist/modules/usePressEvents'; +declare module 'react-native-web/dist/modules/useResponderEvents'; +declare module 'react-native-web/dist/modules/useMergeRefs'; diff --git a/src/web/types.ts b/src/web/types.ts index 81add91b1..f5490d341 100644 --- a/src/web/types.ts +++ b/src/web/types.ts @@ -60,7 +60,7 @@ export interface BaseProps { | React.MutableRefObject; style?: Iterable; - // different tranform props + // different transform props gradientTransform?: TransformProps['transform']; patternTransform?: TransformProps['transform']; } diff --git a/src/web/utils/hasProperty.ts b/src/web/utils/hasProperty.ts new file mode 100644 index 000000000..c3347b9a5 --- /dev/null +++ b/src/web/utils/hasProperty.ts @@ -0,0 +1,33 @@ +import { BaseProps } from '../../types'; + +export function hasTouchableProperty(props: BaseProps): boolean { + return !!( + props.onPress || + props.onPressIn || + props.onPressOut || + props.onLongPress + ); +} + +export function hasResponderEvents(props: BaseProps): boolean { + const responderEvents = [ + 'onResponderGrant', + 'onResponderMove', + 'onResponderRelease', + 'onResponderTerminate', + 'onResponderTerminationRequest', + 'onMoveShouldSetResponder', + 'onMoveShouldSetResponderCapture', + 'onResponderEnd', + 'onResponderReject', + 'onResponderStart', + 'onScrollShouldSetResponder', + 'onScrollShouldSetResponderCapture', + 'onSelectionChangeShouldSetResponder', + 'onSelectionChangeShouldSetResponderCapture', + 'onStartShouldSetResponder', + 'onStartShouldSetResponderCapture', + ]; + + return responderEvents.some((event) => !!props[event as keyof BaseProps]); +} diff --git a/src/web/utils/index.ts b/src/web/utils/index.ts index 0feb34ad9..b533f31aa 100644 --- a/src/web/utils/index.ts +++ b/src/web/utils/index.ts @@ -1,74 +1,7 @@ -import { BaseProps } from '../types'; -import type { TransformProps } from '../../lib/extract/types'; -import { - transformsArrayToProps, - TransformsStyleArray, -} from '../../lib/extract/extractTransform'; - -export const hasTouchableProperty = (props: BaseProps) => - props.onPress || props.onPressIn || props.onPressOut || props.onLongPress; - export const camelCaseToDashed = (camelCase: string) => { return camelCase.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase()); }; -function stringifyTransformProps(transformProps: TransformProps) { - const transformArray = []; - if (transformProps.translate != null) { - transformArray.push(`translate(${transformProps.translate})`); - } - if (transformProps.translateX != null || transformProps.translateY != null) { - transformArray.push( - `translate(${transformProps.translateX || 0}, ${ - transformProps.translateY || 0 - })` - ); - } - if (transformProps.scale != null) { - transformArray.push(`scale(${transformProps.scale})`); - } - if (transformProps.scaleX != null || transformProps.scaleY != null) { - transformArray.push( - `scale(${transformProps.scaleX || 1}, ${transformProps.scaleY || 1})` - ); - } - // rotation maps to rotate, not to collide with the text rotate attribute (which acts per glyph rather than block) - if (transformProps.rotation != null) { - transformArray.push(`rotate(${transformProps.rotation})`); - } - if (transformProps.skewX != null) { - transformArray.push(`skewX(${transformProps.skewX})`); - } - if (transformProps.skewY != null) { - transformArray.push(`skewY(${transformProps.skewY})`); - } - return transformArray; -} - -export function parseTransformProp( - transform: TransformProps['transform'], - props?: BaseProps -) { - const transformArray: string[] = []; - - props && transformArray.push(...stringifyTransformProps(props)); - - if (Array.isArray(transform)) { - if (typeof transform[0] === 'number') { - transformArray.push(`matrix(${transform.join(' ')})`); - } else { - const stringifiedProps = transformsArrayToProps( - transform as TransformsStyleArray - ); - transformArray.push(...stringifyTransformProps(stringifiedProps)); - } - } else if (typeof transform === 'string') { - transformArray.push(transform); - } - - return transformArray.length ? transformArray.join(' ') : undefined; -} - export const getBoundingClientRect = (node: SVGElement) => { if (node) { const isElement = node.nodeType === 1; /* Node.ELEMENT_NODE */ @@ -79,39 +12,6 @@ export const getBoundingClientRect = (node: SVGElement) => { throw new Error('Can not get boundingClientRect of ' + node || 'undefined'); }; -const measureLayout = ( - node: SVGElement, - callback: ( - x: number, - y: number, - width: number, - height: number, - left: number, - top: number - ) => void -) => { - const relativeNode = node?.parentNode; - if (relativeNode) { - setTimeout(() => { - // @ts-expect-error TODO: handle it better - const relativeRect = getBoundingClientRect(relativeNode); - const { height, left, top, width } = getBoundingClientRect(node); - const x = left - relativeRect.left; - const y = top - relativeRect.top; - callback(x, y, width, height, left, top); - }, 0); - } -}; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function remeasure(this: any) { - const tag = this.state.touchable.responderID; - if (tag === null) { - return; - } - measureLayout(tag, this._handleQueryLayout); -} - /* Taken from here: https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0 */ export function encodeSvg(svgString: string) { return svgString diff --git a/src/web/utils/parseTransform.ts b/src/web/utils/parseTransform.ts new file mode 100644 index 000000000..e9da9c40e --- /dev/null +++ b/src/web/utils/parseTransform.ts @@ -0,0 +1,63 @@ +import type { TransformProps } from '../../lib/extract/types'; +import { + transformsArrayToProps, + TransformsStyleArray, +} from '../../lib/extract/extractTransform'; +import type { BaseProps } from '../../types'; + +export function parseTransformProp( + transform: TransformProps['transform'], + props?: BaseProps +) { + const transformArray: string[] = []; + + props && transformArray.push(...stringifyTransformProps(props)); + + if (Array.isArray(transform)) { + if (typeof transform[0] === 'number') { + transformArray.push(`matrix(${transform.join(' ')})`); + } else { + const stringifiedProps = transformsArrayToProps( + transform as TransformsStyleArray + ); + transformArray.push(...stringifyTransformProps(stringifiedProps)); + } + } else if (typeof transform === 'string') { + transformArray.push(transform); + } + + return transformArray.length ? transformArray.join(' ') : undefined; +} + +export function stringifyTransformProps(transformProps: TransformProps) { + const transformArray = []; + if (transformProps.translate != null) { + transformArray.push(`translate(${transformProps.translate})`); + } + if (transformProps.translateX != null || transformProps.translateY != null) { + transformArray.push( + `translate(${transformProps.translateX || 0}, ${ + transformProps.translateY || 0 + })` + ); + } + if (transformProps.scale != null) { + transformArray.push(`scale(${transformProps.scale})`); + } + if (transformProps.scaleX != null || transformProps.scaleY != null) { + transformArray.push( + `scale(${transformProps.scaleX || 1}, ${transformProps.scaleY || 1})` + ); + } + // rotation maps to rotate, not to collide with the text rotate attribute (which acts per glyph rather than block) + if (transformProps.rotation != null) { + transformArray.push(`rotate(${transformProps.rotation})`); + } + if (transformProps.skewX != null) { + transformArray.push(`skewX(${transformProps.skewX})`); + } + if (transformProps.skewY != null) { + transformArray.push(`skewY(${transformProps.skewY})`); + } + return transformArray; +} diff --git a/src/web/utils/prepare.ts b/src/web/utils/prepare.ts index 0679b5513..c59b05589 100644 --- a/src/web/utils/prepare.ts +++ b/src/web/utils/prepare.ts @@ -1,26 +1,19 @@ -import { - GestureResponderEvent, - type ImageProps as RNImageProps, -} from 'react-native'; -import { BaseProps } from '../types'; -import { WebShape } from '../WebShape'; -import { hasTouchableProperty, parseTransformProp } from '.'; +import type { NumberProp } from '../../lib/extract/types'; +import type { CreateComponentProps } from '../../types'; import { resolve } from '../../lib/resolve'; -import { NumberProp } from '../../lib/extract/types'; import { resolveAssetUri } from '../../lib/resolveAssetUri'; +import { parseTransformProp } from './parseTransform'; + /** * `react-native-svg` supports additional props that aren't defined in the spec. * This function replaces them in a spec conforming manner. - * - * @param {WebShape} self Instance given to us. - * @param {Object?} props Optional overridden props given to us. - * @returns {Object} Cleaned props object. - * @private */ -export const prepare = ( - self: WebShape, - props = self.props -) => { +interface PreparedComponentProps extends CreateComponentProps { + 'transform-origin'?: string; + ref?: unknown; +} + +export const prepare = (props: CreateComponentProps) => { const { transform, origin, @@ -34,48 +27,21 @@ export const prepare = ( forwardedRef, gradientTransform, patternTransform, - onPress, + elementRef, ...rest } = props; - const clean: { - onStartShouldSetResponder?: (e: GestureResponderEvent) => boolean; - onResponderMove?: (e: GestureResponderEvent) => void; - onResponderGrant?: (e: GestureResponderEvent) => void; - onResponderRelease?: (e: GestureResponderEvent) => void; - onResponderTerminate?: (e: GestureResponderEvent) => void; - onResponderTerminationRequest?: (e: GestureResponderEvent) => boolean; - onClick?: (e: GestureResponderEvent) => void; - transform?: string; - gradientTransform?: string; - patternTransform?: string; - 'transform-origin'?: string; - href?: RNImageProps['source'] | string | null; - style?: object; - ref?: unknown; - } = { - ...(hasTouchableProperty(props) - ? { - onStartShouldSetResponder: - self.touchableHandleStartShouldSetResponder, - onResponderTerminationRequest: - self.touchableHandleResponderTerminationRequest, - onResponderGrant: self.touchableHandleResponderGrant, - onResponderMove: self.touchableHandleResponderMove, - onResponderRelease: self.touchableHandleResponderRelease, - onResponderTerminate: self.touchableHandleResponderTerminate, - } - : null), - ...rest, - }; + const clean: PreparedComponentProps = rest; - if (origin != null) { + if (origin !== null && origin !== undefined) { clean['transform-origin'] = origin.toString().replace(',', ' '); - } else if (originX != null || originY != null) { + } else if ( + (originX !== null && originX !== undefined) || + (originY !== null && originY !== undefined) + ) { clean['transform-origin'] = `${originX || 0} ${originY || 0}`; } - // we do it like this because setting transform as undefined causes error in web const parsedTransform = parseTransformProp(transform, props); if (parsedTransform) { clean.transform = parsedTransform; @@ -90,11 +56,13 @@ export const prepare = ( } clean.ref = (el: SVGElement | null) => { - self.elementRef.current = el; + if (elementRef) { + elementRef.current = el; + } if (typeof forwardedRef === 'function') { forwardedRef(el); } else if (forwardedRef) { - forwardedRef.current = el; + (forwardedRef as React.MutableRefObject).current = el; } }; @@ -105,24 +73,21 @@ export const prepare = ( fontWeight?: NumberProp; } = {}; - if (fontFamily != null) { + if (fontFamily !== null && fontFamily !== undefined) { styles.fontFamily = fontFamily; } - if (fontSize != null) { + if (fontSize !== null && fontSize !== undefined) { styles.fontSize = fontSize; } - if (fontWeight != null) { + if (fontWeight !== null && fontWeight !== undefined) { styles.fontWeight = fontWeight; } - if (fontStyle != null) { + if (fontStyle !== null && fontStyle !== undefined) { styles.fontStyle = fontStyle; } - clean.style = resolve(style, styles); - if (onPress !== null) { - clean.onClick = props.onPress; - } if (props.href !== null && props.href !== undefined) { clean.href = resolveAssetUri(props.href)?.uri; } + clean.style = resolve(style, styles); return clean; }; diff --git a/src/web/utils/setNativeProps.ts b/src/web/utils/setNativeProps.ts new file mode 100644 index 000000000..c1e3bbaef --- /dev/null +++ b/src/web/utils/setNativeProps.ts @@ -0,0 +1,62 @@ +import { BaseProps, CreateComponentProps } from '../../types'; +import { camelCaseToDashed } from '.'; +import { convertInt32ColorToRGBA } from './convertInt32Color'; + +export const setNativeProps = ( + nativeProps: BaseProps, + elementRef: React.MutableRefObject, + lastMergedProps: React.MutableRefObject> +) => { + const { style, ...rest } = nativeProps; + if (elementRef.current) { + const current = elementRef.current; + + // Set attributes and styles + Object.keys(rest).forEach((cleanAttribute) => { + const cleanValue = rest[cleanAttribute as keyof typeof rest]; + switch (cleanAttribute) { + case 'ref': + case 'children': + break; + case 'style': + Object.assign(current.style, cleanValue); + break; + case 'fill': + if (cleanValue && typeof cleanValue === 'object') { + current.setAttribute( + 'fill', + convertInt32ColorToRGBA( + (cleanValue as { payload: number }).payload + ) + ); + } + break; + case 'stroke': + if (cleanValue && typeof cleanValue === 'object') { + current.setAttribute( + 'stroke', + convertInt32ColorToRGBA( + (cleanValue as { payload: number }).payload + ) + ); + } + break; + default: + // apply all other incoming prop updates as attributes on the node + // same logic as in https://github.com/software-mansion/react-native-reanimated/blob/d04720c82f5941532991b235787285d36d717247/src/reanimated2/js-reanimated/index.ts#L38-L39 + current.setAttribute( + camelCaseToDashed(cleanAttribute), + String(cleanValue) + ); + break; + } + }); + + const mergedProps = { ...rest, style }; + const mergedStyle = Array.isArray(style) + ? Object.assign({}, ...style) + : style; + const merged = { ...mergedProps, style: mergedStyle }; + lastMergedProps.current = merged; + } +}; diff --git a/src/web/webUtils.web.ts b/src/web/webUtils.web.ts new file mode 100644 index 000000000..4f6f26f89 --- /dev/null +++ b/src/web/webUtils.web.ts @@ -0,0 +1,37 @@ +'use strict'; + +import type { + EventHandlers, + PressResponderConfig, + ResponderConfig, +} from './hooks/types'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export let usePressEvents: ( + hostRef: any, + config: PressResponderConfig +) => EventHandlers; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export let useResponderEvents: (hostRef: any, config: ResponderConfig) => void; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export let useMergeRefs: ( + ...args: ReadonlyArray> +) => void; + +try { + usePressEvents = + // eslint-disable-next-line @typescript-eslint/no-var-requires + require('react-native-web/dist/modules/usePressEvents').default; +} catch (e) {} + +try { + useResponderEvents = + // eslint-disable-next-line @typescript-eslint/no-var-requires + require('react-native-web/dist/modules/useResponderEvents').default; +} catch (e) {} + +try { + useMergeRefs = + // eslint-disable-next-line @typescript-eslint/no-var-requires + require('react-native-web/dist/modules/useMergeRefs').default; +} catch (e) {} diff --git a/yarn.lock b/yarn.lock index b10922132..569243a5b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1459,7 +1459,7 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.0.0", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.18.6", "@babel/runtime@^7.8.4": version "7.25.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2" integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ== @@ -2541,6 +2541,11 @@ resolved "https://registry.yarnpkg.com/@react-native/normalize-colors/-/normalize-colors-0.72.0.tgz#14294b7ed3c1d92176d2a00df48456e8d7d62212" integrity sha512-285lfdqSXaqKuBbbtP9qL2tDrfxdOFtIMvkKadtleRQkdOxx+uzGvFr82KHmc/sSiMtfXGp7JnFYWVh4sFl7Yw== +"@react-native/normalize-colors@^0.74.1": + version "0.74.87" + resolved "https://registry.yarnpkg.com/@react-native/normalize-colors/-/normalize-colors-0.74.87.tgz#a814169d0ce4ce13ffebcda0a3a5a3f780ccd772" + integrity sha512-Xh7Nyk/MPefkb0Itl5Z+3oOobeG9lfLb7ZOY2DKpFnoCE1TzBmib9vMNdFaLdSxLIP+Ec6icgKtdzYg8QUPYzA== + "@react-native/virtualized-lists@^0.72.8": version "0.72.8" resolved "https://registry.yarnpkg.com/@react-native/virtualized-lists/-/virtualized-lists-0.72.8.tgz#a2c6a91ea0f1d40eb5a122fb063daedb92ed1dc3" @@ -3241,7 +3246,7 @@ arraybuffer.prototype.slice@^1.0.3: is-array-buffer "^3.0.4" is-shared-array-buffer "^1.0.2" -asap@~2.0.6: +asap@~2.0.3, asap@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== @@ -4094,6 +4099,13 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +cross-fetch@^3.1.5: + version "3.1.8" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" + integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== + dependencies: + node-fetch "^2.6.12" + cross-spawn@^6.0.0: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -4119,6 +4131,13 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== +css-in-js-utils@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz#640ae6a33646d401fc720c54fc61c42cd76ae2bb" + integrity sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A== + dependencies: + hyphenate-style-name "^1.0.3" + css-select@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" @@ -5184,6 +5203,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-loops@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/fast-loops/-/fast-loops-1.1.4.tgz#61bc77d518c0af5073a638c6d9d5c7683f069ce2" + integrity sha512-8dbd3XWoKCTms18ize6JmQF1SFnnfj5s0B7rRry22EofgMu7B6LKHVh+XfFqFGsqnbH54xgeO83PzpKI+ODhlg== + fast-xml-parser@^4.0.12: version "4.5.0" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz#2882b7d01a6825dfdf909638f2de0256351def37" @@ -5205,6 +5229,24 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fbjs-css-vars@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" + integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== + +fbjs@^3.0.4: + version "3.0.5" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.5.tgz#aa0edb7d5caa6340011790bd9249dbef8a81128d" + integrity sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg== + dependencies: + cross-fetch "^3.1.5" + fbjs-css-vars "^1.0.0" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^1.0.35" + fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -5825,6 +5867,11 @@ husky@^8.0.1: resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== +hyphenate-style-name@^1.0.3: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz#1797bf50369588b47b72ca6d5e65374607cf4436" + integrity sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw== + iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -5940,6 +5987,14 @@ ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +inline-style-prefixer@^6.0.1: + version "6.0.4" + resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-6.0.4.tgz#4290ed453ab0e4441583284ad86e41ad88384f44" + integrity sha512-FwXmZC2zbeeS7NzGjJ6pAiqRhXR0ugUShSNb6GApMl6da0/XGc4MOJsoWAywia52EEWbXNSy0pzkwz/+Y+swSg== + dependencies: + css-in-js-utils "^3.1.0" + fast-loops "^1.1.3" + inquirer@8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.0.tgz#f44f008dd344bbfc4b30031f45d984e034a3ac3a" @@ -7401,6 +7456,11 @@ memoize-one@^5.0.0: resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== +memoize-one@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" + integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -7872,7 +7932,7 @@ node-dir@^0.1.17: dependencies: minimatch "^3.0.2" -node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.7: +node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.12, node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -7947,7 +8007,7 @@ ob1@0.76.9: resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.76.9.tgz#a493e4b83a0fb39200de639804b5d06eed5599dc" integrity sha512-g0I/OLnSxf6OrN3QjSew3bTDJCdbZoWxnh8adh1z36alwCuGF1dgDeRA25bTYSakrG5WULSaWJPOdgnf1O/oQw== -object-assign@^4.1.1: +object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -8473,6 +8533,11 @@ possible-typed-array-names@^1.0.0: resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== +postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -8560,6 +8625,13 @@ promise.allsettled@1.0.5: get-intrinsic "^1.1.1" iterate-value "^1.0.2" +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== + dependencies: + asap "~2.0.3" + promise@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/promise/-/promise-8.3.0.tgz#8cb333d1edeb61ef23869fbb8a4ea0279ab60e0a" @@ -8777,6 +8849,20 @@ react-native-builder-bob@^0.20.4: optionalDependencies: jetifier "^2.0.0" +react-native-web@^0.19.12: + version "0.19.13" + resolved "https://registry.yarnpkg.com/react-native-web/-/react-native-web-0.19.13.tgz#2d84849bf0251ec0e3a8072fda7f9a7c29375331" + integrity sha512-etv3bN8rJglrRCp/uL4p7l8QvUNUC++QwDbdZ8CB7BvZiMvsxfFIRM1j04vxNldG3uo2puRd6OSWR3ibtmc29A== + dependencies: + "@babel/runtime" "^7.18.6" + "@react-native/normalize-colors" "^0.74.1" + fbjs "^3.0.4" + inline-style-prefixer "^6.0.1" + memoize-one "^6.0.0" + nullthrows "^1.1.1" + postcss-value-parser "^4.2.0" + styleq "^0.1.3" + react-native-windows@^0.72.4: version "0.72.38" resolved "https://registry.yarnpkg.com/react-native-windows/-/react-native-windows-0.72.38.tgz#5585d6d4e52055d2125dabba9d09c1cd1dc9e863" @@ -9375,6 +9461,11 @@ set-function-name@^2.0.1, set-function-name@^2.0.2: functions-have-names "^1.2.3" has-property-descriptors "^1.0.2" +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -9786,6 +9877,11 @@ strnum@^1.0.5: resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== +styleq@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/styleq/-/styleq-0.1.3.tgz#8efb2892debd51ce7b31dc09c227ad920decab71" + integrity sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA== + sudo-prompt@^9.0.0: version "9.2.1" resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.2.1.tgz#77efb84309c9ca489527a4e749f287e6bdd52afd" @@ -10094,6 +10190,11 @@ typescript@^5.1.6: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== +ua-parser-js@^1.0.35: + version "1.0.39" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.39.tgz#bfc07f361549bf249bd8f4589a4cccec18fd2018" + integrity sha512-k24RCVWlEcjkdOxYmVJgeD/0a1TiSpqLg+ZalVGV9lsnr4yqu0w7tX/x2xX6G4zpkgQnRf89lxuZ1wsbjXM8lw== + uglify-es@^3.1.9: version "3.3.9" resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677"