From f06644af418431ad741b3526f80db0d0dc838346 Mon Sep 17 00:00:00 2001 From: Eike Foken Date: Wed, 19 Feb 2025 09:44:19 +0100 Subject: [PATCH 1/2] Implement support --- apps/expo-app/package.json | 1 + flow-typed/npm/react-native-svg_vx.x.x.js | 12 + flow-typed/react-strict-dom.js | 51 + packages/react-strict-dom/src/native/index.js | 2 + .../modules/createStrictDOMSvgComponent.js | 892 ++++++++++++++++++ packages/react-strict-dom/src/native/svg.js | 464 +++++++++ .../src/shared/isSvgPropAllowed.js | 170 ++++ .../react-strict-dom/src/types/StrictProps.js | 2 + .../src/types/StrictReactDOMSvgProps.js | 488 ++++++++++ .../src/types/StrictSvgProps.js | 114 +++ packages/react-strict-dom/src/web/index.js | 3 +- .../modules/createStrictDOMSvgComponent.js | 79 ++ packages/react-strict-dom/src/web/runtime.js | 3 + packages/react-strict-dom/src/web/svg.js | 520 ++++++++++ .../tests/__mocks__/react-native-svg/index.js | 106 +++ 15 files changed, 2906 insertions(+), 1 deletion(-) create mode 100644 flow-typed/npm/react-native-svg_vx.x.x.js create mode 100644 packages/react-strict-dom/src/native/modules/createStrictDOMSvgComponent.js create mode 100644 packages/react-strict-dom/src/native/svg.js create mode 100644 packages/react-strict-dom/src/shared/isSvgPropAllowed.js create mode 100644 packages/react-strict-dom/src/types/StrictReactDOMSvgProps.js create mode 100644 packages/react-strict-dom/src/types/StrictSvgProps.js create mode 100644 packages/react-strict-dom/src/web/modules/createStrictDOMSvgComponent.js create mode 100644 packages/react-strict-dom/src/web/svg.js create mode 100644 packages/react-strict-dom/tests/__mocks__/react-native-svg/index.js diff --git a/apps/expo-app/package.json b/apps/expo-app/package.json index 96668bb5..ea04a3d1 100644 --- a/apps/expo-app/package.json +++ b/apps/expo-app/package.json @@ -18,6 +18,7 @@ "react": "19.2.0", "react-dom": "19.2.0", "react-native": "0.83.6", + "react-native-svg": "15.8.0", "react-native-web": "^0.21.0", "react-strict-dom": "0.0.55" }, diff --git a/flow-typed/npm/react-native-svg_vx.x.x.js b/flow-typed/npm/react-native-svg_vx.x.x.js new file mode 100644 index 00000000..779b2c61 --- /dev/null +++ b/flow-typed/npm/react-native-svg_vx.x.x.js @@ -0,0 +1,12 @@ +/** + * 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. + * + * @flow strict + */ + +declare module 'react-native-svg' { + declare module.exports: any; +} diff --git a/flow-typed/react-strict-dom.js b/flow-typed/react-strict-dom.js index 3cb785d5..1bea0d27 100644 --- a/flow-typed/react-strict-dom.js +++ b/flow-typed/react-strict-dom.js @@ -18,3 +18,54 @@ declare type ReactStrictDOMDataProps = { // internationalization type which is a string at runtime… // but Flow doesn't know that. declare type Stringish = string; + +declare type SVGCircleElement = {}; +declare type SVGClipPathElement = {}; +declare type SVGDefsElement = {}; +declare type SVGEllipseElement = {}; +declare type SVGFEBlendElement = {}; +declare type SVGFEColorMatrixElement = {}; +declare type SVGFEComponentTransferElement = {}; +declare type SVGFECompositeElement = {}; +declare type SVGFEConvolveMatrixElement = {}; +declare type SVGFEDiffuseLightingElement = {}; +declare type SVGFEDisplacementMapElement = {}; +declare type SVGFEDistantLightElement = {}; +declare type SVGFEDropShadowElement = {}; +declare type SVGFEFloodElement = {}; +declare type SVGFEFuncAElement = {}; +declare type SVGFEFuncBElement = {}; +declare type SVGFEFuncGElement = {}; +declare type SVGFEFuncRElement = {}; +declare type SVGFEGaussianBlurElement = {}; +declare type SVGFEImageElement = {}; +declare type SVGFEMergeElement = {}; +declare type SVGFEMergeNodeElement = {}; +declare type SVGFEMorphologyElement = {}; +declare type SVGFEOffsetElement = {}; +declare type SVGFEPointLightElement = {}; +declare type SVGFESpecularLightingElement = {}; +declare type SVGFESpotLightElement = {}; +declare type SVGFETileElement = {}; +declare type SVGFETurbulenceElement = {}; +declare type SVGFilterElement = {}; +declare type SVGForeignObjectElement = {}; +declare type SVGGElement = {}; +declare type SVGImageElement = {}; +declare type SVGLineElement = {}; +declare type SVGLinearGradientElement = {}; +declare type SVGMarkerElement = {}; +declare type SVGMaskElement = {}; +declare type SVGPathElement = {}; +declare type SVGPatternElement = {}; +declare type SVGPolygonElement = {}; +declare type SVGPolylineElement = {}; +declare type SVGRadialGradientElement = {}; +declare type SVGRectElement = {}; +declare type SVGStopElement = {}; +declare type SVGSVGElement = {}; +declare type SVGSymbolElement = {}; +declare type SVGTextElement = {}; +declare type SVGTextPathElement = {}; +declare type SVGTSpanElement = {}; +declare type SVGUseElement = {}; diff --git a/packages/react-strict-dom/src/native/index.js b/packages/react-strict-dom/src/native/index.js index e4b64e96..11e657ad 100644 --- a/packages/react-strict-dom/src/native/index.js +++ b/packages/react-strict-dom/src/native/index.js @@ -20,6 +20,7 @@ import typeof * as TStyleX from '@stylexjs/stylex'; import * as React from 'react'; import * as compat from './compat'; import * as html from './html'; +import * as svg from './svg'; import * as _css from './css'; import { ProvideCustomProperties } from './modules/ContextCustomProperties'; import { @@ -64,5 +65,6 @@ export { contexts, css, html, + svg, useViewportScale as useViewportScale_DO_NOT_USE }; diff --git a/packages/react-strict-dom/src/native/modules/createStrictDOMSvgComponent.js b/packages/react-strict-dom/src/native/modules/createStrictDOMSvgComponent.js new file mode 100644 index 00000000..8ed8d6d7 --- /dev/null +++ b/packages/react-strict-dom/src/native/modules/createStrictDOMSvgComponent.js @@ -0,0 +1,892 @@ +/** + * 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. + * + * @flow strict-local + */ + +import type { StrictSvgProps } from '../../types/StrictSvgProps'; + +import * as React from 'react'; +import { Animated } from 'react-native'; +import { + Circle, + ClipPath, + Defs, + Ellipse, + FeBlend, + FeColorMatrix, + FeComponentTransfer, + FeComposite, + FeConvolveMatrix, + FeDiffuseLighting, + FeDisplacementMap, + FeDistantLight, + FeDropShadow, + FeFlood, + FeFuncA, + FeFuncB, + FeFuncG, + FeFuncR, + FeGaussianBlur, + FeImage, + FeMerge, + FeMergeNode, + FeMorphology, + FeOffset, + FePointLight, + FeSpecularLighting, + FeSpotLight, + FeTile, + FeTurbulence, + Filter, + ForeignObject, + G, + Image, + Line, + LinearGradient, + Marker, + Mask, + Path, + Pattern, + Polygon, + Polyline, + RadialGradient, + Rect, + Stop, + Svg, + Symbol, + Text, + TextPath, + TSpan, + Use +} from 'react-native-svg'; +import { useNativeProps } from './useNativeProps'; +import { useStrictDOMElement } from './useStrictDOMElement'; +import * as css from '../css'; + +const RE_CAPTURE_VAR_NAME = /^var\(--(.*)\)$/; + +const nativeComponents: Readonly<{ + [string]: component(ref: React.RefSetter) +}> = { + circle: Circle, + clipPath: ClipPath, + defs: Defs, + ellipse: Ellipse, + feBlend: FeBlend, + feColorMatrix: FeColorMatrix, + feComponentTransfer: FeComponentTransfer, + feComposite: FeComposite, + feConvolveMatrix: FeConvolveMatrix, + feDiffuseLighting: FeDiffuseLighting, + feDisplacementMap: FeDisplacementMap, + feDistantLight: FeDistantLight, + feDropShadow: FeDropShadow, + feFlood: FeFlood, + feFuncA: FeFuncA, + feFuncB: FeFuncB, + feFuncG: FeFuncG, + feFuncR: FeFuncR, + feGaussianBlur: FeGaussianBlur, + feImage: FeImage, + feMerge: FeMerge, + feMergeNode: FeMergeNode, + feMorphology: FeMorphology, + feOffset: FeOffset, + fePointLight: FePointLight, + feSpecularLighting: FeSpecularLighting, + feSpotLight: FeSpotLight, + feTile: FeTile, + feTurbulence: FeTurbulence, + filter: Filter, + foreignObject: ForeignObject, + g: G, + image: Image, + line: Line, + linearGradient: LinearGradient, + marker: Marker, + mask: Mask, + path: Path, + pattern: Pattern, + polygon: Polygon, + polyline: Polyline, + radialGradient: RadialGradient, + rect: Rect, + stop: Stop, + svg: Svg, + symbol: Symbol, + text: Text, + textPath: TextPath, + tspan: TSpan, + use: Use +}; + +export function createStrictDOMSvgComponent

( + tagName: string, + _defaultProps?: P +): component(ref?: React.RefSetter, ...P) { + component Component(ref: React.RefSetter, ...props: P) { + let NativeComponent = nativeComponents[tagName]; + const elementRef = useStrictDOMElement(ref, { tagName }); + + const { + alignmentBaseline, + amplitude, + azimuth, + baseFrequency, + baselineShift, + bias, + clipPath, + clipRule, + color, + crossOrigin, + cx, + cy, + d, + diffuseConstant, + divisor, + dx, + dy, + edgeMode, + elevation, + exponent, + fill, + fillOpacity, + fillRule, + filter, + filterUnits, + floodColor, + floodOpacity, + fontFamily, + fontFeatureSettings, + fontSize, + fontStyle, + fontVariant, + fontWeight, + fx, + fy, + gradientTransform, + gradientUnits, + height, + in: _in, + in2, + inlineSize, + intercept, + k1, + k2, + k3, + k4, + kernelMatrix, + kernelUnitLength, + kerning, + lengthAdjust, + letterSpacing, + limitingConeAngle, + marker, + markerEnd, + markerHeight, + markerMid, + markerStart, + markerUnits, + markerWidth, + mask, + maskContentUnits, + maskType, + maskUnits, + method, + midLine, + mode, + numOctaves, + offset, + onLoad, + opacity, + operator, + order, + orient, + patternContentUnits, + patternTransform, + patternUnits, + points, + pointsAtX, + pointsAtY, + pointsAtZ, + preserveAlpha, + preserveAspectRatio, + primitiveUnits, + r, + radius, + refX, + refY, + rotate, + rx, + ry, + scale, + seed, + side, + slope, + spacing, + specularConstant, + specularExponent, + startOffset, + stdDeviation, + stitchTiles, + stopColor, + stopOpacity, + stroke, + strokeDasharray, + strokeDashoffset, + strokeLinecap, + strokeLinejoin, + strokeMiterlimit, + strokeOpacity, + strokeWidth, + surfaceScale, + tableValues, + targetX, + targetY, + textAnchor, + textDecoration, + textLength, + title, + transform, + transformOrigin, + values, + vectorEffect, + verticalAlign, + viewBox, + width, + wordSpacing, + x, + x1, + x2, + xChannelSelector, + xlinkHref, + xmlns, + xmlnsXlink, + y, + y1, + y2, + yChannelSelector, + z + } = props; + + /** + * Resolve global SVG and style props + */ + + const defaultProps = { + style: [ + _defaultProps?.style, + typeof height === 'number' && + typeof width === 'number' && + styles.aspectRatio(width, height) + ] + }; + + const { customProperties, nativeProps } = useNativeProps( + defaultProps, + props as $FlowFixMe, + { + provideInheritableStyle: false, + withInheritedStyle: false, + withTextStyle: false + } + ); + + // Tag-specific props + + if (alignmentBaseline != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.alignmentBaseline = alignmentBaseline; + } + if (amplitude != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.amplitude = amplitude; + } + if (azimuth != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.azimuth = azimuth; + } + if (baseFrequency != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.baseFrequency = baseFrequency; + } + if (baselineShift != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.baselineShift = baselineShift; + } + if (bias != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.bias = bias; + } + if (clipPath != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.clipPath = clipPath; + } + if (clipRule != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.clipRule = clipRule; + } + if (color != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.color = color.startsWith('var(') + ? customProperties?.[color.replace(RE_CAPTURE_VAR_NAME, '$1')] + : color; + } + if (crossOrigin != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.crossOrigin = crossOrigin; + } + if (cx != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.cx = cx; + } + if (cy != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.cy = cy; + } + if (d != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.d = d; + } + if (diffuseConstant != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.diffuseConstant = diffuseConstant; + } + if (divisor != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.divisor = divisor; + } + if (dx != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.dx = dx; + } + if (dy != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.dy = dy; + } + if (edgeMode != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.edgeMode = edgeMode; + } + if (elevation != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.elevation = elevation; + } + if (exponent != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.exponent = exponent; + } + if (fill != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.fill = fill.startsWith('var(') + ? customProperties?.[fill.replace(RE_CAPTURE_VAR_NAME, '$1')] + : fill; + } + if (fillOpacity != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.fillOpacity = fillOpacity; + } + if (fillRule != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.fillRule = fillRule; + } + if (filter != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.filter = filter; + } + if (filterUnits != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.filterUnits = filterUnits; + } + if (floodColor != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.floodColor = floodColor; + } + if (floodOpacity != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.floodOpacity = floodOpacity; + } + if (fontFamily != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.fontFamily = fontFamily; + } + if (fontFeatureSettings != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.fontFeatureSettings = fontFeatureSettings; + } + if (fontSize != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.fontSize = fontSize; + } + if (fontStyle != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.fontStyle = fontStyle; + } + if (fontVariant != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.fontVariant = fontVariant; + } + if (fontWeight != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.fontWeight = fontWeight; + } + if (fx != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.fx = fx; + } + if (fy != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.fy = fy; + } + if (gradientTransform != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.gradientTransform = gradientTransform; + } + if (gradientUnits != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.gradientUnits = gradientUnits; + } + if (height != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.height = height; + } + if (_in != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.in = _in; + } + if (in2 != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.in2 = in2; + } + if (inlineSize != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.inlineSize = inlineSize; + } + if (intercept != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.intercept = intercept; + } + if (k1 != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.k1 = k1; + } + if (k2 != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.k2 = k2; + } + if (k3 != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.k3 = k3; + } + if (k4 != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.k4 = k4; + } + if (kernelMatrix != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.kernelMatrix = kernelMatrix; + } + if (kernelUnitLength != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.kernelUnitLength = kernelUnitLength; + } + if (kerning != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.kerning = kerning; + } + if (lengthAdjust != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.lengthAdjust = lengthAdjust; + } + if (letterSpacing != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.letterSpacing = letterSpacing; + } + if (limitingConeAngle != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.limitingConeAngle = limitingConeAngle; + } + if (marker != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.marker = marker; + } + if (markerEnd != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.markerEnd = markerEnd; + } + if (markerHeight != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.markerHeight = markerHeight; + } + if (markerMid != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.markerMid = markerMid; + } + if (markerStart != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.markerStart = markerStart; + } + if (markerUnits != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.markerUnits = markerUnits; + } + if (markerWidth != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.markerWidth = markerWidth; + } + if (mask != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.mask = mask; + } + if (maskContentUnits != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.maskContentUnits = maskContentUnits; + } + if (maskType != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.maskType = maskType; + } + if (maskUnits != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.maskUnits = maskUnits; + } + if (method != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.method = method; + } + if (midLine != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.midLine = midLine; + } + if (mode != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.mode = mode; + } + if (numOctaves != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.numOctaves = numOctaves; + } + if (offset != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.offset = offset; + } + if (onLoad != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.onLoad = onLoad; + } + if (opacity != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.opacity = opacity; + } + if (operator != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.operator = operator; + } + if (order != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.order = order; + } + if (orient != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.orient = orient; + } + if (patternContentUnits != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.patternContentUnits = patternContentUnits; + } + if (patternTransform != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.patternTransform = patternTransform; + } + if (patternUnits != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.patternUnits = patternUnits; + } + if (points != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.points = points; + } + if (pointsAtX != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.pointsAtX = pointsAtX; + } + if (pointsAtY != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.pointsAtY = pointsAtY; + } + if (pointsAtZ != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.pointsAtZ = pointsAtZ; + } + if (preserveAlpha != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.preserveAlpha = preserveAlpha; + } + if (preserveAspectRatio != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.preserveAspectRatio = preserveAspectRatio; + } + if (primitiveUnits != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.primitiveUnits = primitiveUnits; + } + if (r != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.r = r; + } + if (radius != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.radius = radius; + } + if (refX != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.refX = refX; + } + if (refY != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.refY = refY; + } + if (rotate != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.rotate = rotate; + } + if (rx != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.rx = rx; + } + if (ry != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.ry = ry; + } + if (scale != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.scale = scale; + } + if (seed != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.seed = seed; + } + if (side != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.side = side; + } + if (slope != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.slope = slope; + } + if (spacing != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.spacing = spacing; + } + if (specularConstant != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.specularConstant = specularConstant; + } + if (specularExponent != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.specularExponent = specularExponent; + } + if (startOffset != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.startOffset = startOffset; + } + if (stdDeviation != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.stdDeviation = stdDeviation; + } + if (stitchTiles != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.stitchTiles = stitchTiles; + } + if (stopColor != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.stopColor = stopColor; + } + if (stopOpacity != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.stopOpacity = stopOpacity; + } + if (stroke != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.stroke = stroke.startsWith('var(') + ? customProperties?.[stroke.replace(RE_CAPTURE_VAR_NAME, '$1')] + : stroke; + } + if (strokeDasharray != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.strokeDasharray = strokeDasharray; + } + if (strokeDashoffset != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.strokeDashoffset = strokeDashoffset; + } + if (strokeLinecap != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.strokeLinecap = strokeLinecap; + } + if (strokeLinejoin != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.strokeLinejoin = strokeLinejoin; + } + if (strokeMiterlimit != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.strokeMiterlimit = strokeMiterlimit; + } + if (strokeOpacity != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.strokeOpacity = strokeOpacity; + } + if (strokeWidth != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.strokeWidth = strokeWidth; + } + if (surfaceScale != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.surfaceScale = surfaceScale; + } + if (tableValues != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.tableValues = tableValues; + } + if (targetX != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.targetX = targetX; + } + if (targetY != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.targetY = targetY; + } + if (textAnchor != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.textAnchor = textAnchor; + } + if (textDecoration != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.textDecoration = textDecoration; + } + if (textLength != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.textLength = textLength; + } + if (title != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.title = title; + } + if (transform != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.transform = transform; + } + if (transformOrigin != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.origin = transformOrigin; + } + if (values != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.values = values; + } + if (vectorEffect != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.vectorEffect = vectorEffect; + } + if (verticalAlign != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.verticalAlign = verticalAlign; + } + if (viewBox != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.viewBox = viewBox; + } + if (width != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.width = width; + } + if (wordSpacing != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.wordSpacing = wordSpacing; + } + if (x != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.x = x; + } + if (x1 != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.x1 = x1; + } + if (x2 != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.x2 = x2; + } + if (xChannelSelector != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.xChannelSelector = xChannelSelector; + } + if (xlinkHref != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.xlinkHref = xlinkHref; + } + if (xmlns != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.xmlns = xmlns; + } + if (xmlnsXlink != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.xmlnsXlink = xmlnsXlink; + } + if (y != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.y = y; + } + if (y1 != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.y1 = y1; + } + if (y2 != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.y2 = y2; + } + if (yChannelSelector != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.yChannelSelector = yChannelSelector; + } + if (z != null) { + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.z = z; + } + + // Component-specific props + + // $FlowFixMe[react-rule-hook-mutation] + nativeProps.ref = elementRef; + + // Use Animated components if necessary + if (nativeProps.animated === true) { + NativeComponent = Animated.createAnimatedComponent(NativeComponent); + } + + const element: React.Node = + typeof props.children === 'function' ? ( + props.children(nativeProps) + ) : ( + // $FlowFixMe[incompatible-type] + + ); + + return element; + } + + // eslint-disable-next-line no-unreachable + Component.displayName = `svg.${tagName}`; + return Component; +} + +const styles = css.create({ + aspectRatio: (width: number, height: number) => ({ + aspectRatio: width / height, + width, + height + }) +}); diff --git a/packages/react-strict-dom/src/native/svg.js b/packages/react-strict-dom/src/native/svg.js new file mode 100644 index 00000000..ef9f542e --- /dev/null +++ b/packages/react-strict-dom/src/native/svg.js @@ -0,0 +1,464 @@ +/** + * 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. + * + * @flow strict + */ + +import type { + StrictReactDOMCircleProps, + StrictReactDOMClipPathProps, + StrictReactDOMDefsProps, + StrictReactDOMEllipseProps, + StrictReactDOMFeBlendProps, + StrictReactDOMFeColorMatrixProps, + StrictReactDOMFeComponentTransferProps, + StrictReactDOMFeCompositeProps, + StrictReactDOMFeConvolveMatrixProps, + StrictReactDOMFeDiffuseLightingProps, + StrictReactDOMFeDisplacementMapProps, + StrictReactDOMFeDistantLightProps, + StrictReactDOMFeDropShadowProps, + StrictReactDOMFeFloodProps, + StrictReactDOMFeFuncAProps, + StrictReactDOMFeFuncBProps, + StrictReactDOMFeFuncGProps, + StrictReactDOMFeFuncRProps, + StrictReactDOMFeGaussianBlurProps, + StrictReactDOMFeImageProps, + StrictReactDOMFeMergeNodeProps, + StrictReactDOMFeMergeProps, + StrictReactDOMFeMorphologyProps, + StrictReactDOMFeOffsetProps, + StrictReactDOMFePointLightProps, + StrictReactDOMFeSpecularLightingProps, + StrictReactDOMFeSpotLightProps, + StrictReactDOMFeTileProps, + StrictReactDOMFeTurbulenceProps, + StrictReactDOMFilterProps, + StrictReactDOMForeignObjectProps, + StrictReactDOMGProps, + StrictReactDOMImageProps, + StrictReactDOMLinearGradientProps, + StrictReactDOMLineProps, + StrictReactDOMMarkerProps, + StrictReactDOMMaskProps, + StrictReactDOMPathProps, + StrictReactDOMPatternProps, + StrictReactDOMPolygonProps, + StrictReactDOMPolylineProps, + StrictReactDOMRadialGradientProps, + StrictReactDOMRectProps, + StrictReactDOMStopProps, + StrictReactDOMSvgProps, + StrictReactDOMSymbolProps, + StrictReactDOMTextPathProps, + StrictReactDOMTextProps, + StrictReactDOMTSpanProps, + StrictReactDOMUseProps +} from '../types/StrictReactDOMSvgProps'; + +// $FlowFixMe[nonstrict-import] +import { createStrictDOMSvgComponent as createStrictSvg } from './modules/createStrictDOMSvgComponent'; + +/** + * "circle" + */ +export const circle: component( + ref?: React.RefSetter, + ...StrictReactDOMCircleProps +) = createStrictSvg('circle') as $FlowFixMe; + +/** + * "clipPath" + */ +export const clipPath: component( + ref?: React.RefSetter, + ...StrictReactDOMClipPathProps +) = createStrictSvg('clipPath') as $FlowFixMe; + +/** + * "defs" + */ +export const defs: component( + ref?: React.RefSetter, + ...StrictReactDOMDefsProps +) = createStrictSvg('defs') as $FlowFixMe; + +/** + * "ellipse" + */ +export const ellipse: component( + ref?: React.RefSetter, + ...StrictReactDOMEllipseProps +) = createStrictSvg('ellipse') as $FlowFixMe; + +/** + * "feBlend" + */ +export const feBlend: component( + ref?: React.RefSetter, + ...StrictReactDOMFeBlendProps +) = createStrictSvg('feBlend') as $FlowFixMe; + +/** + * "feColorMatrix" + */ +export const feColorMatrix: component( + ref?: React.RefSetter, + ...StrictReactDOMFeColorMatrixProps +) = createStrictSvg('feColorMatrix') as $FlowFixMe; + +/** + * "feComponentTransfer" + */ +export const feComponentTransfer: component( + ref?: React.RefSetter, + ...StrictReactDOMFeComponentTransferProps +) = createStrictSvg('feComponentTransfer') as $FlowFixMe; + +/** + * "feFuncA" + */ +export const feFuncA: component( + ref?: React.RefSetter, + ...StrictReactDOMFeFuncAProps +) = createStrictSvg('feFuncA') as $FlowFixMe; + +/** + * "feFuncB" + */ +export const feFuncB: component( + ref?: React.RefSetter, + ...StrictReactDOMFeFuncBProps +) = createStrictSvg('feFuncB') as $FlowFixMe; + +/** + * "feFuncG" + */ +export const feFuncG: component( + ref?: React.RefSetter, + ...StrictReactDOMFeFuncGProps +) = createStrictSvg('feFuncG') as $FlowFixMe; + +/** + * "feFuncR" + */ +export const feFuncR: component( + ref?: React.RefSetter, + ...StrictReactDOMFeFuncRProps +) = createStrictSvg('feFuncR') as $FlowFixMe; + +/** + * "feComposite" + */ +export const feComposite: component( + ref?: React.RefSetter, + ...StrictReactDOMFeCompositeProps +) = createStrictSvg('feComposite') as $FlowFixMe; + +/** + * "feConvolveMatrix" + */ +export const feConvolveMatrix: component( + ref?: React.RefSetter, + ...StrictReactDOMFeConvolveMatrixProps +) = createStrictSvg('feConvolveMatrix') as $FlowFixMe; + +/** + * "feDiffuseLighting" + */ +export const feDiffuseLighting: component( + ref?: React.RefSetter, + ...StrictReactDOMFeDiffuseLightingProps +) = createStrictSvg('feDiffuseLighting') as $FlowFixMe; + +/** + * "feDisplacementMap" + */ +export const feDisplacementMap: component( + ref?: React.RefSetter, + ...StrictReactDOMFeDisplacementMapProps +) = createStrictSvg('feDisplacementMap') as $FlowFixMe; + +/** + * "feDistantLight" + */ +export const feDistantLight: component( + ref?: React.RefSetter, + ...StrictReactDOMFeDistantLightProps +) = createStrictSvg('feDistantLight') as $FlowFixMe; + +/** + * "feDropShadow" + */ +export const feDropShadow: component( + ref?: React.RefSetter, + ...StrictReactDOMFeDropShadowProps +) = createStrictSvg('feDropShadow') as $FlowFixMe; + +/** + * "feFlood" + */ +export const feFlood: component( + ref?: React.RefSetter, + ...StrictReactDOMFeFloodProps +) = createStrictSvg('feFlood') as $FlowFixMe; + +/** + * "feGaussianBlur" + */ +export const feGaussianBlur: component( + ref?: React.RefSetter, + ...StrictReactDOMFeGaussianBlurProps +) = createStrictSvg('feGaussianBlur') as $FlowFixMe; + +/** + * "feImage" + */ +export const feImage: component( + ref?: React.RefSetter, + ...StrictReactDOMFeImageProps +) = createStrictSvg('feImage') as $FlowFixMe; + +/** + * "feMerge" + */ +export const feMerge: component( + ref?: React.RefSetter, + ...StrictReactDOMFeMergeProps +) = createStrictSvg('feMerge') as $FlowFixMe; + +/** + * "feMergeNode" + */ +export const feMergeNode: component( + ref?: React.RefSetter, + ...StrictReactDOMFeMergeNodeProps +) = createStrictSvg('feMergeNode') as $FlowFixMe; + +/** + * "feMorphology" + */ +export const feMorphology: component( + ref?: React.RefSetter, + ...StrictReactDOMFeMorphologyProps +) = createStrictSvg('feMorphology') as $FlowFixMe; + +/** + * "feOffset" + */ +export const feOffset: component( + ref?: React.RefSetter, + ...StrictReactDOMFeOffsetProps +) = createStrictSvg('feOffset') as $FlowFixMe; + +/** + * "fePointLight" + */ +export const fePointLight: component( + ref?: React.RefSetter, + ...StrictReactDOMFePointLightProps +) = createStrictSvg('fePointLight') as $FlowFixMe; + +/** + * "feSpecularLighting" + */ +export const feSpecularLighting: component( + ref?: React.RefSetter, + ...StrictReactDOMFeSpecularLightingProps +) = createStrictSvg('feSpecularLighting') as $FlowFixMe; + +/** + * "feSpotLight" + */ +export const feSpotLight: component( + ref?: React.RefSetter, + ...StrictReactDOMFeSpotLightProps +) = createStrictSvg('feSpotLight') as $FlowFixMe; + +/** + * "feTile" + */ +export const feTile: component( + ref?: React.RefSetter, + ...StrictReactDOMFeTileProps +) = createStrictSvg('feTile') as $FlowFixMe; + +/** + * "feTurbulence" + */ +export const feTurbulence: component( + ref?: React.RefSetter, + ...StrictReactDOMFeTurbulenceProps +) = createStrictSvg('feTurbulence') as $FlowFixMe; + +/** + * "filter" + */ +export const filter: component( + ref?: React.RefSetter, + ...StrictReactDOMFilterProps +) = createStrictSvg('filter') as $FlowFixMe; + +/** + * "foreignObject" + */ +export const foreignObject: component( + ref?: React.RefSetter, + ...StrictReactDOMForeignObjectProps +) = createStrictSvg('foreignObject') as $FlowFixMe; + +/** + * "g" + */ +export const g: component( + ref?: React.RefSetter, + ...StrictReactDOMGProps +) = createStrictSvg('g') as $FlowFixMe; + +/** + * "image" + */ +export const image: component( + ref?: React.RefSetter, + ...StrictReactDOMImageProps +) = createStrictSvg('image') as $FlowFixMe; + +/** + * "line" + */ +export const line: component( + ref?: React.RefSetter, + ...StrictReactDOMLineProps +) = createStrictSvg('line') as $FlowFixMe; + +/** + * "linearGradient" + */ +export const linearGradient: component( + ref?: React.RefSetter, + ...StrictReactDOMLinearGradientProps +) = createStrictSvg('linearGradient') as $FlowFixMe; + +/** + * "marker" + */ +export const marker: component( + ref?: React.RefSetter, + ...StrictReactDOMMarkerProps +) = createStrictSvg('marker') as $FlowFixMe; + +/** + * "mask" + */ +export const mask: component( + ref?: React.RefSetter, + ...StrictReactDOMMaskProps +) = createStrictSvg('mask') as $FlowFixMe; + +/** + * "path" + */ +export const path: component( + ref?: React.RefSetter, + ...StrictReactDOMPathProps +) = createStrictSvg('path') as $FlowFixMe; + +/** + * "pattern" + */ +export const pattern: component( + ref?: React.RefSetter, + ...StrictReactDOMPatternProps +) = createStrictSvg('pattern') as $FlowFixMe; + +/** + * "polygon" + */ +export const polygon: component( + ref?: React.RefSetter, + ...StrictReactDOMPolygonProps +) = createStrictSvg('polygon') as $FlowFixMe; + +/** + * "polyline" + */ +export const polyline: component( + ref?: React.RefSetter, + ...StrictReactDOMPolylineProps +) = createStrictSvg('polyline') as $FlowFixMe; + +/** + * "radialGradient" + */ +export const radialGradient: component( + ref?: React.RefSetter, + ...StrictReactDOMRadialGradientProps +) = createStrictSvg('radialGradient') as $FlowFixMe; + +/** + * "rect" + */ +export const rect: component( + ref?: React.RefSetter, + ...StrictReactDOMRectProps +) = createStrictSvg('rect') as $FlowFixMe; + +/** + * "stop" + */ +export const stop: component( + ref?: React.RefSetter, + ...StrictReactDOMStopProps +) = createStrictSvg('stop') as $FlowFixMe; + +/** + * "svg" (inline) + */ +export const svg: component( + ref?: React.RefSetter, + ...StrictReactDOMSvgProps +) = createStrictSvg('svg') as $FlowFixMe; + +/** + * "symbol" + */ +export const symbol: component( + ref?: React.RefSetter, + ...StrictReactDOMSymbolProps +) = createStrictSvg('symbol') as $FlowFixMe; + +/** + * "tspan" + */ +export const tspan: component( + ref?: React.RefSetter, + ...StrictReactDOMTSpanProps +) = createStrictSvg('tSpan') as $FlowFixMe; + +/** + * "text" + */ +export const text: component( + ref?: React.RefSetter, + ...StrictReactDOMTextProps +) = createStrictSvg('text') as $FlowFixMe; + +/** + * "textPath" + */ +export const textPath: component( + ref?: React.RefSetter, + ...StrictReactDOMTextPathProps +) = createStrictSvg('textPath') as $FlowFixMe; + +/** + * "use" + */ +export const use: component( + ref?: React.RefSetter, + ...StrictReactDOMUseProps +) = createStrictSvg('use') as $FlowFixMe; diff --git a/packages/react-strict-dom/src/shared/isSvgPropAllowed.js b/packages/react-strict-dom/src/shared/isSvgPropAllowed.js new file mode 100644 index 00000000..af424215 --- /dev/null +++ b/packages/react-strict-dom/src/shared/isSvgPropAllowed.js @@ -0,0 +1,170 @@ +/** + * 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. + * + * @flow strict + */ + +import { isPropAllowed } from './isPropAllowed'; + +const strictAttributeSet: Set = new Set([ + 'children', + 'color', + 'crossOrigin', + 'height', + 'id', + 'lang', + 'max', + 'media', + 'method', + 'min', + 'name', + 'role', + 'style', + 'tabIndex', + 'target', + 'type', + 'width', + + 'alignmentBaseline', + 'amplitude', + 'azimuth', + 'baseFrequency', + 'baselineShift', + 'bias', + 'clipPath', + 'clipRule', + 'cx', + 'cy', + 'd', + 'diffuseConstant', + 'divisor', + 'dx', + 'dy', + 'edgeMode', + 'elevation', + 'exponent', + 'fill', + 'fillOpacity', + 'fillRule', + 'filter', + 'filterUnits', + 'floodColor', + 'floodOpacity', + 'fontFamily', + 'fontFeatureSettings', + 'fontSize', + 'fontStyle', + 'fontVariant', + 'fontWeight', + 'fx', + 'fy', + 'gradientTransform', + 'gradientUnits', + 'href', + 'in', + 'in2', + 'inlineSize', + 'intercept', + 'k1', + 'k2', + 'k3', + 'k4', + 'kernelMatrix', + 'kernelUnitLength', + 'kerning', + 'lengthAdjust', + 'letterSpacing', + 'limitingConeAngle', + 'marker', + 'markerEnd', + 'markerHeight', + 'markerMid', + 'markerStart', + 'markerUnits', + 'markerWidth', + 'mask', + 'maskContentUnits', + 'maskType', + 'maskUnits', + 'midLine', + 'mode', + 'numOctaves', + 'offset', + 'onLoad', + 'onLoad', + 'opacity', + 'operator', + 'order', + 'orient', + 'patternContentUnits', + 'patternTransform', + 'patternUnits', + 'points', + 'pointsAtX', + 'pointsAtY', + 'pointsAtZ', + 'preserveAlpha', + 'preserveAspectRatio', + 'primitiveUnits', + 'r', + 'radius', + 'refX', + 'refY', + 'rotate', + 'rx', + 'ry', + 'scale', + 'seed', + 'side', + 'slope', + 'spacing', + 'specularConstant', + 'specularExponent', + 'startOffset', + 'stdDeviation', + 'stitchTiles', + 'stopColor', + 'stopOpacity', + 'stroke', + 'strokeDasharray', + 'strokeDashoffset', + 'strokeLinecap', + 'strokeLinejoin', + 'strokeMiterlimit', + 'strokeOpacity', + 'strokeWidth', + 'surfaceScale', + 'tableValues', + 'targetX', + 'targetY', + 'textAnchor', + 'textDecoration', + 'textLength', + 'title', + 'transform', + 'transformOrigin', + 'values', + 'vectorEffect', + 'verticalAlign', + 'viewBox', + 'wordSpacing', + 'x', + 'x1', + 'x2', + 'xChannelSelector', + 'xlinkHref', + 'xmlns', + 'xmlnsXlink', + 'y', + 'y1', + 'y2', + 'yChannelSelector', + 'z' +]); + +export function isSvgPropAllowed(key: string): boolean { + return isPropAllowed(key) || strictAttributeSet.has(key); +} diff --git a/packages/react-strict-dom/src/types/StrictProps.js b/packages/react-strict-dom/src/types/StrictProps.js index a05ef0f9..3a1dd308 100644 --- a/packages/react-strict-dom/src/types/StrictProps.js +++ b/packages/react-strict-dom/src/types/StrictProps.js @@ -17,6 +17,7 @@ import type { StrictReactDOMLabelProps } from './StrictReactDOMLabelProps'; import type { StrictReactDOMListItemProps } from './StrictReactDOMListItemProps'; import type { StrictReactDOMOptionProps } from './StrictReactDOMOptionProps'; import type { StrictReactDOMSelectProps } from './StrictReactDOMSelectProps'; +import type { StrictReactDOMSvgProps } from './StrictReactDOMSvgProps'; import type { StrictReactDOMTextAreaProps } from './StrictReactDOMTextAreaProps'; export type StrictProps = Readonly<{ @@ -29,5 +30,6 @@ export type StrictProps = Readonly<{ ...StrictReactDOMListItemProps, ...StrictReactDOMOptionProps, ...StrictReactDOMSelectProps, + ...StrictReactDOMSvgProps, ...StrictReactDOMTextAreaProps }>; diff --git a/packages/react-strict-dom/src/types/StrictReactDOMSvgProps.js b/packages/react-strict-dom/src/types/StrictReactDOMSvgProps.js new file mode 100644 index 00000000..8a00d6e7 --- /dev/null +++ b/packages/react-strict-dom/src/types/StrictReactDOMSvgProps.js @@ -0,0 +1,488 @@ +/** + * 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. + * + * @flow strict + */ + +import type { StrictReactDOMProps } from './StrictReactDOMProps'; +import type { Styles } from './styles'; + +type Units = 'userSpaceOnUse' | 'objectBoundingBox'; + +type FontProps = Readonly<{ + fontFamily?: string, + fontSize?: string | number, + fontStyle?: 'normal' | 'italic' | 'oblique', + fontVariant?: 'normal' | 'small-caps', + fontWeight?: string | number, + kerning?: string | number, + letterSpacing?: string | number, + textAnchor?: 'start' | 'middle' | 'end', + textDecoration?: 'none' | 'underline' | 'overline' | 'line-through' | 'blink', + wordSpacing?: string | number +}>; + +type TransformProps = Readonly<{ + transform?: string, + transformOrigin?: string, + x?: string | number, + y?: string | number +}>; + +type PathProps = Readonly<{ + ...TransformProps, + ...Pick, + clipPath?: string, + clipRule?: 'evenodd' | 'nonzero', + color?: string, + fill?: string, + fillOpacity?: string | number, + fillRule?: 'evenodd' | 'nonzero', + filter?: string, + id?: ?string, + marker?: string, + markerEnd?: string, + markerMid?: string, + markerStart?: string, + mask?: string, + stroke?: string, + strokeDasharray?: string | number, + strokeDashoffset?: string | number, + strokeLinecap?: 'butt' | 'square' | 'round', + strokeLinejoin?: 'miter' | 'bevel' | 'round', + strokeMiterlimit?: string | number, + strokeOpacity?: string | number, + strokeWidth?: string | number, + vectorEffect?: 'none' | 'non-scaling-stroke' | 'default' | 'inherit' | 'uri' +}>; + +type TextProps = Readonly<{ + ...PathProps, + ...FontProps, + alignmentBaseline?: + | 'baseline' + | 'before-edge' + | 'text-before-edge' + | 'middle' + | 'central' + | 'after-edge' + | 'text-after-edge' + | 'ideographic' + | 'alphabetic' + | 'hanging' + | 'mathematical', + baselineShift?: string | number, + fontFeatureSettings?: string, + lengthAdjust?: 'spacing' | 'spacingAndGlyphs', + textLength?: string | number, + verticalAlign?: string | number +}>; + +export type StrictReactDOMSvgProps = Readonly<{ + ...StrictReactDOMProps, + ...StrictReactDOMGProps, + height?: string | number, + preserveAspectRatio?: string, + title?: string, + viewBox?: string, + width?: string | number, + xmlns?: string, + xmlnsXlink?: string +}>; + +export type StrictReactDOMCircleProps = Readonly<{ + ...PathProps, + cx?: string | number, + cy?: string | number, + opacity?: string | number, + r?: string | number +}>; + +export type StrictReactDOMClipPathProps = Readonly<{ + children?: React.Node, + id?: ?string +}>; + +export type StrictReactDOMDefsProps = Readonly<{ + children?: React.Node, + id?: ?string +}>; + +export type StrictReactDOMEllipseProps = Readonly<{ + ...PathProps, + cx?: string | number, + cy?: string | number, + opacity?: string | number, + rx?: string | number, + ry?: string | number +}>; + +export type StrictReactDOMForeignObjectProps = Readonly<{ + children?: React.Node, + height?: string | number, + width?: string | number, + x?: string | number, + y?: string | number +}>; + +export type StrictReactDOMGProps = Readonly<{ + ...PathProps, + ...FontProps, + children?: React.Node, + opacity?: string | number +}>; + +export type StrictReactDOMImageProps = Readonly<{ + ...PathProps, + height?: string | number, + href?: string, + onLoad?: $FlowFixMe, + opacity?: string | number, + preserveAspectRatio?: string, + width?: string | number, + x?: string | number, + xlinkHref?: string, + y?: string | number +}>; + +export type StrictReactDOMLineProps = Readonly<{ + ...PathProps, + opacity?: string | number, + x1?: string | number, + x2?: string | number, + y1?: string | number, + y2?: string | number +}>; + +export type StrictReactDOMLinearGradientProps = Readonly<{ + children?: React.Node, + gradientTransform?: string, + gradientUnits?: Units, + id?: ?string, + x1?: string | number, + x2?: string | number, + y1?: string | number, + y2?: string | number +}>; + +export type StrictReactDOMMarkerProps = Readonly<{ + children?: React.Node, + id?: ?string, + markerHeight?: string | number, + markerUnits?: 'strokeWidth' | 'userSpaceOnUse', + markerWidth?: string | number, + orient?: string | number, + preserveAspectRatio?: string, + refX?: string | number, + refY?: string | number, + viewBox?: string +}>; + +export type StrictReactDOMMaskProps = Readonly<{ + ...PathProps, + children?: React.Node, + height?: string | number, + id?: ?string, + maskContentUnits?: Units, + maskType?: string | number, + maskUnits?: Units, + style?: ?Styles, + width?: string | number, + x?: string | number, + y?: string | number +}>; + +export type StrictReactDOMPathProps = Readonly<{ + ...PathProps, + d?: string, + opacity?: string | number +}>; + +export type StrictReactDOMPatternProps = Readonly<{ + ...TransformProps, + children?: React.Node, + height?: string | number, + id?: ?string, + patternContentUnits?: Units, + patternTransform?: string, + patternUnits?: Units, + preserveAspectRatio?: string, + viewBox?: string, + width?: string | number, + x?: string | number, + y?: string | number +}>; + +export type StrictReactDOMPolygonProps = Readonly<{ + ...PathProps, + opacity?: string | number, + points?: string +}>; + +export type StrictReactDOMPolylineProps = Readonly<{ + ...PathProps, + opacity?: string | number, + points?: string +}>; + +export type StrictReactDOMRadialGradientProps = Readonly<{ + children?: React.Node, + cx?: string | number, + cy?: string | number, + fx?: string | number, + fy?: string | number, + gradientTransform?: string, + gradientUnits?: Units, + id?: ?string, + r?: string | number, + rx?: string | number, + ry?: string | number +}>; + +export type StrictReactDOMRectProps = Readonly<{ + ...PathProps, + height?: string | number, + opacity?: string | number, + rx?: string | number, + ry?: string | number, + width?: string | number, + x?: string | number, + y?: string | number +}>; + +export type StrictReactDOMStopProps = Readonly<{ + offset?: string | number, + stopColor?: string, + stopOpacity?: string | number +}>; + +export type StrictReactDOMSymbolProps = Readonly<{ + children?: React.Node, + id?: ?string, + opacity?: string | number, + preserveAspectRatio?: string, + viewBox?: string +}>; + +export type StrictReactDOMTSpanProps = Readonly<{ + ...PathProps, + ...FontProps, + children?: React.Node, + dx?: string | number, + dy?: string | number, + inlineSize?: string | number, + rotate?: string | number, + x?: string | number, + y?: string | number +}>; + +export type StrictReactDOMTextProps = Readonly<{ + ...TextProps, + children?: React.Node, + dx?: string | number, + dy?: string | number, + inlineSize?: string | number, + opacity?: string | number, + rotate?: string | number, + style?: ?Styles, + x?: string | number, + y?: string | number +}>; + +export type StrictReactDOMTextPathProps = Readonly<{ + ...TextProps, + children?: React.Node, + href?: string, + method?: 'align' | 'stretch', + midLine?: 'sharp' | 'smooth', + side?: string, + spacing?: 'auto' | 'exact', + startOffset?: string | number, + xlinkHref?: string +}>; + +export type StrictReactDOMUseProps = Readonly<{ + ...PathProps, + children?: React.Node, + height?: string | number, + href?: string, + opacity?: string | number, + width?: string | number, + x?: string | number, + xlinkHref?: string, + y?: string | number +}>; + +export type StrictReactDOMFeBlendProps = Readonly<{ + in?: string, + in2?: string, + mode?: 'normal' | 'multiply' | 'screen' | 'darken' | 'lighten' +}>; + +export type StrictReactDOMFeColorMatrixProps = Readonly<{ + in?: string, + type?: 'matrix' | 'saturate' | 'hueRotate' | 'luminanceToAlpha', + values?: string +}>; + +export type StrictReactDOMFeComponentTransferProps = Readonly<{ + children?: React.Node, + in?: string +}>; + +export type StrictReactDOMFeFuncAProps = Readonly<{ + amplitude?: string | number, + exponent?: string | number, + intercept?: string | number, + offset?: string | number, + slope?: string | number, + tableValues?: string | number, + type?: 'identity' | 'table' | 'discrete' | 'linear' | 'gamma' +}>; + +export type StrictReactDOMFeFuncBProps = StrictReactDOMFeFuncAProps; + +export type StrictReactDOMFeFuncGProps = StrictReactDOMFeFuncAProps; + +export type StrictReactDOMFeFuncRProps = StrictReactDOMFeFuncAProps; + +export type StrictReactDOMFeCompositeProps = Readonly<{ + in?: string, + in2?: string, + k1?: string | number, + k2?: string | number, + k3?: string | number, + k4?: string | number, + operator?: 'over' | 'in' | 'out' | 'atop' | 'xor' | 'arithmetic' +}>; + +export type StrictReactDOMFeConvolveMatrixProps = Readonly<{ + bias?: string | number, + divisor?: string | number, + edgeMode?: 'duplicate' | 'wrap' | 'none', + in?: string, + kernelMatrix?: string | number, + kernelUnitLength?: string | number, + order?: string | number, + preserveAlpha?: boolean | 'true' | 'false', + targetX?: string | number, + targetY?: string | number +}>; + +export type StrictReactDOMFeDiffuseLightingProps = Readonly<{ + diffuseConstant?: string | number, + in?: string, + kernelUnitLength?: string | number, + surfaceScale?: string | number +}>; + +export type StrictReactDOMFeDisplacementMapProps = Readonly<{ + in?: string, + in2?: string, + scale?: string | number, + xChannelSelector?: 'R' | 'G' | 'B' | 'A', + yChannelSelector?: 'R' | 'G' | 'B' | 'A' +}>; + +export type StrictReactDOMFeDistantLightProps = Readonly<{ + azimuth?: string | number, + elevation?: string | number +}>; + +export type StrictReactDOMFeDropShadowProps = Readonly<{ + dx?: string | number, + dy?: string | number, + floodColor?: string, + floodOpacity?: string | number, + in?: string, + stdDeviation?: string | number +}>; + +export type StrictReactDOMFeFloodProps = Readonly<{ + floodColor?: string, + floodOpacity?: string | number, + in?: string +}>; + +export type StrictReactDOMFeGaussianBlurProps = Readonly<{ + edgeMode?: 'duplicate' | 'wrap' | 'none', + in?: string, + stdDeviation?: string | number +}>; + +export type StrictReactDOMFeImageProps = Readonly<{ + crossOrigin?: 'anonymous' | 'use-credentials' | '', + href?: string, + preserveAspectRatio?: string +}>; + +export type StrictReactDOMFeMergeProps = Readonly<{ + children?: React.Node +}>; + +export type StrictReactDOMFeMergeNodeProps = Readonly<{ + in?: string +}>; + +export type StrictReactDOMFeMorphologyProps = Readonly<{ + in?: string, + operator?: 'erode' | 'dilate', + radius?: string | number +}>; + +export type StrictReactDOMFeOffsetProps = Readonly<{ + dx?: string | number, + dy?: string | number, + in?: string +}>; + +export type StrictReactDOMFePointLightProps = Readonly<{ + x?: string | number, + y?: string | number, + z?: string | number +}>; + +export type StrictReactDOMFeSpecularLightingProps = Readonly<{ + in?: string, + kernelUnitLength?: string | number, + specularConstant?: string | number, + specularExponent?: string | number, + surfaceScale?: string | number +}>; + +export type StrictReactDOMFeSpotLightProps = Readonly<{ + limitingConeAngle?: string | number, + pointsAtX?: string | number, + pointsAtY?: string | number, + pointsAtZ?: string | number, + specularExponent?: string | number, + x?: string | number, + y?: string | number, + z?: string | number +}>; + +export type StrictReactDOMFeTileProps = Readonly<{ + in?: string +}>; + +export type StrictReactDOMFeTurbulenceProps = Readonly<{ + baseFrequency?: string | number, + numOctaves?: string | number, + seed?: string | number, + stitchTiles?: 'stitch' | 'noStitch', + type?: 'fractalNoise' | 'turbulence' +}>; + +export type StrictReactDOMFilterProps = Readonly<{ + children?: React.Node, + filterUnits?: Units, + height?: string | number, + id?: ?string, + primitiveUnits?: Units, + width?: string | number, + x?: string | number, + y?: string | number +}>; diff --git a/packages/react-strict-dom/src/types/StrictSvgProps.js b/packages/react-strict-dom/src/types/StrictSvgProps.js new file mode 100644 index 00000000..16b8574f --- /dev/null +++ b/packages/react-strict-dom/src/types/StrictSvgProps.js @@ -0,0 +1,114 @@ +/** + * 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. + * + * @flow strict + */ + +import type { + StrictReactDOMCircleProps, + StrictReactDOMClipPathProps, + StrictReactDOMDefsProps, + StrictReactDOMEllipseProps, + StrictReactDOMFeBlendProps, + StrictReactDOMFeColorMatrixProps, + StrictReactDOMFeComponentTransferProps, + StrictReactDOMFeCompositeProps, + StrictReactDOMFeConvolveMatrixProps, + StrictReactDOMFeDiffuseLightingProps, + StrictReactDOMFeDisplacementMapProps, + StrictReactDOMFeDistantLightProps, + StrictReactDOMFeDropShadowProps, + StrictReactDOMFeFloodProps, + StrictReactDOMFeFuncAProps, + StrictReactDOMFeFuncBProps, + StrictReactDOMFeFuncGProps, + StrictReactDOMFeFuncRProps, + StrictReactDOMFeGaussianBlurProps, + StrictReactDOMFeImageProps, + StrictReactDOMFeMergeNodeProps, + StrictReactDOMFeMergeProps, + StrictReactDOMFeMorphologyProps, + StrictReactDOMFeOffsetProps, + StrictReactDOMFePointLightProps, + StrictReactDOMFeSpecularLightingProps, + StrictReactDOMFeSpotLightProps, + StrictReactDOMFeTileProps, + StrictReactDOMFeTurbulenceProps, + StrictReactDOMFilterProps, + StrictReactDOMForeignObjectProps, + StrictReactDOMGProps, + StrictReactDOMImageProps, + StrictReactDOMLinearGradientProps, + StrictReactDOMLineProps, + StrictReactDOMMarkerProps, + StrictReactDOMMaskProps, + StrictReactDOMPathProps, + StrictReactDOMPatternProps, + StrictReactDOMPolygonProps, + StrictReactDOMPolylineProps, + StrictReactDOMRadialGradientProps, + StrictReactDOMRectProps, + StrictReactDOMStopProps, + StrictReactDOMSvgProps, + StrictReactDOMSymbolProps, + StrictReactDOMTextPathProps, + StrictReactDOMTextProps, + StrictReactDOMTSpanProps, + StrictReactDOMUseProps +} from './StrictReactDOMSvgProps'; + +export type StrictSvgProps = Readonly<{ + ...StrictReactDOMCircleProps, + ...StrictReactDOMClipPathProps, + ...StrictReactDOMDefsProps, + ...StrictReactDOMEllipseProps, + ...StrictReactDOMFeBlendProps, + ...StrictReactDOMFeColorMatrixProps, + ...StrictReactDOMFeComponentTransferProps, + ...StrictReactDOMFeCompositeProps, + ...StrictReactDOMFeConvolveMatrixProps, + ...StrictReactDOMFeDiffuseLightingProps, + ...StrictReactDOMFeDisplacementMapProps, + ...StrictReactDOMFeDistantLightProps, + ...StrictReactDOMFeDropShadowProps, + ...StrictReactDOMFeFloodProps, + ...StrictReactDOMFeFuncAProps, + ...StrictReactDOMFeFuncBProps, + ...StrictReactDOMFeFuncGProps, + ...StrictReactDOMFeFuncRProps, + ...StrictReactDOMFeGaussianBlurProps, + ...StrictReactDOMFeImageProps, + ...StrictReactDOMFeMergeNodeProps, + ...StrictReactDOMFeMergeProps, + ...StrictReactDOMFeMorphologyProps, + ...StrictReactDOMFeOffsetProps, + ...StrictReactDOMFePointLightProps, + ...StrictReactDOMFeSpecularLightingProps, + ...StrictReactDOMFeSpotLightProps, + ...StrictReactDOMFeTileProps, + ...StrictReactDOMFeTurbulenceProps, + ...StrictReactDOMFilterProps, + ...StrictReactDOMForeignObjectProps, + ...StrictReactDOMGProps, + ...StrictReactDOMImageProps, + ...StrictReactDOMLinearGradientProps, + ...StrictReactDOMLineProps, + ...StrictReactDOMMarkerProps, + ...StrictReactDOMMaskProps, + ...StrictReactDOMPathProps, + ...StrictReactDOMPatternProps, + ...StrictReactDOMPolygonProps, + ...StrictReactDOMPolylineProps, + ...StrictReactDOMRadialGradientProps, + ...StrictReactDOMRectProps, + ...StrictReactDOMStopProps, + ...StrictReactDOMSvgProps, + ...StrictReactDOMSymbolProps, + ...StrictReactDOMTextPathProps, + ...StrictReactDOMTextProps, + ...StrictReactDOMTSpanProps, + ...StrictReactDOMUseProps +}>; diff --git a/packages/react-strict-dom/src/web/index.js b/packages/react-strict-dom/src/web/index.js index 36fe2c9f..24c82cce 100644 --- a/packages/react-strict-dom/src/web/index.js +++ b/packages/react-strict-dom/src/web/index.js @@ -16,6 +16,7 @@ import type { } from '@stylexjs/stylex'; import * as html from './html'; +import * as svg from './svg'; import * as css from '@stylexjs/stylex'; type StyleTheme = Theme; @@ -25,4 +26,4 @@ type StylesWithout = StyleXStylesWithout; export type { StaticStyles, StyleTheme, StyleVars, Styles, StylesWithout }; -export { css, html }; +export { css, html, svg }; diff --git a/packages/react-strict-dom/src/web/modules/createStrictDOMSvgComponent.js b/packages/react-strict-dom/src/web/modules/createStrictDOMSvgComponent.js new file mode 100644 index 00000000..fcfa5f4c --- /dev/null +++ b/packages/react-strict-dom/src/web/modules/createStrictDOMSvgComponent.js @@ -0,0 +1,79 @@ +/** + * 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. + * + * @flow strict + */ + +import type { CompiledStyles } from '@stylexjs/stylex/lib/StyleXTypes'; +import type { ReactDOMStyleProps } from '../../types/renderer.web'; +import type { StrictSvgProps } from '../../../dist/types/StrictSvgProps'; + +import * as React from 'react'; +import { errorMsg } from '../../shared/logUtils'; +import { isSvgPropAllowed } from '../../shared/isSvgPropAllowed'; +import { merge } from '../css/merge'; + +// $FlowFixMe[unclear-type] +function validateStrictProps(props: any) { + Object.keys(props).forEach((key) => { + const isValid = isSvgPropAllowed(key); + if (!isValid) { + errorMsg(`invalid prop "${key}"`); + delete props[key]; + } + }); +} + +export function createStrictDOMSvgComponent( + TagName: string, + defaultStyle: StrictSvgProps['style'] +): component(ref?: React.RefSetter, ...P) { + // NOTE: `debug-style` is not generated by `stylex.create` + // so it needs a type-cast + const debugStyle: CompiledStyles = { + $$css: true, + 'debug::name': `svg-${TagName}` as $FlowFixMe + }; + + component Component(ref?: React.RefSetter, ...props: P) { + /** + * get host props + */ + const { style, ...hostProps } = props; + validateStrictProps(hostProps); + + if (props.role != null) { + // "presentation" synonym has wider browser support + // $FlowFixMe[incompatible-type] + hostProps.role = props.role === 'none' ? 'presentation' : props.role; + } + + /** + * get host style props + */ + // $FlowFixMe[incompatible-type] + const hostStyleProps: ReactDOMStyleProps = merge([ + debugStyle, + defaultStyle, + style + ]); + + /** + * Construct tree + * + * Intentional flow error as we are asking for a more specific type + * than React itself. + */ + const element = ( + + ); + return element; + } + + // eslint-disable-next-line no-unreachable + Component.displayName = `svg.${TagName}`; + return Component; +} diff --git a/packages/react-strict-dom/src/web/runtime.js b/packages/react-strict-dom/src/web/runtime.js index e7b24009..5f32ab49 100644 --- a/packages/react-strict-dom/src/web/runtime.js +++ b/packages/react-strict-dom/src/web/runtime.js @@ -157,6 +157,8 @@ const sub: StrictReactDOMPropsStyle = styles.inline; // $FlowFixMe[incompatible-type] const sup: StrictReactDOMPropsStyle = styles.inline; // $FlowFixMe[incompatible-type] +const svg: StrictReactDOMPropsStyle = styles.img; +// $FlowFixMe[incompatible-type] const textarea: StrictReactDOMPropsStyle = [ styles.inlineblock, styles.textarea @@ -212,6 +214,7 @@ export const defaultStyles = { strong: strong as typeof strong, sub: sub as typeof sub, sup: sup as typeof sup, + svg: svg as typeof svg, textarea: textarea as typeof textarea, u: u as typeof u, ul: ul as typeof ul diff --git a/packages/react-strict-dom/src/web/svg.js b/packages/react-strict-dom/src/web/svg.js new file mode 100644 index 00000000..dce53db9 --- /dev/null +++ b/packages/react-strict-dom/src/web/svg.js @@ -0,0 +1,520 @@ +/** + * 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. + * + * @flow strict + */ + +import type { + StrictReactDOMCircleProps, + StrictReactDOMClipPathProps, + StrictReactDOMDefsProps, + StrictReactDOMEllipseProps, + StrictReactDOMFeBlendProps, + StrictReactDOMFeColorMatrixProps, + StrictReactDOMFeComponentTransferProps, + StrictReactDOMFeCompositeProps, + StrictReactDOMFeConvolveMatrixProps, + StrictReactDOMFeDiffuseLightingProps, + StrictReactDOMFeDisplacementMapProps, + StrictReactDOMFeDistantLightProps, + StrictReactDOMFeDropShadowProps, + StrictReactDOMFeFloodProps, + StrictReactDOMFeFuncAProps, + StrictReactDOMFeFuncBProps, + StrictReactDOMFeFuncGProps, + StrictReactDOMFeFuncRProps, + StrictReactDOMFeGaussianBlurProps, + StrictReactDOMFeImageProps, + StrictReactDOMFeMergeNodeProps, + StrictReactDOMFeMergeProps, + StrictReactDOMFeMorphologyProps, + StrictReactDOMFeOffsetProps, + StrictReactDOMFePointLightProps, + StrictReactDOMFeSpecularLightingProps, + StrictReactDOMFeSpotLightProps, + StrictReactDOMFeTileProps, + StrictReactDOMFeTurbulenceProps, + StrictReactDOMFilterProps, + StrictReactDOMForeignObjectProps, + StrictReactDOMGProps, + StrictReactDOMImageProps, + StrictReactDOMLinearGradientProps, + StrictReactDOMLineProps, + StrictReactDOMMarkerProps, + StrictReactDOMMaskProps, + StrictReactDOMPathProps, + StrictReactDOMPatternProps, + StrictReactDOMPolygonProps, + StrictReactDOMPolylineProps, + StrictReactDOMRadialGradientProps, + StrictReactDOMRectProps, + StrictReactDOMStopProps, + StrictReactDOMSvgProps, + StrictReactDOMSymbolProps, + StrictReactDOMTextPathProps, + StrictReactDOMTextProps, + StrictReactDOMTSpanProps, + StrictReactDOMUseProps +} from '../types/StrictReactDOMSvgProps'; + +import { createStrictDOMSvgComponent as createStrictSvg } from './modules/createStrictDOMSvgComponent'; +import { defaultStyles } from './runtime'; + +/** + * "circle" + */ +export const circle: component( + ref?: React.RefSetter, + ...StrictReactDOMCircleProps +) = createStrictSvg('circle'); + +/** + * "clipPath" + */ +export const clipPath: component( + ref?: React.RefSetter, + ...StrictReactDOMClipPathProps +) = createStrictSvg( + 'clipPath' +); + +/** + * "defs" + */ +export const defs: component( + ref?: React.RefSetter, + ...StrictReactDOMDefsProps +) = createStrictSvg('defs'); + +/** + * "ellipse" + */ +export const ellipse: component( + ref?: React.RefSetter, + ...StrictReactDOMEllipseProps +) = createStrictSvg('ellipse'); + +/** + * "feBlend" + */ +export const feBlend: component( + ref?: React.RefSetter, + ...StrictReactDOMFeBlendProps +) = createStrictSvg('feBlend'); + +/** + * "feColorMatrix" + */ +export const feColorMatrix: component( + ref?: React.RefSetter, + ...StrictReactDOMFeColorMatrixProps +) = createStrictSvg( + 'feColorMatrix' +); + +/** + * "feComponentTransfer" + */ +export const feComponentTransfer: component( + ref?: React.RefSetter, + ...StrictReactDOMFeComponentTransferProps +) = createStrictSvg< + SVGFEComponentTransferElement, + StrictReactDOMFeComponentTransferProps +>('feComponentTransfer'); + +/** + * "feFuncA" + */ +export const feFuncA: component( + ref?: React.RefSetter, + ...StrictReactDOMFeFuncAProps +) = createStrictSvg('feFuncA'); + +/** + * "feFuncB" + */ +export const feFuncB: component( + ref?: React.RefSetter, + ...StrictReactDOMFeFuncBProps +) = createStrictSvg('feFuncB'); + +/** + * "feFuncG" + */ +export const feFuncG: component( + ref?: React.RefSetter, + ...StrictReactDOMFeFuncGProps +) = createStrictSvg('feFuncG'); + +/** + * "feFuncR" + */ +export const feFuncR: component( + ref?: React.RefSetter, + ...StrictReactDOMFeFuncRProps +) = createStrictSvg('feFuncR'); + +/** + * "feComposite" + */ +export const feComposite: component( + ref?: React.RefSetter, + ...StrictReactDOMFeCompositeProps +) = createStrictSvg( + 'feComposite' +); + +/** + * "feConvolveMatrix" + */ +export const feConvolveMatrix: component( + ref?: React.RefSetter, + ...StrictReactDOMFeConvolveMatrixProps +) = createStrictSvg< + SVGFEConvolveMatrixElement, + StrictReactDOMFeConvolveMatrixProps +>('feConvolveMatrix'); + +/** + * "feDiffuseLighting" + */ +export const feDiffuseLighting: component( + ref?: React.RefSetter, + ...StrictReactDOMFeDiffuseLightingProps +) = createStrictSvg< + SVGFEDiffuseLightingElement, + StrictReactDOMFeDiffuseLightingProps +>('feDiffuseLighting'); + +/** + * "feDisplacementMap" + */ +export const feDisplacementMap: component( + ref?: React.RefSetter, + ...StrictReactDOMFeDisplacementMapProps +) = createStrictSvg< + SVGFEDisplacementMapElement, + StrictReactDOMFeDisplacementMapProps +>('feDisplacementMap'); + +/** + * "feDistantLight" + */ +export const feDistantLight: component( + ref?: React.RefSetter, + ...StrictReactDOMFeDistantLightProps +) = createStrictSvg< + SVGFEDistantLightElement, + StrictReactDOMFeDistantLightProps +>('feDistantLight'); + +/** + * "feDropShadow" + */ +export const feDropShadow: component( + ref?: React.RefSetter, + ...StrictReactDOMFeDropShadowProps +) = createStrictSvg( + 'feDropShadow' +); + +/** + * "feFlood" + */ +export const feFlood: component( + ref?: React.RefSetter, + ...StrictReactDOMFeFloodProps +) = createStrictSvg('feFlood'); + +/** + * "feGaussianBlur" + */ +export const feGaussianBlur: component( + ref?: React.RefSetter, + ...StrictReactDOMFeGaussianBlurProps +) = createStrictSvg< + SVGFEGaussianBlurElement, + StrictReactDOMFeGaussianBlurProps +>('feGaussianBlur'); + +/** + * "feImage" + */ +export const feImage: component( + ref?: React.RefSetter, + ...StrictReactDOMFeImageProps +) = createStrictSvg('feImage'); + +/** + * "feMerge" + */ +export const feMerge: component( + ref?: React.RefSetter, + ...StrictReactDOMFeMergeProps +) = createStrictSvg('feMerge'); + +/** + * "feMergeNode" + */ +export const feMergeNode: component( + ref?: React.RefSetter, + ...StrictReactDOMFeMergeNodeProps +) = createStrictSvg( + 'feMergeNode' +); + +/** + * "feMorphology" + */ +export const feMorphology: component( + ref?: React.RefSetter, + ...StrictReactDOMFeMorphologyProps +) = createStrictSvg( + 'feMorphology' +); + +/** + * "feOffset" + */ +export const feOffset: component( + ref?: React.RefSetter, + ...StrictReactDOMFeOffsetProps +) = createStrictSvg( + 'feOffset' +); + +/** + * "fePointLight" + */ +export const fePointLight: component( + ref?: React.RefSetter, + ...StrictReactDOMFePointLightProps +) = createStrictSvg( + 'fePointLight' +); + +/** + * "feSpecularLighting" + */ +export const feSpecularLighting: component( + ref?: React.RefSetter, + ...StrictReactDOMFeSpecularLightingProps +) = createStrictSvg< + SVGFESpecularLightingElement, + StrictReactDOMFeSpecularLightingProps +>('feSpecularLighting'); + +/** + * "feSpotLight" + */ +export const feSpotLight: component( + ref?: React.RefSetter, + ...StrictReactDOMFeSpotLightProps +) = createStrictSvg( + 'feSpotLight' +); + +/** + * "feTile" + */ +export const feTile: component( + ref?: React.RefSetter, + ...StrictReactDOMFeTileProps +) = createStrictSvg('feTile'); + +/** + * "feTurbulence" + */ +export const feTurbulence: component( + ref?: React.RefSetter, + ...StrictReactDOMFeTurbulenceProps +) = createStrictSvg( + 'feTurbulence' +); + +/** + * "filter" + */ +export const filter: component( + ref?: React.RefSetter, + ...StrictReactDOMFilterProps +) = createStrictSvg('filter'); + +/** + * "foreignObject" + */ +export const foreignObject: component( + ref?: React.RefSetter, + ...StrictReactDOMForeignObjectProps +) = createStrictSvg( + 'foreignObject' +); + +/** + * "g" + */ +export const g: component( + ref?: React.RefSetter, + ...StrictReactDOMGProps +) = createStrictSvg('g'); + +/** + * "image" + */ +export const image: component( + ref?: React.RefSetter, + ...StrictReactDOMImageProps +) = createStrictSvg('image'); + +/** + * "line" + */ +export const line: component( + ref?: React.RefSetter, + ...StrictReactDOMLineProps +) = createStrictSvg('line'); + +/** + * "linearGradient" + */ +export const linearGradient: component( + ref?: React.RefSetter, + ...StrictReactDOMLinearGradientProps +) = createStrictSvg< + SVGLinearGradientElement, + StrictReactDOMLinearGradientProps +>('linearGradient'); + +/** + * "marker" + */ +export const marker: component( + ref?: React.RefSetter, + ...StrictReactDOMMarkerProps +) = createStrictSvg('marker'); + +/** + * "mask" + */ +export const mask: component( + ref?: React.RefSetter, + ...StrictReactDOMMaskProps +) = createStrictSvg('mask'); + +/** + * "path" + */ +export const path: component( + ref?: React.RefSetter, + ...StrictReactDOMPathProps +) = createStrictSvg('path'); + +/** + * "pattern" + */ +export const pattern: component( + ref?: React.RefSetter, + ...StrictReactDOMPatternProps +) = createStrictSvg('pattern'); + +/** + * "polygon" + */ +export const polygon: component( + ref?: React.RefSetter, + ...StrictReactDOMPolygonProps +) = createStrictSvg('polygon'); + +/** + * "polyline" + */ +export const polyline: component( + ref?: React.RefSetter, + ...StrictReactDOMPolylineProps +) = createStrictSvg( + 'polyline' +); + +/** + * "radialGradient" + */ +export const radialGradient: component( + ref?: React.RefSetter, + ...StrictReactDOMRadialGradientProps +) = createStrictSvg< + SVGRadialGradientElement, + StrictReactDOMRadialGradientProps +>('radialGradient'); + +/** + * "rect" + */ +export const rect: component( + ref?: React.RefSetter, + ...StrictReactDOMRectProps +) = createStrictSvg('rect'); + +/** + * "stop" + */ +export const stop: component( + ref?: React.RefSetter, + ...StrictReactDOMStopProps +) = createStrictSvg('stop'); + +/** + * "svg" (inline) + */ +export const svg: component( + ref?: React.RefSetter, + ...StrictReactDOMSvgProps +) = createStrictSvg( + 'svg', + defaultStyles.svg +); + +/** + * "symbol" + */ +export const symbol: component( + ref?: React.RefSetter, + ...StrictReactDOMSymbolProps +) = createStrictSvg('symbol'); + +/** + * "tspan" + */ +export const tspan: component( + ref?: React.RefSetter, + ...StrictReactDOMTSpanProps +) = createStrictSvg('tSpan'); + +/** + * "text" + */ +export const text: component( + ref?: React.RefSetter, + ...StrictReactDOMTextProps +) = createStrictSvg('text'); + +/** + * "textPath" + */ +export const textPath: component( + ref?: React.RefSetter, + ...StrictReactDOMTextPathProps +) = createStrictSvg( + 'textPath' +); + +/** + * "use" + */ +export const use: component( + ref?: React.RefSetter, + ...StrictReactDOMUseProps +) = createStrictSvg('use'); diff --git a/packages/react-strict-dom/tests/__mocks__/react-native-svg/index.js b/packages/react-strict-dom/tests/__mocks__/react-native-svg/index.js new file mode 100644 index 00000000..d0b852e7 --- /dev/null +++ b/packages/react-strict-dom/tests/__mocks__/react-native-svg/index.js @@ -0,0 +1,106 @@ +/** + * 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. + */ + +export const Circle = 'Circle'; + +export const ClipPath = 'ClipPath'; + +export const Defs = 'Defs'; + +export const Ellipse = 'Ellipse'; + +export const FeBlend = 'FeBlend'; + +export const FeColorMatrix = 'FeColorMatrix'; + +export const FeComponentTransfer = 'FeComponentTransfer'; + +export const FeComposite = 'FeComposite'; + +export const FeConvolveMatrix = 'FeConvolveMatrix'; + +export const FeDiffuseLighting = 'FeDiffuseLighting'; + +export const FeDisplacementMap = 'FeDisplacementMap'; + +export const FeDistantLight = 'FeDistantLight'; + +export const FeDropShadow = 'FeDropShadow'; + +export const FeFlood = 'FeFlood'; + +export const FeFuncA = 'FeFuncA'; + +export const FeFuncB = 'FeFuncB'; + +export const FeFuncG = 'FeFuncG'; + +export const FeFuncR = 'FeFuncR'; + +export const FeGaussianBlur = 'FeGaussianBlur'; + +export const FeImage = 'FeImage'; + +export const FeMerge = 'FeMerge'; + +export const FeMergeNode = 'FeMergeNode'; + +export const FeMorphology = 'FeMorphology'; + +export const FeOffset = 'FeOffset'; + +export const FePointLight = 'FePointLight'; + +export const FeSpecularLighting = 'FeSpecularLighting'; + +export const FeSpotLight = 'FeSpotLight'; + +export const FeTile = 'FeTile'; + +export const FeTurbulence = 'FeTurbulence'; + +export const Filter = 'Filter'; + +export const ForeignObject = 'ForeignObject'; + +export const G = 'G'; + +export const Image = 'Image'; + +export const Line = 'Line'; + +export const LinearGradient = 'LinearGradient'; + +export const Marker = 'Marker'; + +export const Mask = 'Mask'; + +export const Path = 'Path'; + +export const Pattern = 'Pattern'; + +export const Polygon = 'Polygon'; + +export const Polyline = 'Polyline'; + +export const RadialGradient = 'RadialGradient'; + +export const Rect = 'Rect'; + +export const Stop = 'Stop'; + +export const Svg = 'Svg'; + +export const Symbol = 'Symbol'; + +export const Text = 'Text'; + +export const TextPath = 'TextPath'; + +export const TSpan = 'TSpan'; + +export const Use = 'Use'; From 02faee1c40630ea795eb9739184ebc274c29564d Mon Sep 17 00:00:00 2001 From: Eike Foken Date: Fri, 5 Jun 2026 14:33:47 +0200 Subject: [PATCH 2/2] Move everything to a react-strict-svg package --- package-lock.json | 119 ++++++++++++++++++ packages/react-strict-dom/src/native/index.js | 10 +- .../react-strict-dom/src/types/StrictProps.js | 2 - packages/react-strict-dom/src/web/index.js | 4 +- packages/react-strict-dom/src/web/runtime.js | 3 - packages/react-strict-svg/LICENSE | 21 ++++ packages/react-strict-svg/jest.config.js | 24 ++++ packages/react-strict-svg/jest.setup.js | 20 +++ packages/react-strict-svg/package.json | 51 ++++++++ packages/react-strict-svg/src/native/index.js | 12 ++ .../modules/createStrictDOMSvgComponent.js | 8 +- .../src/native/svg.js | 0 .../src/shared/isSvgPropAllowed.js | 6 +- .../react-strict-svg/src/shared/logUtils.js | 18 +++ .../src/types/StrictReactDOMProps.js | 13 ++ .../src/types/StrictReactDOMSvgProps.js | 4 +- .../src/types/StrictSvgProps.js | 0 packages/react-strict-svg/src/types/styles.js | 22 ++++ packages/react-strict-svg/src/web/index.js | 12 ++ .../modules/createStrictDOMSvgComponent.js | 17 +-- packages/react-strict-svg/src/web/runtime.js | 32 +++++ .../src/web/svg.js | 1 + .../react-strict-svg/tools/rollup.config.mjs | 90 +++++++++++++ .../tools/rollup/babelConfig.mjs | 29 +++++ 24 files changed, 489 insertions(+), 29 deletions(-) create mode 100644 packages/react-strict-svg/LICENSE create mode 100644 packages/react-strict-svg/jest.config.js create mode 100644 packages/react-strict-svg/jest.setup.js create mode 100644 packages/react-strict-svg/package.json create mode 100644 packages/react-strict-svg/src/native/index.js rename packages/{react-strict-dom => react-strict-svg}/src/native/modules/createStrictDOMSvgComponent.js (99%) rename packages/{react-strict-dom => react-strict-svg}/src/native/svg.js (100%) rename packages/{react-strict-dom => react-strict-svg}/src/shared/isSvgPropAllowed.js (94%) create mode 100644 packages/react-strict-svg/src/shared/logUtils.js create mode 100644 packages/react-strict-svg/src/types/StrictReactDOMProps.js rename packages/{react-strict-dom => react-strict-svg}/src/types/StrictReactDOMSvgProps.js (99%) rename packages/{react-strict-dom => react-strict-svg}/src/types/StrictSvgProps.js (100%) create mode 100644 packages/react-strict-svg/src/types/styles.js create mode 100644 packages/react-strict-svg/src/web/index.js rename packages/{react-strict-dom => react-strict-svg}/src/web/modules/createStrictDOMSvgComponent.js (81%) create mode 100644 packages/react-strict-svg/src/web/runtime.js rename packages/{react-strict-dom => react-strict-svg}/src/web/svg.js (99%) create mode 100644 packages/react-strict-svg/tools/rollup.config.mjs create mode 100644 packages/react-strict-svg/tools/rollup/babelConfig.mjs diff --git a/package-lock.json b/package-lock.json index 133050ee..2e9a9e31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,6 +68,7 @@ "react": "19.2.0", "react-dom": "19.2.0", "react-native": "0.83.6", + "react-native-svg": "15.8.0", "react-native-web": "^0.21.0", "react-strict-dom": "0.0.55" }, @@ -29386,6 +29387,40 @@ "react-native": "*" } }, + "node_modules/react-native-svg": { + "version": "15.8.0", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.8.0.tgz", + "integrity": "sha512-KHJzKpgOjwj1qeZzsBjxNdoIgv2zNCO9fVcoq2TEhTRsVV5DGTZ9JzUZwybd7q4giT/H3RdtqC3u44dWdO0Ffw==", + "license": "MIT", + "dependencies": { + "css-select": "^5.1.0", + "css-tree": "^1.1.3", + "warn-once": "0.1.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-svg/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/react-native-svg/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0" + }, "node_modules/react-native-web": { "version": "0.21.2", "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz", @@ -29595,6 +29630,10 @@ "resolved": "packages/scripts", "link": true }, + "node_modules/react-strict-svg": { + "resolved": "packages/react-strict-svg", + "link": true + }, "node_modules/react-test-renderer": { "version": "19.2.6", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.2.6.tgz", @@ -34150,6 +34189,12 @@ "makeerror": "1.0.12" } }, + "node_modules/warn-once": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.1.tgz", + "integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==", + "license": "MIT" + }, "node_modules/watchpack": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", @@ -35274,6 +35319,80 @@ "react-native": ">=0.82.0" } }, + "packages/react-strict-svg": { + "version": "0.0.55", + "dependencies": { + "@stylexjs/stylex": "^0.18.3", + "react-strict-dom": "0.0.55" + }, + "devDependencies": { + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^26.0.1", + "@rollup/plugin-node-resolve": "^15.2.3", + "@testing-library/react": "^16.3.0", + "react": "~19.0.0", + "react-dom": "~19.0.0", + "react-test-renderer": "~19.0.0", + "rollup": "^4.22.4" + }, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-native": ">=0.79.5", + "react-native-svg": "^15.8.0" + } + }, + "packages/react-strict-svg/node_modules/react": { + "version": "19.0.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.7.tgz", + "integrity": "sha512-RmJKQ4jeqxrOUF3mjX4G9RIhfkYSQmUDpiM1G/wud3Ctm/xMtGBZoL8BYBFhZ0Q0JgNrtc99CXlnu4EZ6RbvRQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "packages/react-strict-svg/node_modules/react-dom": { + "version": "19.0.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.7.tgz", + "integrity": "sha512-4nDBdQzr+9LsRcti6VGL1lDWWClEk0KaqoAaSO9B1DyIRCai/pnPc2ShZRHEKuAwVKKQpw6Lhi1expDi6Q0NCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.7" + } + }, + "packages/react-strict-svg/node_modules/react-is": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", + "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", + "dev": true, + "license": "MIT" + }, + "packages/react-strict-svg/node_modules/react-test-renderer": { + "version": "19.0.7", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.0.7.tgz", + "integrity": "sha512-9cXSY3CKSkBoUIEOrodgkYqzdmdmqoS+q+y6/5Co2i9RyDktO3Ai54tXgzwln3MDzhNry62HH2PeEgV7EToSgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "react-is": "^19.0.7", + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.7" + } + }, + "packages/react-strict-svg/node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "dev": true, + "license": "MIT" + }, "packages/scripts": { "name": "react-strict-dom-scripts", "version": "0.0.55", diff --git a/packages/react-strict-dom/src/native/index.js b/packages/react-strict-dom/src/native/index.js index 11e657ad..a5cc618a 100644 --- a/packages/react-strict-dom/src/native/index.js +++ b/packages/react-strict-dom/src/native/index.js @@ -20,13 +20,15 @@ import typeof * as TStyleX from '@stylexjs/stylex'; import * as React from 'react'; import * as compat from './compat'; import * as html from './html'; -import * as svg from './svg'; import * as _css from './css'; import { ProvideCustomProperties } from './modules/ContextCustomProperties'; import { ProvideViewportScale, useViewportScale } from './modules/ContextViewportScale'; +import { useNativeProps } from './modules/useNativeProps'; +import { useStrictDOMElement } from './modules/useStrictDOMElement'; +import { isPropAllowed } from '../shared/isPropAllowed'; type StyleTheme = Theme; type StyleVars = VarGroup; @@ -65,6 +67,8 @@ export { contexts, css, html, - svg, - useViewportScale as useViewportScale_DO_NOT_USE + useViewportScale as useViewportScale_DO_NOT_USE, + useNativeProps as useNativeProps_DO_NOT_USE, + useStrictDOMElement as useStrictDOMElement_DO_NOT_USE, + isPropAllowed as isPropAllowed_DO_NOT_USE }; diff --git a/packages/react-strict-dom/src/types/StrictProps.js b/packages/react-strict-dom/src/types/StrictProps.js index 3a1dd308..a05ef0f9 100644 --- a/packages/react-strict-dom/src/types/StrictProps.js +++ b/packages/react-strict-dom/src/types/StrictProps.js @@ -17,7 +17,6 @@ import type { StrictReactDOMLabelProps } from './StrictReactDOMLabelProps'; import type { StrictReactDOMListItemProps } from './StrictReactDOMListItemProps'; import type { StrictReactDOMOptionProps } from './StrictReactDOMOptionProps'; import type { StrictReactDOMSelectProps } from './StrictReactDOMSelectProps'; -import type { StrictReactDOMSvgProps } from './StrictReactDOMSvgProps'; import type { StrictReactDOMTextAreaProps } from './StrictReactDOMTextAreaProps'; export type StrictProps = Readonly<{ @@ -30,6 +29,5 @@ export type StrictProps = Readonly<{ ...StrictReactDOMListItemProps, ...StrictReactDOMOptionProps, ...StrictReactDOMSelectProps, - ...StrictReactDOMSvgProps, ...StrictReactDOMTextAreaProps }>; diff --git a/packages/react-strict-dom/src/web/index.js b/packages/react-strict-dom/src/web/index.js index 24c82cce..67ea295b 100644 --- a/packages/react-strict-dom/src/web/index.js +++ b/packages/react-strict-dom/src/web/index.js @@ -16,8 +16,8 @@ import type { } from '@stylexjs/stylex'; import * as html from './html'; -import * as svg from './svg'; import * as css from '@stylexjs/stylex'; +import { isPropAllowed } from '../shared/isPropAllowed'; type StyleTheme = Theme; type StyleVars = VarGroup; @@ -26,4 +26,4 @@ type StylesWithout = StyleXStylesWithout; export type { StaticStyles, StyleTheme, StyleVars, Styles, StylesWithout }; -export { css, html, svg }; +export { css, html, isPropAllowed as isPropAllowed_DO_NOT_USE }; diff --git a/packages/react-strict-dom/src/web/runtime.js b/packages/react-strict-dom/src/web/runtime.js index 5f32ab49..e7b24009 100644 --- a/packages/react-strict-dom/src/web/runtime.js +++ b/packages/react-strict-dom/src/web/runtime.js @@ -157,8 +157,6 @@ const sub: StrictReactDOMPropsStyle = styles.inline; // $FlowFixMe[incompatible-type] const sup: StrictReactDOMPropsStyle = styles.inline; // $FlowFixMe[incompatible-type] -const svg: StrictReactDOMPropsStyle = styles.img; -// $FlowFixMe[incompatible-type] const textarea: StrictReactDOMPropsStyle = [ styles.inlineblock, styles.textarea @@ -214,7 +212,6 @@ export const defaultStyles = { strong: strong as typeof strong, sub: sub as typeof sub, sup: sup as typeof sup, - svg: svg as typeof svg, textarea: textarea as typeof textarea, u: u as typeof u, ul: ul as typeof ul diff --git a/packages/react-strict-svg/LICENSE b/packages/react-strict-svg/LICENSE new file mode 100644 index 00000000..b93be905 --- /dev/null +++ b/packages/react-strict-svg/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/react-strict-svg/jest.config.js b/packages/react-strict-svg/jest.config.js new file mode 100644 index 00000000..8304161b --- /dev/null +++ b/packages/react-strict-svg/jest.config.js @@ -0,0 +1,24 @@ +/** + * 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. + */ + +'use strict'; + +const babelConfig = require('../react-strict-dom/tools/jest/babelConfig.js'); + +module.exports = { + displayName: 'react-strict-svg (web)', + rootDir: __dirname, + setupFilesAfterEnv: ['/jest.setup.js'], + testEnvironment: 'jsdom', + testMatch: ['/src/web/**/__tests__/*-test.js'], + moduleNameMapper: { + '^react-strict-dom$': '/../react-strict-dom/src/web/index.js' + }, + transform: { + '\\.[jt]sx?$': ['babel-jest', babelConfig()] + } +}; diff --git a/packages/react-strict-svg/jest.setup.js b/packages/react-strict-svg/jest.setup.js new file mode 100644 index 00000000..600e300d --- /dev/null +++ b/packages/react-strict-svg/jest.setup.js @@ -0,0 +1,20 @@ +/** + * 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. + */ + +'use strict'; + +if ( + typeof HTMLElement !== 'undefined' && + typeof HTMLElement.prototype.animate !== 'function' +) { + HTMLElement.prototype.animate = function () { + return { + finished: Promise.resolve(), + cancel() {} + }; + }; +} diff --git a/packages/react-strict-svg/package.json b/packages/react-strict-svg/package.json new file mode 100644 index 00000000..79650106 --- /dev/null +++ b/packages/react-strict-svg/package.json @@ -0,0 +1,51 @@ +{ + "name": "react-strict-svg", + "version": "0.0.55", + "description": "React Strict SVG", + "exports": { + ".": { + "react-native": { + "types": "./dist/native/index.d.ts", + "default": "./dist/native/index.js" + }, + "default": { + "types": "./dist/web/index.d.ts", + "default": "./dist/web/index.js" + } + }, + "./package.json": "./package.json" + }, + "files": [ + "dist/*", + "LICENSE", + "package.json" + ], + "sideEffects": false, + "scripts": { + "jest": "jest --config ./jest.config.js", + "jest:report": "jest --config ./jest.config.js --collect-coverage", + "build": "rollup --config ./tools/rollup.config.mjs", + "clean": "del-cli \"./dist/*\"", + "prebuild": "npm run clean && generate-types -i src/ -o dist" + }, + "dependencies": { + "@stylexjs/stylex": "^0.18.3", + "react-strict-dom": "0.0.55" + }, + "devDependencies": { + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^26.0.1", + "@rollup/plugin-node-resolve": "^15.2.3", + "@testing-library/react": "^16.3.0", + "react": "~19.0.0", + "react-dom": "~19.0.0", + "react-test-renderer": "~19.0.0", + "rollup": "^4.22.4" + }, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-native": ">=0.79.5", + "react-native-svg": "^15.8.0" + } +} diff --git a/packages/react-strict-svg/src/native/index.js b/packages/react-strict-svg/src/native/index.js new file mode 100644 index 00000000..092b2994 --- /dev/null +++ b/packages/react-strict-svg/src/native/index.js @@ -0,0 +1,12 @@ +/** + * 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. + * + * @flow strict + */ + +import * as svg from './svg'; + +export { svg }; diff --git a/packages/react-strict-dom/src/native/modules/createStrictDOMSvgComponent.js b/packages/react-strict-svg/src/native/modules/createStrictDOMSvgComponent.js similarity index 99% rename from packages/react-strict-dom/src/native/modules/createStrictDOMSvgComponent.js rename to packages/react-strict-svg/src/native/modules/createStrictDOMSvgComponent.js index 8ed8d6d7..67dab5b7 100644 --- a/packages/react-strict-dom/src/native/modules/createStrictDOMSvgComponent.js +++ b/packages/react-strict-svg/src/native/modules/createStrictDOMSvgComponent.js @@ -63,9 +63,11 @@ import { TSpan, Use } from 'react-native-svg'; -import { useNativeProps } from './useNativeProps'; -import { useStrictDOMElement } from './useStrictDOMElement'; -import * as css from '../css'; +import { + css, + useNativeProps_DO_NOT_USE as useNativeProps, + useStrictDOMElement_DO_NOT_USE as useStrictDOMElement +} from 'react-strict-dom'; const RE_CAPTURE_VAR_NAME = /^var\(--(.*)\)$/; diff --git a/packages/react-strict-dom/src/native/svg.js b/packages/react-strict-svg/src/native/svg.js similarity index 100% rename from packages/react-strict-dom/src/native/svg.js rename to packages/react-strict-svg/src/native/svg.js diff --git a/packages/react-strict-dom/src/shared/isSvgPropAllowed.js b/packages/react-strict-svg/src/shared/isSvgPropAllowed.js similarity index 94% rename from packages/react-strict-dom/src/shared/isSvgPropAllowed.js rename to packages/react-strict-svg/src/shared/isSvgPropAllowed.js index af424215..614f78f0 100644 --- a/packages/react-strict-dom/src/shared/isSvgPropAllowed.js +++ b/packages/react-strict-svg/src/shared/isSvgPropAllowed.js @@ -4,10 +4,10 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow strict + * @flow strict-local */ -import { isPropAllowed } from './isPropAllowed'; +import { isPropAllowed_DO_NOT_USE } from 'react-strict-dom'; const strictAttributeSet: Set = new Set([ 'children', @@ -166,5 +166,5 @@ const strictAttributeSet: Set = new Set([ ]); export function isSvgPropAllowed(key: string): boolean { - return isPropAllowed(key) || strictAttributeSet.has(key); + return isPropAllowed_DO_NOT_USE(key) || strictAttributeSet.has(key); } diff --git a/packages/react-strict-svg/src/shared/logUtils.js b/packages/react-strict-svg/src/shared/logUtils.js new file mode 100644 index 00000000..1e17e0b3 --- /dev/null +++ b/packages/react-strict-svg/src/shared/logUtils.js @@ -0,0 +1,18 @@ +/** + * 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. + * + * @flow strict + */ + +const loggedMessages: { [string]: boolean, ... } = {}; + +export function errorMsg(msg: string) { + if (process.env.NODE_ENV !== 'test' && loggedMessages[msg]) { + return; + } + loggedMessages[msg] = true; + console.error(`[error] React Strict DOM: ${msg}`); +} diff --git a/packages/react-strict-svg/src/types/StrictReactDOMProps.js b/packages/react-strict-svg/src/types/StrictReactDOMProps.js new file mode 100644 index 00000000..bfedbc26 --- /dev/null +++ b/packages/react-strict-svg/src/types/StrictReactDOMProps.js @@ -0,0 +1,13 @@ +/** + * 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. + * + * @flow strict + */ + +// $FlowFixMe[nonstrict-import] +import { html } from 'react-strict-dom'; + +export type StrictReactDOMProps = React.PropsOf; diff --git a/packages/react-strict-dom/src/types/StrictReactDOMSvgProps.js b/packages/react-strict-svg/src/types/StrictReactDOMSvgProps.js similarity index 99% rename from packages/react-strict-dom/src/types/StrictReactDOMSvgProps.js rename to packages/react-strict-svg/src/types/StrictReactDOMSvgProps.js index 8a00d6e7..d613e680 100644 --- a/packages/react-strict-dom/src/types/StrictReactDOMSvgProps.js +++ b/packages/react-strict-svg/src/types/StrictReactDOMSvgProps.js @@ -7,8 +7,8 @@ * @flow strict */ -import type { StrictReactDOMProps } from './StrictReactDOMProps'; import type { Styles } from './styles'; +import type { StrictReactDOMProps } from './StrictReactDOMProps'; type Units = 'userSpaceOnUse' | 'objectBoundingBox'; @@ -82,7 +82,7 @@ type TextProps = Readonly<{ }>; export type StrictReactDOMSvgProps = Readonly<{ - ...StrictReactDOMProps, + ...Omit, ...StrictReactDOMGProps, height?: string | number, preserveAspectRatio?: string, diff --git a/packages/react-strict-dom/src/types/StrictSvgProps.js b/packages/react-strict-svg/src/types/StrictSvgProps.js similarity index 100% rename from packages/react-strict-dom/src/types/StrictSvgProps.js rename to packages/react-strict-svg/src/types/StrictSvgProps.js diff --git a/packages/react-strict-svg/src/types/styles.js b/packages/react-strict-svg/src/types/styles.js new file mode 100644 index 00000000..6f3bb082 --- /dev/null +++ b/packages/react-strict-svg/src/types/styles.js @@ -0,0 +1,22 @@ +/** + * 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. + * + * @flow strict + */ + +import type { + InlineStyles, + StyleXArray, + StyleXStyles, + Theme, + VarGroup +} from '@stylexjs/stylex'; + +export type Style = InlineStyles; + +export type Styles = StyleXArray< + StyleXStyles<> | Theme> +>; diff --git a/packages/react-strict-svg/src/web/index.js b/packages/react-strict-svg/src/web/index.js new file mode 100644 index 00000000..092b2994 --- /dev/null +++ b/packages/react-strict-svg/src/web/index.js @@ -0,0 +1,12 @@ +/** + * 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. + * + * @flow strict + */ + +import * as svg from './svg'; + +export { svg }; diff --git a/packages/react-strict-dom/src/web/modules/createStrictDOMSvgComponent.js b/packages/react-strict-svg/src/web/modules/createStrictDOMSvgComponent.js similarity index 81% rename from packages/react-strict-dom/src/web/modules/createStrictDOMSvgComponent.js rename to packages/react-strict-svg/src/web/modules/createStrictDOMSvgComponent.js index fcfa5f4c..52176287 100644 --- a/packages/react-strict-dom/src/web/modules/createStrictDOMSvgComponent.js +++ b/packages/react-strict-svg/src/web/modules/createStrictDOMSvgComponent.js @@ -4,17 +4,16 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow strict + * @flow strict-local */ -import type { CompiledStyles } from '@stylexjs/stylex/lib/StyleXTypes'; -import type { ReactDOMStyleProps } from '../../types/renderer.web'; -import type { StrictSvgProps } from '../../../dist/types/StrictSvgProps'; +import type { StrictSvgProps } from '../../types/StrictSvgProps'; import * as React from 'react'; import { errorMsg } from '../../shared/logUtils'; import { isSvgPropAllowed } from '../../shared/isSvgPropAllowed'; -import { merge } from '../css/merge'; +// $FlowFixMe[cannot-resolve-module] +import { merge } from 'react-strict-dom/runtime'; // $FlowFixMe[unclear-type] function validateStrictProps(props: any) { @@ -33,7 +32,7 @@ export function createStrictDOMSvgComponent( ): component(ref?: React.RefSetter, ...P) { // NOTE: `debug-style` is not generated by `stylex.create` // so it needs a type-cast - const debugStyle: CompiledStyles = { + const debugStyle = { $$css: true, 'debug::name': `svg-${TagName}` as $FlowFixMe }; @@ -55,11 +54,7 @@ export function createStrictDOMSvgComponent( * get host style props */ // $FlowFixMe[incompatible-type] - const hostStyleProps: ReactDOMStyleProps = merge([ - debugStyle, - defaultStyle, - style - ]); + const hostStyleProps = merge([debugStyle, defaultStyle, style]); /** * Construct tree diff --git a/packages/react-strict-svg/src/web/runtime.js b/packages/react-strict-svg/src/web/runtime.js new file mode 100644 index 00000000..18f5200e --- /dev/null +++ b/packages/react-strict-svg/src/web/runtime.js @@ -0,0 +1,32 @@ +/** + * 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. + * + * @flow strict + */ + +import type { StrictReactDOMProps } from '../types/StrictReactDOMProps'; + +type StrictReactDOMPropsStyle = StrictReactDOMProps['style']; + +import * as stylex from '@stylexjs/stylex'; + +// set this on the root, probably in the theme context +// const fontFamily = '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif'; + +const styles = stylex.create({ + svg: { + aspectRatio: 'attr(width) / attr(height)', + height: 'auto', + maxWidth: '100%' + } +}); + +// $FlowFixMe[incompatible-type] +const svg: StrictReactDOMPropsStyle = styles.svg; + +export const defaultStyles = { + svg: svg as typeof svg +}; diff --git a/packages/react-strict-dom/src/web/svg.js b/packages/react-strict-svg/src/web/svg.js similarity index 99% rename from packages/react-strict-dom/src/web/svg.js rename to packages/react-strict-svg/src/web/svg.js index dce53db9..e7a15b01 100644 --- a/packages/react-strict-dom/src/web/svg.js +++ b/packages/react-strict-svg/src/web/svg.js @@ -60,6 +60,7 @@ import type { StrictReactDOMUseProps } from '../types/StrictReactDOMSvgProps'; +// $FlowFixMe[nonstrict-import] import { createStrictDOMSvgComponent as createStrictSvg } from './modules/createStrictDOMSvgComponent'; import { defaultStyles } from './runtime'; diff --git a/packages/react-strict-svg/tools/rollup.config.mjs b/packages/react-strict-svg/tools/rollup.config.mjs new file mode 100644 index 00000000..9c29658e --- /dev/null +++ b/packages/react-strict-svg/tools/rollup.config.mjs @@ -0,0 +1,90 @@ +/** + * 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. + */ + +import { babel } from '@rollup/plugin-babel'; +import commonjs from '@rollup/plugin-commonjs'; +import resolve from '@rollup/plugin-node-resolve'; + +import path, { dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const babelPlugin = babel({ + babelHelpers: 'bundled', + configFile: path.resolve(__dirname, 'rollup/babelConfig.mjs') +}); + +function ossLicensePlugin() { + const header = `/** + * @license react-strict-dom + * 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. + */ + +'use strict';`; + + return { + renderChunk(source) { + return `${header}\n${source}`; + } + }; +} + +const sharedPlugins = [ + babelPlugin, + ossLicensePlugin(), + resolve(), + commonjs() // commonjs packages: postcss-value-parser, styleq +]; + +/** + * Web bundles + */ +const webConfigs = [ + // OSS build + { + external: ['react', 'react/jsx-runtime', 'react-strict-dom'], + input: path.join(__dirname, '../src/web/index.js'), + output: { + file: path.join(__dirname, '../dist/web/index.js'), + format: 'es' + }, + plugins: [...sharedPlugins], + treeshake: { + moduleSideEffects: false + } + } +]; + +/** + * Native bundles + */ +const nativeConfigs = [ + // OSS build + { + external: [ + 'react', + 'react/jsx-runtime', + 'react-strict-dom', + /^react-native.*/ + ], + input: path.join(__dirname, '../src/native/index.js'), + output: { + file: path.join(__dirname, '../dist/native/index.js'), + format: 'es' + }, + plugins: [...sharedPlugins], + treeshake: { + moduleSideEffects: false + } + } +]; + +export default [...webConfigs, ...nativeConfigs]; diff --git a/packages/react-strict-svg/tools/rollup/babelConfig.mjs b/packages/react-strict-svg/tools/rollup/babelConfig.mjs new file mode 100644 index 00000000..24dbc6e6 --- /dev/null +++ b/packages/react-strict-svg/tools/rollup/babelConfig.mjs @@ -0,0 +1,29 @@ +/** + * 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. + */ + +const config = { + assumptions: { + iterableIsArray: true + }, + comments: false, + parserOpts: { + enableExperimentalComponentSyntax: true, + reactRuntimeTarget: '19' + }, + plugins: ['babel-plugin-syntax-hermes-parser'], + presets: [ + [ + '@babel/preset-react', + { + runtime: 'automatic' + } + ], + '@babel/preset-flow' + ] +}; + +export default config;