| 1 | /** |
| 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. |
| 3 | * |
| 4 | * This source code is licensed under the MIT license found in the |
| 5 | * LICENSE file in the root directory of this source tree. |
| 6 | */ |
| 7 | |
| 8 | import * as React from 'react'; |
| 9 | import ReactVersion from 'shared/ReactVersion'; |
| 10 | import {LegacyRoot, ConcurrentRoot} from 'react-reconciler/src/ReactRootTags'; |
| 11 | import { |
| 12 | createContainer, |
| 13 | updateContainerSync, |
| 14 | injectIntoDevTools, |
| 15 | flushSyncWork, |
| 16 | defaultOnUncaughtError, |
| 17 | defaultOnCaughtError, |
| 18 | defaultOnRecoverableError, |
| 19 | } from 'react-reconciler/src/ReactFiberReconciler'; |
| 20 | |
| 21 | import Transform from 'art/core/transform'; |
| 22 | import Mode from 'art/modes/current'; |
| 23 | import FastNoSideEffects from 'art/modes/fast-noSideEffects'; |
| 24 | import {disableLegacyMode} from 'shared/ReactFeatureFlags'; |
| 25 | |
| 26 | import {TYPES, childrenAsString} from './ReactARTInternals'; |
| 27 | |
| 28 | function defaultOnDefaultTransitionIndicator() { |
| 29 | // Noop |
| 30 | } |
| 31 | |
| 32 | Mode.setCurrent( |
| 33 | // Change to 'art/modes/dom' for easier debugging via SVG |
| 34 | FastNoSideEffects, |
| 35 | ); |
| 36 | |
| 37 | /** Declarative fill-type objects; API design not finalized */ |
| 38 | |
| 39 | const slice = Array.prototype.slice; |
| 40 | |
| 41 | class LinearGradient { |
| 42 | constructor(stops, x1, y1, x2, y2) { |
| 43 | this._args = slice.call(arguments); |
| 44 | } |
| 45 | |
| 46 | applyFill(node) { |
| 47 | node.fillLinear.apply(node, this._args); |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | class RadialGradient { |
| 52 | constructor(stops, fx, fy, rx, ry, cx, cy) { |
| 53 | this._args = slice.call(arguments); |
| 54 | } |
| 55 | |
| 56 | applyFill(node) { |
| 57 | node.fillRadial.apply(node, this._args); |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | class Pattern { |
| 62 | constructor(url, width, height, left, top) { |
| 63 | this._args = slice.call(arguments); |
| 64 | } |
| 65 | |
| 66 | applyFill(node) { |
| 67 | node.fillImage.apply(node, this._args); |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | /** React Components */ |
| 72 | |
| 73 | class Surface extends React.Component { |
| 74 | componentDidMount() { |
| 75 | const {height, width} = this.props; |
| 76 | |
| 77 | this._surface = Mode.Surface(+width, +height, this._tagRef); |
| 78 | |
| 79 | this._mountNode = createContainer( |
| 80 | this._surface, |
| 81 | disableLegacyMode ? ConcurrentRoot : LegacyRoot, |
| 82 | null, |
| 83 | false, |
| 84 | false, |
| 85 | '', |
| 86 | defaultOnUncaughtError, |
| 87 | defaultOnCaughtError, |
| 88 | defaultOnRecoverableError, |
| 89 | defaultOnDefaultTransitionIndicator, |
| 90 | null, |
| 91 | ); |
| 92 | // We synchronously flush updates coming from above so that they commit together |
| 93 | // and so that refs resolve before the parent life cycles. |
| 94 | updateContainerSync(this.props.children, this._mountNode, this); |
| 95 | flushSyncWork(); |
| 96 | } |
| 97 | |
| 98 | componentDidUpdate(prevProps, prevState) { |
| 99 | const props = this.props; |
| 100 | |
| 101 | if (props.height !== prevProps.height || props.width !== prevProps.width) { |
| 102 | this._surface.resize(+props.width, +props.height); |
| 103 | } |
| 104 | |
| 105 | // We synchronously flush updates coming from above so that they commit together |
| 106 | // and so that refs resolve before the parent life cycles. |
| 107 | updateContainerSync(this.props.children, this._mountNode, this); |
| 108 | flushSyncWork(); |
| 109 | |
| 110 | if (this._surface.render) { |
| 111 | this._surface.render(); |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | componentWillUnmount() { |
| 116 | // We synchronously flush updates coming from above so that they commit together |
| 117 | // and so that refs resolve before the parent life cycles. |
| 118 | updateContainerSync(null, this._mountNode, this); |
| 119 | flushSyncWork(); |
| 120 | } |
| 121 | |
| 122 | render() { |
| 123 | // This is going to be a placeholder because we don't know what it will |
| 124 | // actually resolve to because ART may render canvas, vml or svg tags here. |
| 125 | // We only allow a subset of properties since others might conflict with |
| 126 | // ART's properties. |
| 127 | const props = this.props; |
| 128 | |
| 129 | // TODO: ART's Canvas Mode overrides surface title and cursor |
| 130 | const Tag = Mode.Surface.tagName; |
| 131 | |
| 132 | return ( |
| 133 | <Tag |
| 134 | ref={ref => (this._tagRef = ref)} |
| 135 | accessKey={props.accessKey} |
| 136 | className={props.className} |
| 137 | draggable={props.draggable} |
| 138 | role={props.role} |
| 139 | style={props.style} |
| 140 | tabIndex={props.tabIndex} |
| 141 | title={props.title} |
| 142 | /> |
| 143 | ); |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | class Text extends React.Component { |
| 148 | constructor(props) { |
| 149 | super(props); |
| 150 | // We allow reading these props. Ideally we could expose the Text node as |
| 151 | // ref directly. |
| 152 | ['height', 'width', 'x', 'y'].forEach(key => { |
| 153 | Object.defineProperty(this, key, { |
| 154 | get: function () { |
| 155 | return this._text ? this._text[key] : undefined; |
| 156 | }, |
| 157 | }); |
| 158 | }); |
| 159 | } |
| 160 | render() { |
| 161 | // This means you can't have children that render into strings... |
| 162 | const T = TYPES.TEXT; |
| 163 | return ( |
| 164 | <T {...this.props} ref={t => (this._text = t)}> |
| 165 | {childrenAsString(this.props.children)} |
| 166 | </T> |
| 167 | ); |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | injectIntoDevTools(); |
| 172 | |
| 173 | /** API */ |
| 174 | |
| 175 | export const ClippingRectangle = TYPES.CLIPPING_RECTANGLE; |
| 176 | export const Group = TYPES.GROUP; |
| 177 | export const Shape = TYPES.SHAPE; |
| 178 | export const Path = Mode.Path; |
| 179 | export {LinearGradient, Pattern, RadialGradient, Surface, Text, Transform}; |
| 180 | |
| 181 | export {ReactVersion as version}; |