├── .gitignore ├── .npmignore ├── HISTORY.md ├── README.md ├── bsconfig.json ├── documentation.md ├── package.json └── src ├── reactDOMRe.re ├── reactDOMServerRe.re ├── reactEventRe.re ├── reactEventRe.rei ├── reactRe.re └── reactRe.rei /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules* 3 | /lib/ 4 | npm-debug.log 5 | finalOutput/*.js 6 | .merlin 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .merlin 2 | /lib/bs 3 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | ## 0.1.3 2 | DOM ref is now typed as `Js.null Dom.element`, instead of just `Dom.element` (https://github.com/reasonml/reason-react/commit/6f2a75b). Trivial migration: https://github.com/chenglou/reason-react-example/commit/b44587a 3 | 4 | ## 0.1.2 5 | Add `defaultValue` prop for input DOM component (https://github.com/reasonml/reason-react/commit/c293c6e) 6 | 7 | ## 0.1.1 8 | Correct type of `dangerouslySetInnerHTML` (#76) 9 | 10 | ## 0.1.0 11 | Lots of great people working together. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | We've moved! New repo at https://github.com/reasonml/reason-react 2 | -------------------------------------------------------------------------------- /bsconfig.json: -------------------------------------------------------------------------------- 1 | /* This is the BuckleScript configuration file. Note that this is a comment; 2 | BuckleScript comes with a JSON parser that supports comments and trailing 3 | comma. If this screws with your editor highlighting, please tell us by filing 4 | an issue! */ 5 | { 6 | "name" : "reason-react", 7 | "sources": "src", 8 | } 9 | -------------------------------------------------------------------------------- /documentation.md: -------------------------------------------------------------------------------- 1 | __This documentation assumes relative familiarity with ReactJS.__ 2 | 3 | ## JSX 4 | 5 | The JSX ppx transform resides in the Reason repo itself. The documentation is [here](https://github.com/facebook/reason/tree/master/src#jsx-transform-for-reactjs). 6 | 7 | ## Bindings Usage 8 | 9 | ### "component bag" 10 | 11 | Reason-React's uses idiomatic Reason/OCaml modules to get rid of ReactJS' `this`, a common pain point for ReactJS newcomers. To fulfill the same role, relevant functions (they're not methods; just regular functions!) accept as argument a `componentBag` record of shape `{props, state, updater, handler, instanceVars, setState}`. A render would look like: 12 | 13 | ```reason 14 | /* normal record destructuring. Pick what you need! */ 15 | let render {props, state} =>
(ReactRe.stringToElement state.message)
; 16 | ``` 17 | 18 | #### `props`, `state` 19 | Same as ReactJS'. 20 | 21 | #### `updater` 22 | The secret sauce function that wraps every callback handler. The ReactJS `
` becomes `
`. `updater` takes in your familiar callback and returns a new (memoized) callback that'll give you the up-to-date "component bag" (props, state and other values) when the callback is called, and ensures that the component state is appropriately updated after the callback. Example: 23 | 24 | ```reason 25 | /* `props` is up-to-date here, even though onClick is asynchronously triggered. The same goes for other component bag values */ 26 | let handleClick {props} event => {Js.log "clicked!"; None}; 27 | let render {props, updater} =>
; 28 | ``` 29 | 30 | The return type is `option state`, where you indicate whether the handler needs to trigger a state update or not. 31 | 32 | ##### Note 33 | 34 | `updater` memoizes up to 30 callbacks. 35 | 36 | In Reason-React, and likewise in ReactJS, when you do ` 1) />` inside `render`, you're creating a new, inline function each time and passing it down to `Foo`'s `onClick`. If `Foo` implemented [shouldComponentUpdate](https://facebook.github.io/react/docs/react-component.html#shouldcomponentupdate), you'd accidentally cause its `shouldComponentUpdate` to return `true` every time (since `props.onClick !== nextProps.onClick`). This has been avoided largely because in ReactJS, people did ``, which keeps pointing to the same reference (thus `props.onClick === nextProps.onClick` in `Foo`). 37 | 38 | But in Reason-React, since you need to wrap your handler in `updater`, we need to make sure that for each `myHandlerReference`, we return the same reference if `(updater myHandlerReference)` has been called before (in other words: `updater myHandlerReference === updater myHandlerReference`). We achieve this by storing `myHandlerReference` in a collection internally, and finding it the next time it's passed to us. To avoid memory leaks (in case people mistakenly inline the handler through `updater (fun () => ...)` and the `render`'s called again and again), the `updater` stores up to 30 functions. 39 | 40 | #### `handler` 41 | Like `updater`, but a convenient subset in terms of functionality: it doesn't update the component, so doesn't ask you for a `option state` return type (just unit). 42 | 43 | #### `instanceVars` 44 | Occasionally, ReactJS components are used to store instance properties, e.g. `timeoutID`, `subscriptions`, `isMounted`. We support this pattern. 45 | 46 | ```reason 47 | type instanceVars = {mutable intervalID: option int}; 48 | /* this method & the life cycle methods will be explained below */ 49 | let getInstanceVars () => {intervalID: None}; 50 | let componentDidMount {instanceVars} => { 51 | instanceVars.intervalID = Some (someBindingsToSetInterval (fun () => Js.log "testing!") 1000); 52 | None; 53 | }; 54 | let componentWillUnmount {instanceVars} => 55 | switch instanceVars.intervalID { 56 | | None => (); 57 | | Some id => someBindingsToClearInterval id; 58 | }; 59 | ``` 60 | 61 | See also the use-case with the previous `handler`. 62 | 63 | #### `setState` 64 | **Different use-cases than ReactJS' `setState`**! Since lifecycle events (below) and handlers return an `option state`, this `setState` API is rarely used and only serves as an escape hatch when you know what you're doing. 65 | 66 | ### Lifecycle events 67 | All lifecycle hooks from ReactJS exist, apart from `componentWillMount` (`componentDidMount` is [recommended](https://facebook.github.io/react/docs/react-component.html#componentwillmount)) and `shouldComponentUpdate` (not implemented yet), e.g. 68 | 69 | ```reason 70 | /* typical use-case: compare current & next props, then optionally set the state. */ 71 | let componentWillReceiveProps {props, state} ::nextProps => nextProps.toggle === props.toggle ? None : Some {count: state.count + 1}; 72 | ``` 73 | 74 | (A common nit of ReactJS' lifecycle events is that folks can never remember prevState/prevProps/nextProps and their position in a lifecycle event. Note how we've used types and function labels to solve this.) 75 | 76 | Instead of imperatively calling `setState`, the lifecycle functions look for potential state update from their return value. `None` means no state update needed, `Some whateverNewState` means setting the state and trigger a re-render. 77 | 78 | - `componentDidMount`: `componentBag => option state` 79 | - `componentWillReceiveProps`: `componentBag => nextProps::props => option state` 80 | - `componentWillUpdate`: `componentBag => nextProps::props => nextState::state => option state` 81 | - `componentDidUpdate`: `prevProps::props => prevState::state => componentBag => option state` 82 | - `componentWillUnmount`: `componentBag => unit` 83 | 84 | ### Component API 85 | 86 | The component API uses OCaml's module system rather than our own `createClass`-like API (less APIs!). **All you need to know about modules is [here](http://facebook.github.io/reason/modules.html)**. 87 | 88 | See the `examples/` folder. The components declaration structure should look quite familiar to those who've worked with ReactJS. To declare a React component class, you'd create a normal OCaml module and include some pre-declared module definitions. Here's the basic version: 89 | 90 | ```reason 91 | /* This is like the object declaration part of ReactJS' `React.createClass(theObjectSpec)` */ 92 | module MyComponent = { 93 | include ReactRe.Component.Stateful; 94 | let name = "MyComponent"; 95 | type props = { 96 | foo: string, 97 | bar: option int, 98 | }; 99 | /*...*/ 100 | }; 101 | 102 | /* This is the equivalent of `React.createClass` */ 103 | include ReactRe.CreateComponent MyComponent; 104 | 105 | /* The actual call exposed to consumers, via JSX */ 106 | let createElement ::foo ::bar=? => wrapProps {foo, bar}; 107 | ``` 108 | 109 | Or, with a single prop: 110 | 111 | ```reason 112 | module MyComponent = { 113 | include ReactRe.Component.Stateful; 114 | let name = "MyComponent"; 115 | type props = {foo: string}; 116 | /*...*/ 117 | }; 118 | 119 | include ReactRe.CreateComponent MyComponent; 120 | let createElement ::foo => wrapProps {foo: foo}; 121 | // ^ Note: there's no record field punning sugar for records with a single field. Careful! `{foo}` has a different meaning. 122 | ``` 123 | 124 | #### Last Two Expressions 125 | 126 | ##### `include ReactRe.CreateComponent MyComponent` 127 | 128 | `ReactRe.CreateComponent` takes in the declared MyComponent and returns a new module containing `comp` (for JS interop, described later) and `wrapProps`. You need to expose these two by `include`-ing this returned module, aka "spread" the two definitions into the current file. 129 | 130 | ##### 'createElement' 131 | 132 | `createElement` exposes the call used by the JSX sugar described at the beginning. From another module, you'd be able to call `MyComponent.createElement ...` or use the JSX sugar (remember that Reason JSX desugars `` to `MyComponent.createElement foo::"hi" children::[] ()`). 133 | 134 | `wrapProps` creates the bindings magic; it takes a props record that you've constructed from the arguments, and **curries `children`, `ref` and `key` for you** (see below for details on working with `ref` and `children`)! So the whole call is actually: 135 | 136 | ```reason 137 | let createElement ::foo ::bar=? ::children ::ref=? key=? () => wrapProps {foo, bar} ::children ::ref=? key=? (); 138 | ``` 139 | 140 | But since we have currying, this can simply to: 141 | 142 | ```reason 143 | let createElement ::foo ::bar=? => wrapProps {foo, bar}; 144 | ``` 145 | 146 | Now components don't have to worry about declaring `ref` and `key` in `createElement`! If their props actually need to read into `children`, then simply uncurry that (aka, add `::children` to **both** sides of the expression!): 147 | 148 | ```reason 149 | let createElement ::foo ::bar=? ::children => wrapProps {foo, bar, children} ::children; 150 | ``` 151 | 152 | Note that Reason functions can have default values and be optional. This maps well to ReactJS' defaultProps and optional props. There's no (need for a) special Reason-React API for these use-cases. `::bar=?` means `bar` is an `option whateverTypeBarIs`. 153 | 154 | **Tips: if your component doesn't accept any prop**: 155 | 156 | ```reason 157 | /*...*/ 158 | type props = unit; 159 | /*...*/ 160 | let createElement = wrapProps (); 161 | ``` 162 | 163 | #### The Module Itself 164 | 165 | For the `MyComponent` module, you'd mix in one of the pre-declared definitions under `ReactRe.Component.*`: 166 | 167 | ```reason 168 | module MyComponent = { 169 | include ReactRe.Component.Stateful.JsProps; 170 | let name = "MyComponent"; 171 | /*...*/ 172 | }; 173 | ``` 174 | 175 | Including such definition allows you to provide the associated functions & types. As of today, the most fully-featured component spec is `Stateful.InstanceVars.JsProps` (a stateful component, that allows attaching instance properties, and that can be called as JSX from JS). You can omit any of the three, but the order should be kept, e.g. `include ReactRe.Component.Stateful.JsProps` works but `include ReactRe.Component.JsProps.Stateful` doesn't. 176 | 177 | ##### Nothing (just `ReactRe.Component`) 178 | The default. Stateless component. You need to provide: 179 | - `name`, the string name of your component, for tooling purposes, e.g. [React devtools](https://github.com/facebook/react-devtools). 180 | - `props`, the type of the component's props, preferably a record, or `unit` if no props. 181 | - `render`, your render function! It takes in the `componentBag` record, described earlier. In the case of the `React.Component`, only `props` and `updater` are relevant. 182 | 183 | ##### Stateful 184 | In addition to the default component definitions, this one also asks you to provide: 185 | - `state`, the type of your state, preferably a record. 186 | - `getInitialState`, a function that takes the initial `props` (which you can ignore) and returns the initial `state`. 187 | 188 | ##### InstanceVars 189 | In addition to the default component definitions, add: 190 | - `instanceVars` (documented earlier), the type that's usually of the following form: 191 | 192 | ```reason 193 | type instanceVars = { 194 | mutable timeoutID: option int, 195 | mutable subscription1: option whateverSubscriptionType 196 | }; 197 | ``` 198 | 199 | The fields of the record don't have to be mutable, but in the context of the usages of instanceVars, e.g. setting up a timeOut in `componentDidMount`, your record's likely populated with mostly mutable fields. 200 | 201 | - `getInstanceVars`, a function of the type `unit => instanceVars`, which initiates your `instanceVars`: 202 | 203 | ```reason 204 | let getInstanceVars () => {timeoutID: None, subscription1: None}; 205 | let componentDidMount componentBag => {componentBag.instanceVars.timeoutID = Some (setIntervalBinding bla); None}; /* lifecycle events need to return `option state` */ 206 | ``` 207 | 208 | ##### JsProps 209 | Add: 210 | - `jsProps`, the type of the props passed to you from the JS side, which should be `Js.t {. myField: myType}`, like so: 211 | 212 | ```reason 213 | type jsProps = Js.t {. 214 | message: string, 215 | count: Js.null_undefined int /* optional arg. Read below to see how all of this wires up */ 216 | }; 217 | ``` 218 | 219 | - `jsPropsToReasonProps`, of the type `option (jsProps => props)`, which translates the JS props into the more idiomatic `props` type you've defined, like so: 220 | 221 | ```reason 222 | let jsPropsToReasonProps = Some (fun jsProps => {message: jsProps##message, count: Js.Null_undefined.to_opt jsProps##count}); 223 | ``` 224 | 225 | ### Interop With Existing JavaScript Components 226 | While it's nice to marvel at OCaml's great type system, Reason-React's slick API, BuckleScript's mind-blowing idiomatic output, our toolchain's superb static analysis, etc., it's unpragmatic to suddenly convert over all existing JS components to Reason. We've exposed simple hooks to talk to the JS components. 227 | 228 | #### ReactJS Using Reason-React 229 | (Also see [jsProps](#jsprops) for Reason <-> JS `props` conversion.) 230 | 231 | On the JS side, you can require your component like so: 232 | 233 | ```js 234 | var MyReasonComponent = require('myReasonComponent').comp; 235 | // make sure you're passing the correct data types! See the jsProps section 236 | ; 237 | ``` 238 | 239 | Every Reason-React component expose a `comp` value, implicitly, when doing `include ReactRe.CreateComponent MyComponentModule;`. This is mentioned [here](#include-reactrecreatecomponent-mycomponent). 240 | 241 | #### Reason-React Using ReactJs 242 | We only need a single hook, `wrapPropsShamelessly` to make calling a JS component work! Assuming we have `Banner.js`, here's how we'd use it in Reason: 243 | 244 | ```reason 245 | /* bannerRe.re */ 246 | external foo : ReactRe.reactClass = "Banner" [@@bs.module]; 247 | 248 | /* this is the call that takes in Reason data and converts it to JS data. Make sure you get the conversion right! JS won't warn you of type errors... */ 249 | let createElement 250 | className::(className: option string)=? 251 | show::(show: bool) 252 | onClick::(onClick: ReactRe.event => unit) => 253 | ReactRe.wrapPropsShamelessly 254 | foo 255 | { 256 | /* turn an option string into a js nullable string */ 257 | "className": Js.Null_undefined.from_opt className, 258 | /* turn a mandatory bool into a js boolean */ 259 | "show": Js.Bool.to_js_boolean show, 260 | /* this stays the same, in BuckleScript and in JS */ 261 | "onClick": onClick 262 | }; 263 | ``` 264 | 265 | In the rare case where the JS component doesn't accept any prop: 266 | 267 | ```reason 268 | /* Note that here it's a binding =, not a function =>. We're currying. See the section on `createElement`. */ 269 | let createElement = 270 | ReactRe.wrapPropsShamelessly 271 | foo 272 | (Js.Obj.empty ()) /* BuckleScript bindings. Represents the empty `{}` JS object. */ 273 | ``` 274 | 275 | Usage: 276 | 277 | ```reason 278 | /* myApp.re */ 279 | /* ... */ 280 | 281 | 282 | ``` 283 | 284 | That's pretty much it! 285 | 286 | ### Working with Children 287 | 288 | ReactJS' children are a bit "loose" and don't type well. Reason-React components `children` are always `list ReactRe.reactElement`. E.g. the JS code `hello` needs to be `(ReactRe.stringToElement "hello")` in Reason-React. 289 | 290 | What can be a `reactElement`? 291 | 292 | - The DOM components from `ReactDOMRe.createElement` (JSX sugar: `
`, `` and the rest). 293 | - The components from `ReactRe.createElement` (JSX sugar: ``). 294 | - String, but only through `ReactRe.stringToElement myString`. 295 | - The null element, `ReactRe.nullElement` (BuckleScript's `Js.null` won't work). 296 | - `array ReactRe.reactElement`, but only through `ReactRe.arrayToElement myArray`. 297 | - `list ReactRe.reactElement`, through `ReactRe.listToElement` (a helper implemented as `ReactRe.arrayToElement (Array.of_list myList)`). 298 | 299 | ReactJS children must be typed as `Js.null_undefined ReactRe.reactJsChildren`. They can be converted into a `list ReactRe.reactElement` with `ReactRe.jsChildrenToReason myJSChildren`; 300 | 301 | To pass Reason-React children to the JS side, do a conversion, e.g. `
(ReactRe.arrayToElement (Array.of_list props.children))
` 302 | 303 | See also the section on [createElement](#createelement). 304 | 305 | ### Working with Events 306 | 307 | Reason-React events map cleanly to ReactJS [synthetic events](https://facebook.github.io/react/docs/events.html). More info in the [inline docs](https://github.com/reasonml/reason-react/blob/94b22c3ecad4374d00266727250694fb193b63e2/src/reactEventRe.rei#L1). 308 | 309 | If you're accessing fields on your event object, like `event.target.value`, see [Working with DOM](#working-with-dom) below. 310 | 311 | ### Working with Styles 312 | 313 | Since CSS-in-JS is all the rage right now, we'll recommend our official pick soon. In the meantime, for inline styles, there's the `ReactDOMRe.Style.make` API: 314 | 315 | ```reason 316 |
322 | ``` 323 | 324 | It's a labelled (typed!) function call that maps to the familiar style object `{color: '#444444', fontSize: '68px'}`. **Note** that `make` returns an opaque `ReactDOMRe.style` type that you can't read into. We also expose a `ReactDOMRe.Style.combine` that takes in two `style`s and combine them. 325 | 326 | ### Working with Refs 327 | 328 | There's no ref-specific API! Just a type: `ReactRe.reactRef`. Through the combination of `handler` and `instanceVars`, we can get ref support: 329 | 330 | ```reason 331 | type instanceVars = { 332 | mutable divRef: option Dom.element, /* dom refs directly return the element or null, no need for the `reactRef` type */ 333 | mutable componentRef: option ReactRe.reactRef 334 | }; 335 | let getInstanceVars () => {divRef: None, componentRef: None}; 336 | let getDivRef componentBag theRef => componentBag.instanceVars.divRef = Js.Null.to_opt theRef; 337 | let getComponentRef componentBag theRef => componentBag.instanceVars.componentRef = Some theRef; 338 | let render {props, handler} => 339 | 340 |
341 | ; 342 | ``` 343 | 344 | **Note** how [React DOM refs can be null](https://github.com/facebook/react/issues/9328#issuecomment-298438237). Which is why `getDivRef` converts `theRef` from a [JS nullable](http://bloomberg.github.io/bucklescript/Manual.html#_null_and_undefined) to an OCaml `option` (Some/None). 345 | 346 | Reason-React ref only accept callbacks. The string `ref` from ReactJS is a deprecated feature, which couldn't be easily removed due to the lack of types in JS. 347 | 348 | We also expose an escape hatch `ReactRe.refToJsObj` (type: `ReactRe.reactRef => Js.t {..}`) which turns your ref into a JS object you can freely use; **this is only used to access ReactJS component class methods**. 349 | 350 | ```reason 351 | let callSomethingDangerous componentBag => 352 | switch componentBag.instanceVars.theRef { 353 | | None => (); 354 | | Some r => (ReactRe.refToJsObj r);##someMethod 1 2 3 /* I solemnly swear that I am up to no good */ 355 | }; 356 | ``` 357 | 358 | If you're attaching `ref` on a DOM component, the type of the ref in the ref callback is `DOM.element`. See [Working with DOM](#working-with-dom) below for usage. 359 | 360 | ### Working with DOM 361 | 362 | The `ReactDOMRe` module below exposes an unsafe `domElementToObj`. That's all you need. Alternatively, we have experimental DOM bindings at [reason-js](https://github.com/BuckleTypes/reason-js). 363 | 364 | ## ReactDOM 365 | 366 | Reason-React's equivalent `ReactDOMRe` exposes: 367 | 368 | - `render : ReactRe.reactElement => Dom.element => unit` 369 | 370 | - `unmountComponentAtNode : Dom.element => unit` 371 | 372 | - `findDOMNode : ReactRe.reactRef => Dom.element` 373 | 374 | - `objToDOMProps : Js.t {..} => reactDOMProps` (see use-case in [Invalid Prop Name](invalid-prop-name)) 375 | 376 | And two helpers: 377 | 378 | - `domElementToObj : Dom.element => Js.t {..}`: turns a dom element into a Js object whose fields that you can dangerously access: `(ReactDOMRe.domElementToObj (ReactEventRe.Form.target event))##value`. 379 | 380 | - `renderToElementWithClassName : ReactRe.reactElement => string => unit`: convenience. Finds the (first) element of the provided class name and `render` to it. 381 | 382 | - `renderToElementWithId : ReactRe.reactElement => string => unit`: convenience. Finds the element of the provided id and `render` to it. 383 | 384 | ## ReactDOMServer 385 | 386 | Reason-React's equivalent `ReactDOMServerRe` exposes: 387 | 388 | - `renderToString : ReactRe.reactElement => string` 389 | 390 | - `renderToStaticMarkup : ReactRe.reactElement => string` 391 | 392 | ## Converting Over ReactJS Idioms 393 | 394 | ### Passing in Components Class as a Prop 395 | 396 | In ReactJS, `` is easy; in Reason-React, we can't trivially pass the whole component module ([explanations](http://facebook.github.io/reason/modules.html#modules-basic-modules)). Solution: 397 | 398 | ```reason 399 | ) /> 400 | ``` 401 | 402 | ### Invalid Prop Name 403 | Prop names like `type` (as in ``) aren't syntactically valid; `type` is a reserved keyword in Reason/OCaml. Use `` instead. This follows BuckleScript's [name mangling rules](http://bloomberg.github.io/bucklescript/Manual.html#_object_label_translation_convention). 404 | 405 | For `data-*` and `aria-*`, this is a bit trickier; You'd currently need to resort to using `ReactDOMRe.objToDOMProps` + the underlying desugared JSX call: 406 | 407 | ```reason 408 | ReactDOMRe.createElement 409 | "li" 410 | props::(ReactDOMRe.objToDOMProps {"className": "foo", "aria-selected": true}) 411 | [|child1, child2|] 412 | ``` 413 | 414 | For non-dom components, you'd need to expose valid prop names. 415 | 416 | ## Miscellaneous 417 | 418 | If reading a linear doc bores you, try this [alternative tutorial](https://github.com/glennsl/reason-react-quick-start/blob/master/quick-start.md)! 419 | 420 | - Reason-React doesn't support ReactJS context (yet). 421 | - No mixins/yes mixins =). OCaml's `include` is actually a form of mixin! With the bindings, you're essentially mixing in functionalities. There are several differences: 422 | - For us, the runtime metaprogramming of mixing in declarations from other modules is statically analyzed and compiled away (see the output), saving us code initiation cost. 423 | - Mixins are statically typed and prevent funny (ab)uses-cases. They're constrained to be easy to understand. 424 | - Since the mixins aren't provided by Reason-React proper, there's no library-specific learning overhead/magic mixin behavior (e.g. React.createClass's mixin will merge/override/warn in certain mixin properties). 425 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reason-react", 3 | "version": "0.1.3", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "exit 0", 8 | "start": "bsb -make-world -w", 9 | "clean": "bsb -clean-world && rm -f finalOutput/*.js" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/reasonml/reason-react.git" 17 | }, 18 | "devDependencies": { 19 | "bs-platform": "^1.6.0" 20 | }, 21 | "peerDependencies": { 22 | "react": "^15.0.0", 23 | "react-dom": "^15.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/reactDOMRe.re: -------------------------------------------------------------------------------- 1 | /* First time reading an OCaml/Reason/BuckleScript file? */ 2 | /* `external` is the foreign function call in OCaml. */ 3 | 4 | /* here we're saying `I guarantee that on the JS side, we have a `render` function in the module "react-dom" 5 | that takes in a reactElement, a dom element, and returns unit (nothing) */ 6 | 7 | /* It's like `let`, except you're pointing the implementation to the JS side. The compiler will inline these 8 | calls and add the appropriate `require("react-dom")` in the file calling this `render` */ 9 | 10 | external render : ReactRe.reactElement => Dom.element => unit = 11 | "render" [@@bs.val] [@@bs.module "react-dom"]; 12 | 13 | external _getElementsByClassName : string => array Dom.element = 14 | "document.getElementsByClassName" [@@bs.val]; 15 | external _getElementById : string => option Dom.element = 16 | "document.getElementById" [@@bs.val] [@@bs.return null_to_opt]; 17 | 18 | let renderToElementWithClassName reactElement className => { 19 | let elements = _getElementsByClassName className; 20 | if (Array.length elements == 0) { 21 | raise ( 22 | Invalid_argument ( 23 | "ReactDOMRE.renderToElementWithClassName: no element of class " ^ 24 | className ^ " found in the HTML." 25 | ) 26 | ) 27 | } else { 28 | render reactElement elements.(0) 29 | } 30 | }; 31 | 32 | let renderToElementWithId reactElement id => 33 | switch (_getElementById id) { 34 | | None => 35 | raise ( 36 | Invalid_argument ( 37 | "ReactDOMRE.renderToElementWithId : no element of id " ^ id ^ " found in the HTML." 38 | ) 39 | ) 40 | | Some element => render reactElement element 41 | }; 42 | 43 | external unmountComponentAtNode : Dom.element => unit = 44 | "unmountComponentAtNode" [@@bs.val] [@@bs.module "react-dom"]; 45 | 46 | external findDOMNode : ReactRe.reactRef => Dom.element = 47 | "findDOMNode" [@@bs.val] [@@bs.module "react-dom"]; 48 | 49 | external domElementToObj : Dom.element => Js.t {..} = "%identity"; 50 | 51 | type reactDOMProps; 52 | 53 | external objToDOMProps : Js.t {..} => reactDOMProps = "%identity"; 54 | 55 | type style; 56 | 57 | /* This list isn't exhaustive. We'll add more as we go. */ 58 | external props : 59 | key::string? => 60 | ref::(Js.null Dom.element => unit)? => 61 | 62 | /* react textarea/input */ 63 | defaultValue::string? => 64 | 65 | /* global html attributes */ 66 | accessKey::string? => 67 | className::string? => /* substitute for "class" */ 68 | contentEditable::Js.boolean? => 69 | contextMenu::string? => 70 | dir::string? => /* "ltr", "rtl" or "auto" */ 71 | draggable::Js.boolean? => 72 | hidden::Js.boolean? => 73 | id::string? => 74 | lang::string? => 75 | role::string? => /* ARIA role */ 76 | style::style? => 77 | spellCheck::Js.boolean? => 78 | tabIndex::int? => 79 | title::string? => 80 | 81 | /* html5 microdata */ 82 | itemID::string? => 83 | itemProp::string? => 84 | itemRef::string? => 85 | itemScope::Js.boolean? => 86 | itemType::string? => /* uri */ 87 | 88 | 89 | /* tag-specific html attributes */ 90 | accept::string? => 91 | acceptCharset::string? => 92 | action::string? => /* uri */ 93 | allowFullScreen::Js.boolean? => 94 | alt::string? => 95 | async::Js.boolean? => 96 | autoComplete::string? => /* has a fixed, but large-ish, set of possible values */ 97 | autoFocus::Js.boolean? => 98 | autoPlay::Js.boolean? => 99 | challenge::string? => 100 | charSet::string? => 101 | checked::Js.boolean? => 102 | cite::string? => /* uri */ 103 | cols::int? => 104 | colSpan::int? => 105 | content::string? => 106 | controls::Js.boolean? => 107 | coords::string? => /* set of values specifying the coordinates of a region */ 108 | data::string? => /* uri */ 109 | dateTime::string? => /* "valid date string with optional time" */ 110 | default::Js.boolean? => 111 | defer::Js.boolean? => 112 | disabled::Js.boolean? => 113 | download::string? => /* should really be either a boolean, signifying presence, or a string */ 114 | encType::string? => /* "application/x-www-form-urlencoded", "multipart/form-data" or "text/plain" */ 115 | form::string? => 116 | formAction::string? => /* uri */ 117 | headers::string? => 118 | height::string? => /* in html5 this can only be a number, but in html4 it can ba a percentage as well */ 119 | high::int? => 120 | href::string? => /* uri */ 121 | hrefLang::string? => 122 | htmlFor::string? => /* substitute for "for" */ 123 | httpEquiv::string? => /* has a fixed set of possible values */ 124 | icon::string? => /* uri? */ 125 | integrity::string? => 126 | keyType::string? => 127 | kind::string? => /* has a fixed set of possible values */ 128 | label::string? => 129 | list::string? => 130 | low::int? => 131 | manifest::string? => /* uri */ 132 | max::string? => /* should be int or Js.Date.t */ 133 | maxLength::int? => 134 | media::string? => /* a valid media query */ 135 | method::string? => /* "post" or "get" */ 136 | min::int? => 137 | minLength::int? => 138 | multiple::Js.boolean? => 139 | muted::Js.boolean? => 140 | name::string? => 141 | noValidate::Js.boolean? => 142 | open::Js.boolean? => 143 | optimum::int? => 144 | pattern::string? => /* valid Js RegExp */ 145 | placeholder::string? => 146 | poster::string? => /* uri */ 147 | preload::string? => /* "none", "metadata" or "auto" (and "" as a synonym for "auto") */ 148 | radioGroup::string? => 149 | readOnly::Js.boolean? => 150 | rel::string? => /* a space- or comma-separated (depending on the element) list of a fixed set of "link types" */ 151 | required::Js.boolean? => 152 | reversed::Js.boolean? => 153 | rows::int? => 154 | rowSpan::int? => 155 | sandbox::string? => /* has a fixed set of possible values */ 156 | scope::string? => /* has a fixed set of possible values */ 157 | scoped::Js.boolean? => 158 | scrolling::string? => /* html4 only, "auto", "yes" or "no" */ 159 | /* seamless - supported by React, but removed from the html5 spec */ 160 | selected::Js.boolean? => 161 | shape::string? => 162 | size::int? => 163 | sizes::string? => 164 | span::int? => 165 | src::string? => /* uri */ 166 | srcDoc::string? => 167 | srcLang::string? => 168 | srcSet::string? => 169 | start::int? => 170 | step::int? => 171 | summary::string? => /* deprecated */ 172 | target::string? => 173 | _type::string? => /* has a fixed but large-ish set of possible values */ 174 | useMap::string? => 175 | value::string? => 176 | width::string? => /* in html5 this can only be a number, but in html4 it can ba a percentage as well */ 177 | wrap::string? => /* "hard" or "soft" */ 178 | 179 | /* Clipboard events */ 180 | onCopy::(ReactEventRe.Clipboard.t => unit)? => 181 | onCut::(ReactEventRe.Clipboard.t => unit)? => 182 | onPaste::(ReactEventRe.Clipboard.t => unit)? => 183 | 184 | /* Composition events */ 185 | onCompositionEnd::(ReactEventRe.Composition.t => unit)? => 186 | onCompositionStart::(ReactEventRe.Composition.t => unit)? => 187 | onCompositionUpdate::(ReactEventRe.Composition.t => unit)? => 188 | 189 | /* Keyboard events */ 190 | onKeyDown::(ReactEventRe.Keyboard.t => unit)? => 191 | onKeyPress::(ReactEventRe.Keyboard.t => unit)? => 192 | onKeyUp::(ReactEventRe.Keyboard.t => unit)? => 193 | 194 | /* Focus events */ 195 | onFocus::(ReactEventRe.Focus.t => unit)? => 196 | onBlur::(ReactEventRe.Focus.t => unit)? => 197 | 198 | /* Form events */ 199 | onChange::(ReactEventRe.Form.t => unit)? => 200 | onInput::(ReactEventRe.Form.t => unit)? => 201 | onSubmit::(ReactEventRe.Form.t => unit)? => 202 | 203 | /* Mouse events */ 204 | onClick::(ReactEventRe.Mouse.t => unit)? => 205 | onContextMenu::(ReactEventRe.Mouse.t => unit)? => 206 | onDoubleClick::(ReactEventRe.Mouse.t => unit)? => 207 | onDrag::(ReactEventRe.Mouse.t => unit)? => 208 | onDragEnd::(ReactEventRe.Mouse.t => unit)? => 209 | onDragEnter::(ReactEventRe.Mouse.t => unit)? => 210 | onDragExit::(ReactEventRe.Mouse.t => unit)? => 211 | onDragLeave::(ReactEventRe.Mouse.t => unit)? => 212 | onDragOver::(ReactEventRe.Mouse.t => unit)? => 213 | onDragStart::(ReactEventRe.Mouse.t => unit)? => 214 | onDrop::(ReactEventRe.Mouse.t => unit)? => 215 | onMouseDown::(ReactEventRe.Mouse.t => unit)? => 216 | onMouseEnter::(ReactEventRe.Mouse.t => unit)? => 217 | onMouseLeave::(ReactEventRe.Mouse.t => unit)? => 218 | onMouseMove::(ReactEventRe.Mouse.t => unit)? => 219 | onMouseOut::(ReactEventRe.Mouse.t => unit)? => 220 | onMouseOver::(ReactEventRe.Mouse.t => unit)? => 221 | onMouseUp::(ReactEventRe.Mouse.t => unit)? => 222 | 223 | /* Selection events */ 224 | onSelect::(ReactEventRe.Selection.t => unit)? => 225 | 226 | /* Touch events */ 227 | onTouchCancel::(ReactEventRe.Touch.t => unit)? => 228 | onTouchEnd::(ReactEventRe.Touch.t => unit)? => 229 | onTouchMove::(ReactEventRe.Touch.t => unit)? => 230 | onTouchStart::(ReactEventRe.Touch.t => unit)? => 231 | 232 | /* UI events */ 233 | onScroll::(ReactEventRe.UI.t => unit)? => 234 | 235 | /* Wheel events */ 236 | onWheel::(ReactEventRe.Wheel.t => unit)? => 237 | 238 | /* Media events */ 239 | onAbort::(ReactEventRe.Media.t => unit)? => 240 | onCanPlay::(ReactEventRe.Media.t => unit)? => 241 | onCanPlayThrough::(ReactEventRe.Media.t => unit)? => 242 | onDurationChange::(ReactEventRe.Media.t => unit)? => 243 | onEmptied::(ReactEventRe.Media.t => unit)? => 244 | onEncrypetd::(ReactEventRe.Media.t => unit)? => 245 | onEnded::(ReactEventRe.Media.t => unit)? => 246 | onError::(ReactEventRe.Media.t => unit)? => 247 | onLoadedData::(ReactEventRe.Media.t => unit)? => 248 | onLoadedMetadata::(ReactEventRe.Media.t => unit)? => 249 | onLoadStart::(ReactEventRe.Media.t => unit)? => 250 | onPause::(ReactEventRe.Media.t => unit)? => 251 | onPlay::(ReactEventRe.Media.t => unit)? => 252 | onPlaying::(ReactEventRe.Media.t => unit)? => 253 | onProgress::(ReactEventRe.Media.t => unit)? => 254 | onRateChange::(ReactEventRe.Media.t => unit)? => 255 | onSeeked::(ReactEventRe.Media.t => unit)? => 256 | onSeeking::(ReactEventRe.Media.t => unit)? => 257 | onStalled::(ReactEventRe.Media.t => unit)? => 258 | onSuspend::(ReactEventRe.Media.t => unit)? => 259 | onTimeUpdate::(ReactEventRe.Media.t => unit)? => 260 | onVolumeChange::(ReactEventRe.Media.t => unit)? => 261 | onWaiting::(ReactEventRe.Media.t => unit)? => 262 | 263 | /* Image events */ 264 | onLoad::(ReactEventRe.Image.t => unit)? => 265 | /*onError::(ReactEventRe.Image.t => unit)? =>*/ /* duplicate */ 266 | 267 | /* Animation events */ 268 | onAnimationStart::(ReactEventRe.Animation.t => unit)? => 269 | onAnimationEnd::(ReactEventRe.Animation.t => unit)? => 270 | onAnimationIteration::(ReactEventRe.Animation.t => unit)? => 271 | 272 | /* Transition events */ 273 | onTransitionEnd::(ReactEventRe.Transition.t => unit)? => 274 | 275 | /* svg */ 276 | accentHeight::string? => 277 | accumulate::string? => 278 | additive::string? => 279 | alignmentBaseline::string? => 280 | allowReorder::string? => 281 | alphabetic::string? => 282 | amplitude::string? => 283 | arabicForm::string? => 284 | ascent::string? => 285 | attributeName::string? => 286 | attributeType::string? => 287 | autoReverse::string? => 288 | azimuth::string? => 289 | baseFrequency::string? => 290 | baseProfile::string? => 291 | baselineShift::string? => 292 | bbox::string? => 293 | begin::string? => 294 | bias::string? => 295 | by::string? => 296 | calcMode::string? => 297 | capHeight::string? => 298 | clip::string? => 299 | clipPath::string? => 300 | clipPathUnits::string? => 301 | clipRule::string? => 302 | colorInterpolation::string? => 303 | colorInterpolationFilters::string? => 304 | colorProfile::string? => 305 | colorRendering::string? => 306 | contentScriptType::string? => 307 | contentStyleType::string? => 308 | cursor::string? => 309 | cx::string? => 310 | cy::string? => 311 | d::string? => 312 | decelerate::string? => 313 | descent::string? => 314 | diffuseConstant::string? => 315 | direction::string? => 316 | display::string? => 317 | divisor::string? => 318 | dominantBaseline::string? => 319 | dur::string? => 320 | dx::string? => 321 | dy::string? => 322 | edgeMode::string? => 323 | elevation::string? => 324 | enableBackground::string? => 325 | end::string? => 326 | exponent::string? => 327 | externalResourcesRequired::string? => 328 | fill::string? => 329 | fillOpacity::string? => 330 | fillRule::string? => 331 | filter::string? => 332 | filterRes::string? => 333 | filterUnits::string? => 334 | floodColor::string? => 335 | floodOpacity::string? => 336 | focusable::string? => 337 | fontFamily::string? => 338 | fontSize::string? => 339 | fontSizeAdjust::string? => 340 | fontStretch::string? => 341 | fotStyle::string? => 342 | fontVariant::string? => 343 | fontWeight::string? => 344 | fomat::string? => 345 | from::string? => 346 | fx::string? => 347 | fy::string? => 348 | g1::string? => 349 | g2::string? => 350 | glyphName::string? => 351 | glyphOrientationHorizontal::string? => 352 | glyphOrientationVertical::string? => 353 | glyphRef::string? => 354 | gradientTransform::string? => 355 | gradientUnits::string? => 356 | hanging::string? => 357 | horizAdvX::string? => 358 | horizOriginX::string? => 359 | ideographic::string? => 360 | imageRendering::string? => 361 | in::string? => 362 | in2::string? => 363 | intercept::string? => 364 | k::string? => 365 | k1::string? => 366 | k2::string? => 367 | k3::string? => 368 | k4::string? => 369 | kernelMatrix::string? => 370 | kernelUnitLength::string? => 371 | kerning::string? => 372 | keyPoints::string? => 373 | keySplines::string? => 374 | keyTimes::string? => 375 | lengthAdjust::string? => 376 | letterSpacing::string? => 377 | lightingColor::string? => 378 | limitingConeAngle::string? => 379 | local::string? => 380 | markerEnd::string? => 381 | markerHeight::string? => 382 | markerMid::string? => 383 | markerStart::string? => 384 | markerUnits::string? => 385 | markerWidth::string? => 386 | mask::string? => 387 | maskContentUnits::string? => 388 | maskUnits::string? => 389 | mathematical::string? => 390 | mode::string? => 391 | numOctaves::string? => 392 | offset::string? => 393 | opacity::string? => 394 | operator::string? => 395 | order::string? => 396 | orient::string? => 397 | orientation::string? => 398 | origin::string? => 399 | overflow::string? => 400 | overlinePosition::string? => 401 | overlineThickness::string? => 402 | paintOrder::string? => 403 | panose1::string? => 404 | pathLength::string? => 405 | patternContentUnits::string? => 406 | patternTransform::string? => 407 | patternUnits::string? => 408 | pointerEvents::string? => 409 | points::string? => 410 | pointsAtX::string? => 411 | pointsAtY::string? => 412 | pointsAtZ::string? => 413 | preserveAlpha::string? => 414 | preserveAspectRatio::string? => 415 | primitiveUnits::string? => 416 | r::string? => 417 | radius::string? => 418 | refX::string? => 419 | refY::string? => 420 | renderingIntent::string? => 421 | repeatCount::string? => 422 | repeatDur::string? => 423 | requiredExtensions::string? => 424 | requiredFeatures::string? => 425 | restart::string? => 426 | result::string? => 427 | rotate::string? => 428 | rx::string? => 429 | ry::string? => 430 | scale::string? => 431 | seed::string? => 432 | shapeRendering::string? => 433 | slope::string? => 434 | spacing::string? => 435 | specularConstant::string? => 436 | specularExponent::string? => 437 | speed::string? => 438 | spreadMethod::string? => 439 | startOffset::string? => 440 | stdDeviation::string? => 441 | stemh::string? => 442 | stemv::string? => 443 | stitchTiles::string? => 444 | stopColor::string? => 445 | stopOpacity::string? => 446 | strikethroughPosition::string? => 447 | strikethroughThickness::string? => 448 | string::string? => 449 | stroke::string? => 450 | strokeDasharray::string? => 451 | strokeDashoffset::string? => 452 | strokeLinecap::string? => 453 | strokeLinejoin::string? => 454 | strokeMiterlimit::string? => 455 | strokeOpacity::string? => 456 | strokeWidth::string? => 457 | surfaceScale::string? => 458 | systemLanguage::string? => 459 | tableValues::string? => 460 | targetX::string? => 461 | targetY::string? => 462 | textAnchor::string? => 463 | textDecoration::string? => 464 | textLength::string? => 465 | textRendering::string? => 466 | _to::string? => 467 | transform::string? => 468 | u1::string? => 469 | u2::string? => 470 | underlinePosition::string? => 471 | underlineThickness::string? => 472 | unicode::string? => 473 | unicodeBidi::string? => 474 | unicodeRange::string? => 475 | unitsPerEm::string? => 476 | vAlphabetic::string? => 477 | vHanging::string? => 478 | vIdeographic::string? => 479 | vMathematical::string? => 480 | values::string? => 481 | vectorEffect::string? => 482 | version::string? => 483 | vertAdvX::string? => 484 | vertAdvY::string? => 485 | vertOriginX::string? => 486 | vertOriginY::string? => 487 | viewBox::string? => 488 | viewTarget::string? => 489 | visibility::string? => 490 | /*width::string? =>*/ 491 | widths::string? => 492 | wordSpacing::string? => 493 | writingMode::string? => 494 | x::string? => 495 | x1::string? => 496 | x2::string? => 497 | xChannelSelector::string? => 498 | xHeight::string? => 499 | xlinkActuate::string? => 500 | xlinkArcrole::string? => 501 | xlinkHref::string? => 502 | xlinkRole::string? => 503 | xlinkShow::string? => 504 | xlinkTitle::string? => 505 | xlinkType::string? => 506 | xmlns::string? => 507 | xmlnsXlink::string? => 508 | xmlBase::string? => 509 | xmlLang::string? => 510 | xmlSpace::string? => 511 | y::string? => 512 | y1::string? => 513 | y2::string? => 514 | yChannelSelector::string? => 515 | z::string? => 516 | zoomAndPan::string? => 517 | 518 | /* RDFa */ 519 | about::string? => 520 | datatype::string? => 521 | inlist::string? => 522 | prefix::string? => 523 | property::string? => 524 | resource::string? => 525 | typeof::string? => 526 | vocab::string? => 527 | 528 | /* react-specific */ 529 | dangerouslySetInnerHTML::Js.t {. __html: string }? => 530 | suppressContentEditableWarning::Js.boolean? => 531 | 532 | unit => 533 | reactDOMProps = 534 | "" [@@bs.obj]; 535 | 536 | external createElement : 537 | string => props::reactDOMProps? => array ReactRe.reactElement => ReactRe.reactElement = 538 | "createElement" [@@bs.splice] [@@bs.val] [@@bs.module "react"]; 539 | 540 | module Style = { 541 | type t = style; 542 | 543 | external make : 544 | 545 | /* CSS2Properties: https://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSS2Properties */ 546 | azimuth::string? => 547 | background::string? => 548 | backgroundAttachment::string? => 549 | backgroundColor::string? => 550 | backgroundImage::string? => 551 | backgroundPosition::string? => 552 | backgroundRepeat::string? => 553 | border::string? => 554 | borderCollapse::string? => 555 | borderColor::string? => 556 | borderSpacing::string? => 557 | borderStyle::string? => 558 | borderTop::string? => 559 | borderRight::string? => 560 | borderBottom::string? => 561 | borderLeft::string? => 562 | borderTopColor::string? => 563 | borderRightColor::string? => 564 | borderBottomColor::string? => 565 | borderLeftColor::string? => 566 | borderTopStyle::string? => 567 | borderRightStyle::string? => 568 | borderBottomStyle::string? => 569 | borderLeftStyle::string? => 570 | borderTopWidth::string? => 571 | borderRightWidth::string? => 572 | borderBottomWidth::string? => 573 | borderLeftWidth::string? => 574 | borderWidth::string? => 575 | bottom::string? => 576 | captionSide::string? => 577 | clear::string? => 578 | clip::string? => 579 | color::string? => 580 | content::string? => 581 | counterIncrement::string? => 582 | counterReset::string? => 583 | cue::string? => 584 | cueAfter::string? => 585 | cueBefore::string? => 586 | cursor::string? => 587 | direction::string? => 588 | display::string? => 589 | elevation::string? => 590 | emptyCells::string? => 591 | cssFloat::string? => 592 | font::string? => 593 | fontFamily::string? => 594 | fontSize::string? => 595 | fontSizeAdjust::string? => 596 | fontStretch::string? => 597 | fontStyle::string? => 598 | fontVariant::string? => 599 | fontWeight::string? => 600 | height::string? => 601 | left::string? => 602 | letterSpacing::string? => 603 | lineHeight::string? => 604 | listStyle::string? => 605 | listStyleImage::string? => 606 | listStylePosition::string? => 607 | listStyleType::string? => 608 | margin::string? => 609 | marginTop::string? => 610 | marginRight::string? => 611 | marginBottom::string? => 612 | marginLeft::string? => 613 | markerOffset::string? => 614 | marks::string? => 615 | maxHeight::string? => 616 | maxWidth::string? => 617 | minHeight::string? => 618 | minWidth::string? => 619 | orphans::string? => 620 | outline::string? => 621 | outlineColor::string? => 622 | outlineStyle::string? => 623 | outlineWidth::string? => 624 | overflow::string? => 625 | padding::string? => 626 | paddingTop::string? => 627 | paddingRight::string? => 628 | paddingBottom::string? => 629 | paddingLeft::string? => 630 | page::string? => 631 | pageBreakAfter::string? => 632 | pageBreakBefore::string? => 633 | pageBreakInside::string? => 634 | pause::string? => 635 | pauseAfter::string? => 636 | pauseBefore::string? => 637 | pitch::string? => 638 | pitchRange::string? => 639 | playDuring::string? => 640 | position::string? => 641 | quotes::string? => 642 | richness::string? => 643 | right::string? => 644 | size::string? => 645 | speak::string? => 646 | speakHeader::string? => 647 | speakNumeral::string? => 648 | speakPunctuation::string? => 649 | speechRate::string? => 650 | stress::string? => 651 | tableLayout::string? => 652 | textAlign::string? => 653 | textDecoration::string? => 654 | textIndent::string? => 655 | textShadow::string? => 656 | textTransform::string? => 657 | top::string? => 658 | unicodeBidi::string? => 659 | verticalAlign::string? => 660 | visibility::string? => 661 | voiceFamily::string? => 662 | volume::string? => 663 | whiteSpace::string? => 664 | widows::string? => 665 | width::string? => 666 | wordSpacing::string? => 667 | zIndex::string? => 668 | 669 | /* Below properties based on https://www.w3.org/Style/CSS/all-properties */ 670 | 671 | /* Color Level 3 - REC */ 672 | opacity::string? => 673 | 674 | /* Backgrounds and Borders Level 3 - CR */ 675 | /* backgroundRepeat - already defined by CSS2Properties */ 676 | /* backgroundAttachment - already defined by CSS2Properties */ 677 | backgroundOrigin::string? => 678 | backgroundSize::string? => 679 | backgroundClip::string? => 680 | borderRadius::string? => 681 | borderTopLeftRadius::string? => 682 | borderTopRightRadius::string? => 683 | borderBottomLeftRadius::string? => 684 | borderBottomRightRadius::string? => 685 | borderImage::string? => 686 | borderImageSource::string? => 687 | borderImageSlice::string? => 688 | borderImageWidth::string? => 689 | borderImageOutset::string? => 690 | borderImageRepeat::string? => 691 | boxShadow::string? => 692 | 693 | /* Multi-column Layout - CR */ 694 | columns::string? => 695 | columnCount::string? => 696 | columnFill::string? => 697 | columnGap::string? => 698 | columnRule::string? => 699 | columnRuleColor::string? => 700 | columnRuleStyle::string? => 701 | columnRuleWidth::string? => 702 | columnSpan::string? => 703 | columnWidth::string? => 704 | breakAfter::string? => 705 | breakBefore::string? => 706 | breakInside::string? => 707 | 708 | /* Speech - CR */ 709 | rest::string? => 710 | restAfter::string? => 711 | restBefore::string? => 712 | speakAs::string? => 713 | voiceBalance::string? => 714 | voiceDuration::string? => 715 | voicePitch::string? => 716 | voiceRange::string? => 717 | voiceRate::string? => 718 | voiceStress::string? => 719 | voiceVolume::string? => 720 | 721 | /* Image Values and Replaced Content Level 3 - CR */ 722 | objectFit::string? => 723 | objectPosition::string? => 724 | imageResolution::string? => 725 | imageOrientation::string? => 726 | 727 | /* Flexible Box Layout - CR */ 728 | alignContent::string? => 729 | alignItems::string? => 730 | alignSelf::string? => 731 | flex::string? => 732 | flexBasis::string? => 733 | flexDirection::string? => 734 | flexFlow::string? => 735 | flexGrow::string? => 736 | flexShrink::string? => 737 | flexWrap::string? => 738 | justifyContent::string? => 739 | order::string? => 740 | 741 | /* Text Decoration Level 3 - CR */ 742 | /* textDecoration - already defined by CSS2Properties */ 743 | textDecorationColor::string? => 744 | textDecorationLine::string? => 745 | textDecorationSkip::string? => 746 | textDecorationStyle::string? => 747 | textEmphasis::string? => 748 | textEmphasisColor::string? => 749 | textEmphasisPosition::string? => 750 | textEmphasisStyle::string? => 751 | /* textShadow - already defined by CSS2Properties */ 752 | textUnderlinePosition::string? => 753 | 754 | /* Fonts Level 3 - CR */ 755 | fontFeatureSettings::string? => 756 | fontKerning::string? => 757 | fontLanguageOverride::string? => 758 | /* fontSizeAdjust - already defined by CSS2Properties */ 759 | /* fontStretch - already defined by CSS2Properties */ 760 | fontSynthesis::string? => 761 | forntVariantAlternates::string? => 762 | fontVariantCaps::string? => 763 | fontVariantEastAsian::string? => 764 | fontVariantLigatures::string? => 765 | fontVariantNumeric::string? => 766 | fontVariantPosition::string? => 767 | 768 | /* Cascading and Inheritance Level 3 - CR */ 769 | all::string? => 770 | 771 | /* Writing Modes Level 3 - CR */ 772 | glyphOrientationVertical::string? => 773 | textCombineUpright::string? => 774 | textOrientation::string? => 775 | writingMode::string? => 776 | 777 | /* Shapes Level 1 - CR */ 778 | shapeImageThreshold::string? => 779 | shapeMargin::string? => 780 | shapeOutside::string? => 781 | 782 | /* Masking Level 1 - CR */ 783 | clipPath::string? => 784 | clipRule::string? => 785 | mask::string? => 786 | maskBorder::string? => 787 | maskBorderMode::string? => 788 | maskBorderOutset::string? => 789 | maskBorderRepeat::string? => 790 | maskBorderSlice::string? => 791 | maskBorderSource::string? => 792 | maskBorderWidth::string? => 793 | maskClip::string? => 794 | maskComposite::string? => 795 | maskImage::string? => 796 | maskMode::string? => 797 | maskOrigin::string? => 798 | maskPosition::string? => 799 | maskRepeat::string? => 800 | maskSize::string? => 801 | maskType::string? => 802 | 803 | /* Compositing and Blending Level 1 - CR */ 804 | backgroundBlendMode::string? => 805 | isolation::string? => 806 | mixBlendMode::string? => 807 | 808 | /* Fragmentation Level 3 - CR */ 809 | boxDecorationBreak::string? => 810 | /* breakAfter - already defined by Multi-column Layout */ 811 | /* breakBefore - already defined by Multi-column Layout */ 812 | /* breakInside - already defined by Multi-column Layout */ 813 | 814 | /* Basic User Interface Level 3 - CR */ 815 | boxSizing::string? => 816 | caretColor::string? => 817 | navDown::string? => 818 | navLeft::string? => 819 | navRight::string? => 820 | navUp::string? => 821 | outlineOffset::string? => 822 | resize::string? => 823 | textOverflow::string? => 824 | 825 | /* Grid Layout Level 1 - CR */ 826 | grid::string? => 827 | gridArea::string? => 828 | gridAutoColumns::string? => 829 | gridAutoFlow::string? => 830 | gridAutoRows::string? => 831 | gridColumn::string? => 832 | gridColumnEnd::string? => 833 | gridColumnGap::string? => 834 | gridColumnStart::string? => 835 | gridGap::string? => 836 | gridRow::string? => 837 | gridRowEnd::string? => 838 | gridRowGap::string? => 839 | gridRowStart::string? => 840 | gridTemplate::string? => 841 | gridTempalteAreas::string? => 842 | gridTemplateColumns::string? => 843 | gridTemplateRows::string? => 844 | 845 | /* Will Change Level 1 - CR */ 846 | willChange::string? => 847 | 848 | /* Text Level 3 - LC */ 849 | hangingPunctuation::string? => 850 | hyphens::string? => 851 | /* letterSpacing - already defined by CSS2Properties */ 852 | lineBreak::string? => 853 | overflowWrap::string? => 854 | tabSize::string? => 855 | /* textAlign - already defined by CSS2Properties */ 856 | textAlignLast::string? => 857 | textJustify::string? => 858 | wordBreak::string? => 859 | wordWrap::string? => 860 | 861 | /* Animations - WD */ 862 | animation::string? => 863 | animationDelay::string? => 864 | animationDirection::string? => 865 | anumationDuration::string? => 866 | animationFillMode::string? => 867 | animationIterationCount::string? => 868 | animationName::string? => 869 | animationPlayState::string? => 870 | animationTimingFunction::string? => 871 | 872 | /* Transitions - WD */ 873 | transition::string? => 874 | transitionDelay::string? => 875 | transitionDuration::string? => 876 | transitionProperty::string? => 877 | transitionTimingFunction::string? => 878 | 879 | /* Transforms Level 1 - WD */ 880 | backfaceVisibility::string? => 881 | perspective::string? => 882 | perspectiveOrigin::string? => 883 | transform::string? => 884 | transformOrigin::string? => 885 | transformStyle::string? => 886 | 887 | /* Box Alignment Level 3 - WD */ 888 | /* alignContent - already defined by Flexible Box Layout */ 889 | /* alignItems - already defined by Flexible Box Layout */ 890 | justifyItems::string? => 891 | justifySelf::string? => 892 | placeContent::string? => 893 | placeItems::string? => 894 | placeSelf::string? => 895 | 896 | /* Basic User Interface Level 4 - FPWD */ 897 | appearance::string? => 898 | caret::string? => 899 | caretAnimation::string? => 900 | caretShape::string? => 901 | userSelect::string? => 902 | 903 | /* Overflow Level 3 - WD */ 904 | maxLines::string? => 905 | 906 | /* Basix Box Model - WD */ 907 | marqueeDirection::string? => 908 | marqueeLoop::string? => 909 | marqueeSpeed::string? => 910 | marqueeStyle::string? => 911 | overflowStyle::string? => 912 | rotation::string? => 913 | rotationPoint::string? => 914 | 915 | /* svg */ 916 | fill::string? => 917 | stroke::string? => 918 | strokeWidth::string? => 919 | strokeMiterlimit::string? => 920 | 921 | /* Not added yet 922 | * ------------- 923 | * Generated Content for Paged Media - WD 924 | * Generated Content Level 3 - WD 925 | * Line Grid Level 1 - WD 926 | * Regions - WD 927 | * Inline Layout Level 3 - WD 928 | * Round Display Level 1 - WD 929 | * Ruby Layout Level 1 - WD 930 | * Inline Layout Level 3 - WD 931 | * Image Values and Replaced Content Level 4 - WD 932 | * Positioned Layout Level 3 - WD 933 | * Filter Effects Level 1 - -WD 934 | * Lists and Counters Level 3 - WD 935 | * Exclusions Level 1 - WD 936 | * Text Level 4 - FPWD 937 | * SVG Markers - FPWD 938 | * Motion Path Level 1 - FPWD 939 | * Color Level 4 - FPWD 940 | * SVG Strokes - FPWD 941 | * Table Level 3 - FPWD 942 | */ 943 | 944 | unit => 945 | style = "" [@@bs.obj]; 946 | 947 | let combine : style => style => style = [%bs.raw {| 948 | function (a, b) { 949 | return Object.assign({}, a, b); 950 | } 951 | |}]; 952 | 953 | } 954 | -------------------------------------------------------------------------------- /src/reactDOMServerRe.re: -------------------------------------------------------------------------------- 1 | external renderToString : ReactRe.reactElement => string = 2 | "renderToString" [@@bs.val] [@@bs.module "react-dom/server"]; 3 | 4 | external renderToStaticMarkup : ReactRe.reactElement => string = 5 | "renderToStaticMarkup" [@@bs.val] [@@bs.module "react-dom/server"]; 6 | -------------------------------------------------------------------------------- /src/reactEventRe.re: -------------------------------------------------------------------------------- 1 | type synthetic 'a; 2 | 3 | module MakeSyntheticWrapper (Type : {type t;}) => { 4 | external bubbles : Type.t => bool = "" [@@bs.get]; 5 | external cancelable : Type.t => bool = "" [@@bs.get]; 6 | external currentTarget : Type.t => Dom.element = "" [@@bs.get]; /* Should return Dom.evetTarget */ 7 | external defaultPrevented : Type.t => bool = "" [@@bs.get]; 8 | external eventPhase : Type.t => int = "" [@@bs.get]; 9 | external isTrusted : Type.t => bool = "" [@@bs.get]; 10 | external nativeEvent : Type.t => Js.t {..} = "" [@@bs.get]; /* Should return Dom.event */ 11 | 12 | external preventDefault : unit = "" [@@bs.send.pipe: Type.t]; 13 | external isDefaultPrevented : bool = "" [@@bs.send.pipe: Type.t]; 14 | external stopPropagation : unit = "" [@@bs.send.pipe: Type.t]; 15 | external isPropagationStopped : bool = "" [@@bs.send.pipe: Type.t]; 16 | external target : Type.t => Dom.element = "" [@@bs.get]; /* Should return Dom.evetTarget */ 17 | 18 | external timeStamp : Type.t => float = "" [@@bs.get]; 19 | external _type : Type.t => string = "type" [@@bs.get]; 20 | 21 | external persist : unit = "" [@@bs.send.pipe: Type.t]; 22 | }; 23 | 24 | module Synthetic = { 25 | type tag; 26 | type t = synthetic tag; 27 | 28 | external bubbles : synthetic 'a => bool = "" [@@bs.get]; 29 | external cancelable : synthetic 'a => bool = "" [@@bs.get]; 30 | external currentTarget : synthetic 'a => Dom.element = "" [@@bs.get]; /* Should return Dom.evetTarget */ 31 | external defaultPrevented : synthetic 'a => bool = "" [@@bs.get]; 32 | external eventPhase : synthetic 'a => int = "" [@@bs.get]; 33 | external isTrusted : synthetic 'a => bool = "" [@@bs.get]; 34 | external nativeEvent : synthetic 'a => Js.t {..} = "" [@@bs.get]; /* Should return Dom.event */ 35 | 36 | external preventDefault : unit = "" [@@bs.send.pipe: synthetic 'a]; 37 | external isDefaultPrevented : bool = "" [@@bs.send.pipe: synthetic 'a]; 38 | external stopPropagation : unit = "" [@@bs.send.pipe: synthetic 'a]; 39 | external isPropagationStopped : bool = "" [@@bs.send.pipe: synthetic 'a]; 40 | external target : synthetic 'a => Dom.element = "" [@@bs.get]; /* Should return Dom.evetTarget */ 41 | 42 | external timeStamp : synthetic 'a => float = "" [@@bs.get]; 43 | external _type : synthetic 'a => string = "type" [@@bs.get]; 44 | 45 | external persist : unit = "" [@@bs.send.pipe: synthetic 'a]; 46 | }; 47 | 48 | /* Cast any event type to the general synthetic type. This is safe, since synthetic is more general */ 49 | external toSyntheticEvent: synthetic 'a => Synthetic.t = "%identity"; 50 | 51 | module Clipboard = { 52 | type tag; 53 | type t = synthetic tag; 54 | 55 | include MakeSyntheticWrapper { type nonrec t = t }; 56 | 57 | external clipboardData : t => Js.t {..} = "" [@@bs.get]; /* Should return Dom.dataTransfer */ 58 | }; 59 | 60 | module Composition = { 61 | type tag; 62 | type t = synthetic tag; 63 | 64 | include MakeSyntheticWrapper { type nonrec t = t }; 65 | 66 | external data : t => string = "" [@@bs.get]; 67 | }; 68 | 69 | module Keyboard = { 70 | type tag; 71 | type t = synthetic tag; 72 | 73 | include MakeSyntheticWrapper { type nonrec t = t }; 74 | 75 | external altKey : t => bool = "" [@@bs.get]; 76 | external charCode : t => int = "" [@@bs.get]; 77 | external ctrlKey : t => bool = "" [@@bs.get]; 78 | external getModifierState : string => bool = "" [@@bs.send.pipe: t]; 79 | external key : t => string = "" [@@bs.get]; 80 | external keyCode : t => int = "" [@@bs.get]; 81 | external locale : t => string = "" [@@bs.get]; 82 | external location : t => int = "" [@@bs.get]; 83 | external metaKey : t => bool = "" [@@bs.get]; 84 | external repeat : t => bool = "" [@@bs.get]; 85 | external shiftKey : t => bool = "" [@@bs.get]; 86 | external which : t => int = "" [@@bs.get]; 87 | }; 88 | 89 | module Focus = { 90 | type tag; 91 | type t = synthetic tag; 92 | 93 | include MakeSyntheticWrapper { type nonrec t = t }; 94 | 95 | external relatedTarget : t => Dom.element = "" [@@bs.get]; /* Should return Dom.eventTarget */ 96 | }; 97 | 98 | module Form = { 99 | type tag; 100 | type t = synthetic tag; 101 | 102 | include MakeSyntheticWrapper { type nonrec t = t }; 103 | }; 104 | 105 | module Mouse = { 106 | type tag; 107 | type t = synthetic tag; 108 | 109 | include MakeSyntheticWrapper { type nonrec t = t }; 110 | 111 | external altKey : t => bool = "" [@@bs.get]; 112 | external button : t => int = "" [@@bs.get]; 113 | external buttons : t => int = "" [@@bs.get]; 114 | external clientX : t => int = "" [@@bs.get]; 115 | external clientY : t => int = "" [@@bs.get]; 116 | external ctrlKey : t => bool = "" [@@bs.get]; 117 | external getModifierState : string => bool = "" [@@bs.send.pipe: t]; 118 | external metaKey : t => bool = "" [@@bs.get]; 119 | external pageX : t => int = "" [@@bs.get]; 120 | external pageY : t => int = "" [@@bs.get]; 121 | external relatedTarget : t => Dom.element = "" [@@bs.get]; /* Should return Dom.eventTarget */ 122 | external screenX : t => int = "" [@@bs.get]; 123 | external screenY : t => int = "" [@@bs.get]; 124 | external shiftKey : t => bool = "" [@@bs.get]; 125 | }; 126 | 127 | module Selection = { 128 | type tag; 129 | type t = synthetic tag; 130 | 131 | include MakeSyntheticWrapper { type nonrec t = t }; 132 | }; 133 | 134 | module Touch = { 135 | type tag; 136 | type t = synthetic tag; 137 | 138 | include MakeSyntheticWrapper { type nonrec t = t }; 139 | 140 | external altKey : t => bool = "" [@@bs.get]; 141 | external changedTouches : t => Js.t {..} = "" [@@bs.get]; /* Should return Dom.touchList */ 142 | external ctrlKey : t => bool = "" [@@bs.get]; 143 | external getModifierState : string => bool = "" [@@bs.send.pipe: t]; 144 | external metaKey : t => bool = "" [@@bs.get]; 145 | external shiftKey : t => bool = "" [@@bs.get]; 146 | external targetTouches : t => Js.t {..} = "" [@@bs.get]; /* Should return Dom.touchList */ 147 | external touches : t => Js.t {..} = "" [@@bs.get]; /* Should return Dom.touchList */ 148 | }; 149 | 150 | module UI = { 151 | type tag; 152 | type t = synthetic tag; 153 | 154 | include MakeSyntheticWrapper { type nonrec t = t }; 155 | 156 | external detail : t => int = "" [@@bs.get]; 157 | external view : t => Dom.window = "" [@@bs.get]; /* Should return DOMAbstractView/WindowProxy */ 158 | }; 159 | 160 | module Wheel = { 161 | type tag; 162 | type t = synthetic tag; 163 | 164 | include MakeSyntheticWrapper { type nonrec t = t }; 165 | 166 | external deltaMode : t => int = "" [@@bs.get]; 167 | external deltaX : t => float = "" [@@bs.get]; 168 | external deltaY : t => float = "" [@@bs.get]; 169 | external deltaZ : t => float = "" [@@bs.get]; 170 | }; 171 | 172 | module Media = { 173 | type tag; 174 | type t = synthetic tag; 175 | 176 | include MakeSyntheticWrapper { type nonrec t = t }; 177 | }; 178 | 179 | module Image = { 180 | type tag; 181 | type t = synthetic tag; 182 | 183 | include MakeSyntheticWrapper { type nonrec t = t }; 184 | }; 185 | 186 | module Animation = { 187 | type tag; 188 | type t = synthetic tag; 189 | 190 | include MakeSyntheticWrapper { type nonrec t = t }; 191 | 192 | external animationName : t => string = "" [@@bs.get]; 193 | external pseudoElement : t => string = "" [@@bs.get]; 194 | external elapsedTime : t => float = "" [@@bs.get]; 195 | }; 196 | 197 | module Transition = { 198 | type tag; 199 | type t = synthetic tag; 200 | 201 | include MakeSyntheticWrapper { type nonrec t = t }; 202 | 203 | external propertyName : t => string = "" [@@bs.get]; 204 | external pseudoElement : t => string = "" [@@bs.get]; 205 | external elapsedTime : t => float = "" [@@bs.get]; 206 | }; 207 | -------------------------------------------------------------------------------- /src/reactEventRe.rei: -------------------------------------------------------------------------------- 1 | /* This is the whole synthetic event system of ReactJS/Reason-React. The first module `Synthetic` represents 2 | the generic synthetic event. The rest are the specific ones. 3 | 4 | In each module, the type `t` commonly means "the type of that module" (OCaml convention). In our case, e.g. 5 | `ReactEventRe.Mouse.t` represents a ReactJS synthetic mouse event. You'd use it to type your props: 6 | 7 | ``` 8 | type props = { 9 | onClick: ReactEventRe.Mouse.t => unit 10 | }; 11 | ``` 12 | 13 | All the methods and properties of a type of event are in the module, as seen below. 14 | 15 | Each module also has a `tag` type. You can ignore it; they're only needed by their `t` type. This way, we 16 | get to allow a base `Synthetic` event module with generic methods. So e.g. even a mouse event (`Mouse.t`) 17 | get to be passed to a generic handler: 18 | 19 | ``` 20 | let handleClick {state, props} event => { 21 | ReactEventRe.Mouse.preventDefault event; 22 | ... 23 | }; 24 | let handleSubmit {state, props} event => { 25 | /* this handler can be triggered by either a Keyboard or a Mouse event; conveniently use the generic 26 | preventDefault */ 27 | ReactEventRe.Synthetic.preventDefault event; 28 | ... 29 | }; 30 | 31 | let render _ => ; 32 | ``` 33 | 34 | How to translate idioms from ReactJS: 35 | 36 | 1. myMouseEvent.preventDefault() -> ReactEventRe.Mouse.preventDefault myMouseEvent 37 | 2. myKeyboardEvent.which -> ReactEventRe.Keyboard.which myMouseEvent 38 | */ 39 | 40 | type synthetic 'a; 41 | 42 | module Synthetic: { 43 | type tag; 44 | type t = synthetic tag ; 45 | 46 | external bubbles : synthetic 'a => bool = "" [@@bs.get]; 47 | external cancelable : synthetic 'a => bool = "" [@@bs.get]; 48 | external currentTarget : synthetic 'a => Dom.element = "" [@@bs.get]; 49 | external defaultPrevented : synthetic 'a => bool = "" [@@bs.get]; 50 | external eventPhase : synthetic 'a => int = "" [@@bs.get]; 51 | external isTrusted : synthetic 'a => bool = "" [@@bs.get]; 52 | external nativeEvent : synthetic 'a => Js.t {..} = "" [@@bs.get]; 53 | 54 | external preventDefault : unit = "" [@@bs.send.pipe: synthetic 'a]; 55 | external isDefaultPrevented : bool = "" [@@bs.send.pipe: synthetic 'a]; 56 | external stopPropagation : unit = "" [@@bs.send.pipe: synthetic 'a]; 57 | external isPropagationStopped : bool = "" [@@bs.send.pipe: synthetic 'a]; 58 | external target : synthetic 'a => Dom.element = "" [@@bs.get]; 59 | 60 | external timeStamp : synthetic 'a => float = "" [@@bs.get]; 61 | external _type : synthetic 'a => string = "type" [@@bs.get]; 62 | 63 | external persist : unit = "" [@@bs.send.pipe: synthetic 'a]; 64 | }; 65 | 66 | /* Cast any event type to the general synthetic type. This is safe, since synthetic is more general */ 67 | external toSyntheticEvent: synthetic 'a => Synthetic.t = "%identity"; 68 | 69 | module Clipboard: { 70 | type tag; 71 | type t = synthetic tag; 72 | 73 | external bubbles : t => bool = "" [@@bs.get]; 74 | external cancelable : t => bool = "" [@@bs.get]; 75 | external currentTarget : t => Dom.element = "" [@@bs.get]; 76 | external defaultPrevented : t => bool = "" [@@bs.get]; 77 | external eventPhase : t => int = "" [@@bs.get]; 78 | external isTrusted : t => bool = "" [@@bs.get]; 79 | external nativeEvent : t => Js.t {..} = "" [@@bs.get]; 80 | 81 | external preventDefault : unit = "" [@@bs.send.pipe: t]; 82 | external isDefaultPrevented : bool = "" [@@bs.send.pipe: t]; 83 | external stopPropagation : unit = "" [@@bs.send.pipe: t]; 84 | external isPropagationStopped : bool = "" [@@bs.send.pipe: t]; 85 | external target : t => Dom.element = "" [@@bs.get]; 86 | 87 | external timeStamp : t => float = "" [@@bs.get]; 88 | external _type : t => string = "type" [@@bs.get]; 89 | 90 | external persist : unit = "" [@@bs.send.pipe: t]; 91 | 92 | external clipboardData : t => Js.t {..} = "" [@@bs.get]; /* Should return Dom.dataTransfer */ 93 | }; 94 | 95 | module Composition: { 96 | type tag; 97 | type t = synthetic tag; 98 | 99 | external bubbles : t => bool = "" [@@bs.get]; 100 | external cancelable : t => bool = "" [@@bs.get]; 101 | external currentTarget : t => Dom.element = "" [@@bs.get]; 102 | external defaultPrevented : t => bool = "" [@@bs.get]; 103 | external eventPhase : t => int = "" [@@bs.get]; 104 | external isTrusted : t => bool = "" [@@bs.get]; 105 | external nativeEvent : t => Js.t {..} = "" [@@bs.get]; 106 | 107 | external preventDefault : unit = "" [@@bs.send.pipe: t]; 108 | external isDefaultPrevented : bool = "" [@@bs.send.pipe: t]; 109 | external stopPropagation : unit = "" [@@bs.send.pipe: t]; 110 | external isPropagationStopped : bool = "" [@@bs.send.pipe: t]; 111 | external target : t => Dom.element = "" [@@bs.get]; 112 | 113 | external timeStamp : t => float = "" [@@bs.get]; 114 | external _type : t => string = "type" [@@bs.get]; 115 | 116 | external persist : unit = "" [@@bs.send.pipe: t]; 117 | 118 | external data : t => string = "" [@@bs.get]; 119 | }; 120 | 121 | module Keyboard: { 122 | type tag; 123 | type t = synthetic tag; 124 | 125 | external bubbles : t => bool = "" [@@bs.get]; 126 | external cancelable : t => bool = "" [@@bs.get]; 127 | external currentTarget : t => Dom.element = "" [@@bs.get]; 128 | external defaultPrevented : t => bool = "" [@@bs.get]; 129 | external eventPhase : t => int = "" [@@bs.get]; 130 | external isTrusted : t => bool = "" [@@bs.get]; 131 | external nativeEvent : t => Js.t {..} = "" [@@bs.get]; 132 | 133 | external preventDefault : unit = "" [@@bs.send.pipe: t]; 134 | external isDefaultPrevented : bool = "" [@@bs.send.pipe: t]; 135 | external stopPropagation : unit = "" [@@bs.send.pipe: t]; 136 | external isPropagationStopped : bool = "" [@@bs.send.pipe: t]; 137 | external target : t => Dom.element = "" [@@bs.get]; 138 | 139 | external timeStamp : t => float = "" [@@bs.get]; 140 | external _type : t => string = "type" [@@bs.get]; 141 | 142 | external persist : unit = "" [@@bs.send.pipe: t]; 143 | 144 | external altKey : t => bool = "" [@@bs.get]; 145 | external charCode : t => int = "" [@@bs.get]; 146 | external ctrlKey : t => bool = "" [@@bs.get]; 147 | external getModifierState : string => bool = "" [@@bs.send.pipe: t]; 148 | external key : t => string = "" [@@bs.get]; 149 | external keyCode : t => int = "" [@@bs.get]; 150 | external locale : t => string = "" [@@bs.get]; 151 | external location : t => int = "" [@@bs.get]; 152 | external metaKey : t => bool = "" [@@bs.get]; 153 | external repeat : t => bool = "" [@@bs.get]; 154 | external shiftKey : t => bool = "" [@@bs.get]; 155 | external which : t => int = "" [@@bs.get]; 156 | }; 157 | 158 | module Focus: { 159 | type tag; 160 | type t = synthetic tag; 161 | 162 | external bubbles : t => bool = "" [@@bs.get]; 163 | external cancelable : t => bool = "" [@@bs.get]; 164 | external currentTarget : t => Dom.element = "" [@@bs.get]; 165 | external defaultPrevented : t => bool = "" [@@bs.get]; 166 | external eventPhase : t => int = "" [@@bs.get]; 167 | external isTrusted : t => bool = "" [@@bs.get]; 168 | external nativeEvent : t => Js.t {..} = "" [@@bs.get]; 169 | 170 | external preventDefault : unit = "" [@@bs.send.pipe: t]; 171 | external isDefaultPrevented : bool = "" [@@bs.send.pipe: t]; 172 | external stopPropagation : unit = "" [@@bs.send.pipe: t]; 173 | external isPropagationStopped : bool = "" [@@bs.send.pipe: t]; 174 | external target : t => Dom.element = "" [@@bs.get]; 175 | 176 | external timeStamp : t => float = "" [@@bs.get]; 177 | external _type : t => string = "type" [@@bs.get]; 178 | 179 | external persist : unit = "" [@@bs.send.pipe: t]; 180 | 181 | external relatedTarget : t => Dom.element = "" [@@bs.get]; /* Should return Dom.eventTarget */ 182 | }; 183 | 184 | module Form: { 185 | type tag; 186 | type t = synthetic tag; 187 | 188 | external bubbles : t => bool = "" [@@bs.get]; 189 | external cancelable : t => bool = "" [@@bs.get]; 190 | external currentTarget : t => Dom.element = "" [@@bs.get]; 191 | external defaultPrevented : t => bool = "" [@@bs.get]; 192 | external eventPhase : t => int = "" [@@bs.get]; 193 | external isTrusted : t => bool = "" [@@bs.get]; 194 | external nativeEvent : t => Js.t {..} = "" [@@bs.get]; 195 | 196 | external preventDefault : unit = "" [@@bs.send.pipe: t]; 197 | external isDefaultPrevented : bool = "" [@@bs.send.pipe: t]; 198 | external stopPropagation : unit = "" [@@bs.send.pipe: t]; 199 | external isPropagationStopped : bool = "" [@@bs.send.pipe: t]; 200 | external target : t => Dom.element = "" [@@bs.get]; 201 | 202 | external timeStamp : t => float = "" [@@bs.get]; 203 | external _type : t => string = "type" [@@bs.get]; 204 | 205 | external persist : unit = "" [@@bs.send.pipe: t]; 206 | }; 207 | 208 | module Mouse: { 209 | type tag; 210 | type t = synthetic tag; 211 | 212 | external bubbles : t => bool = "" [@@bs.get]; 213 | external cancelable : t => bool = "" [@@bs.get]; 214 | external currentTarget : t => Dom.element = "" [@@bs.get]; 215 | external defaultPrevented : t => bool = "" [@@bs.get]; 216 | external eventPhase : t => int = "" [@@bs.get]; 217 | external isTrusted : t => bool = "" [@@bs.get]; 218 | external nativeEvent : t => Js.t {..} = "" [@@bs.get]; 219 | 220 | external preventDefault : unit = "" [@@bs.send.pipe: t]; 221 | external isDefaultPrevented : bool = "" [@@bs.send.pipe: t]; 222 | external stopPropagation : unit = "" [@@bs.send.pipe: t]; 223 | external isPropagationStopped : bool = "" [@@bs.send.pipe: t]; 224 | external target : t => Dom.element = "" [@@bs.get]; 225 | 226 | external timeStamp : t => float = "" [@@bs.get]; 227 | external _type : t => string = "type" [@@bs.get]; 228 | 229 | external persist : unit = "" [@@bs.send.pipe: t]; 230 | 231 | external altKey : t => bool = "" [@@bs.get]; 232 | external button : t => int = "" [@@bs.get]; 233 | external buttons : t => int = "" [@@bs.get]; 234 | external clientX : t => int = "" [@@bs.get]; 235 | external clientY : t => int = "" [@@bs.get]; 236 | external ctrlKey : t => bool = "" [@@bs.get]; 237 | external getModifierState : string => bool = "" [@@bs.send.pipe: t]; 238 | external metaKey : t => bool = "" [@@bs.get]; 239 | external pageX : t => int = "" [@@bs.get]; 240 | external pageY : t => int = "" [@@bs.get]; 241 | external relatedTarget : t => Dom.element = "" [@@bs.get]; /* Should return Dom.eventTarget */ 242 | external screenX : t => int = "" [@@bs.get]; 243 | external screenY : t => int = "" [@@bs.get]; 244 | external shiftKey : t => bool = "" [@@bs.get]; 245 | }; 246 | 247 | module Selection: { 248 | type tag; 249 | type t = synthetic tag; 250 | 251 | external bubbles : t => bool = "" [@@bs.get]; 252 | external cancelable : t => bool = "" [@@bs.get]; 253 | external currentTarget : t => Dom.element = "" [@@bs.get]; 254 | external defaultPrevented : t => bool = "" [@@bs.get]; 255 | external eventPhase : t => int = "" [@@bs.get]; 256 | external isTrusted : t => bool = "" [@@bs.get]; 257 | external nativeEvent : t => Js.t {..} = "" [@@bs.get]; 258 | 259 | external preventDefault : unit = "" [@@bs.send.pipe: t]; 260 | external isDefaultPrevented : bool = "" [@@bs.send.pipe: t]; 261 | external stopPropagation : unit = "" [@@bs.send.pipe: t]; 262 | external isPropagationStopped : bool = "" [@@bs.send.pipe: t]; 263 | external target : t => Dom.element = "" [@@bs.get]; 264 | 265 | external timeStamp : t => float = "" [@@bs.get]; 266 | external _type : t => string = "type" [@@bs.get]; 267 | 268 | external persist : unit = "" [@@bs.send.pipe: t]; 269 | }; 270 | 271 | module Touch: { 272 | type tag; 273 | type t = synthetic tag; 274 | 275 | external bubbles : t => bool = "" [@@bs.get]; 276 | external cancelable : t => bool = "" [@@bs.get]; 277 | external currentTarget : t => Dom.element = "" [@@bs.get]; 278 | external defaultPrevented : t => bool = "" [@@bs.get]; 279 | external eventPhase : t => int = "" [@@bs.get]; 280 | external isTrusted : t => bool = "" [@@bs.get]; 281 | external nativeEvent : t => Js.t {..} = "" [@@bs.get]; 282 | 283 | external preventDefault : unit = "" [@@bs.send.pipe: t]; 284 | external isDefaultPrevented : bool = "" [@@bs.send.pipe: t]; 285 | external stopPropagation : unit = "" [@@bs.send.pipe: t]; 286 | external isPropagationStopped : bool = "" [@@bs.send.pipe: t]; 287 | external target : t => Dom.element = "" [@@bs.get]; 288 | 289 | external timeStamp : t => float = "" [@@bs.get]; 290 | external _type : t => string = "type" [@@bs.get]; 291 | 292 | external persist : unit = "" [@@bs.send.pipe: t]; 293 | 294 | external altKey : t => bool = "" [@@bs.get]; 295 | external changedTouches : t => Js.t {..} = "" [@@bs.get]; /* Should return Dom.touchList */ 296 | external ctrlKey : t => bool = "" [@@bs.get]; 297 | external getModifierState : string => bool = "" [@@bs.send.pipe: t]; 298 | external metaKey : t => bool = "" [@@bs.get]; 299 | external shiftKey : t => bool = "" [@@bs.get]; 300 | external targetTouches : t => Js.t {..} = "" [@@bs.get]; /* Should return Dom.touchList */ 301 | external touches : t => Js.t {..} = "" [@@bs.get]; /* Should return Dom.touchList */ 302 | }; 303 | 304 | module UI: { 305 | type tag; 306 | type t = synthetic tag; 307 | 308 | external bubbles : t => bool = "" [@@bs.get]; 309 | external cancelable : t => bool = "" [@@bs.get]; 310 | external currentTarget : t => Dom.element = "" [@@bs.get]; 311 | external defaultPrevented : t => bool = "" [@@bs.get]; 312 | external eventPhase : t => int = "" [@@bs.get]; 313 | external isTrusted : t => bool = "" [@@bs.get]; 314 | external nativeEvent : t => Js.t {..} = "" [@@bs.get]; 315 | 316 | external preventDefault : unit = "" [@@bs.send.pipe: t]; 317 | external isDefaultPrevented : bool = "" [@@bs.send.pipe: t]; 318 | external stopPropagation : unit = "" [@@bs.send.pipe: t]; 319 | external isPropagationStopped : bool = "" [@@bs.send.pipe: t]; 320 | external target : t => Dom.element = "" [@@bs.get]; 321 | 322 | external timeStamp : t => float = "" [@@bs.get]; 323 | external _type : t => string = "type" [@@bs.get]; 324 | 325 | external persist : unit = "" [@@bs.send.pipe: t]; 326 | 327 | external detail : t => int = "" [@@bs.get]; 328 | external view : t => Dom.window = "" [@@bs.get]; /* Should return DOMAbstractView/WindowProxy */ 329 | }; 330 | 331 | module Wheel: { 332 | type tag; 333 | type t = synthetic tag; 334 | 335 | external bubbles : t => bool = "" [@@bs.get]; 336 | external cancelable : t => bool = "" [@@bs.get]; 337 | external currentTarget : t => Dom.element = "" [@@bs.get]; 338 | external defaultPrevented : t => bool = "" [@@bs.get]; 339 | external eventPhase : t => int = "" [@@bs.get]; 340 | external isTrusted : t => bool = "" [@@bs.get]; 341 | external nativeEvent : t => Js.t {..} = "" [@@bs.get]; 342 | 343 | external preventDefault : unit = "" [@@bs.send.pipe: t]; 344 | external isDefaultPrevented : bool = "" [@@bs.send.pipe: t]; 345 | external stopPropagation : unit = "" [@@bs.send.pipe: t]; 346 | external isPropagationStopped : bool = "" [@@bs.send.pipe: t]; 347 | external target : t => Dom.element = "" [@@bs.get]; 348 | 349 | external timeStamp : t => float = "" [@@bs.get]; 350 | external _type : t => string = "type" [@@bs.get]; 351 | 352 | external persist : unit = "" [@@bs.send.pipe: t]; 353 | 354 | external deltaMode : t => int = "" [@@bs.get]; 355 | external deltaX : t => float = "" [@@bs.get]; 356 | external deltaY : t => float = "" [@@bs.get]; 357 | external deltaZ : t => float = "" [@@bs.get]; 358 | }; 359 | 360 | module Media: { 361 | type tag; 362 | type t = synthetic tag; 363 | 364 | external bubbles : t => bool = "" [@@bs.get]; 365 | external cancelable : t => bool = "" [@@bs.get]; 366 | external currentTarget : t => Dom.element = "" [@@bs.get]; 367 | external defaultPrevented : t => bool = "" [@@bs.get]; 368 | external eventPhase : t => int = "" [@@bs.get]; 369 | external isTrusted : t => bool = "" [@@bs.get]; 370 | external nativeEvent : t => Js.t {..} = "" [@@bs.get]; 371 | 372 | external preventDefault : unit = "" [@@bs.send.pipe: t]; 373 | external isDefaultPrevented : bool = "" [@@bs.send.pipe: t]; 374 | external stopPropagation : unit = "" [@@bs.send.pipe: t]; 375 | external isPropagationStopped : bool = "" [@@bs.send.pipe: t]; 376 | external target : t => Dom.element = "" [@@bs.get]; 377 | 378 | external timeStamp : t => float = "" [@@bs.get]; 379 | external _type : t => string = "type" [@@bs.get]; 380 | 381 | external persist : unit = "" [@@bs.send.pipe: t]; 382 | }; 383 | 384 | module Image: { 385 | type tag; 386 | type t = synthetic tag; 387 | 388 | external bubbles : t => bool = "" [@@bs.get]; 389 | external cancelable : t => bool = "" [@@bs.get]; 390 | external currentTarget : t => Dom.element = "" [@@bs.get]; 391 | external defaultPrevented : t => bool = "" [@@bs.get]; 392 | external eventPhase : t => int = "" [@@bs.get]; 393 | external isTrusted : t => bool = "" [@@bs.get]; 394 | external nativeEvent : t => Js.t {..} = "" [@@bs.get]; 395 | 396 | external preventDefault : unit = "" [@@bs.send.pipe: t]; 397 | external isDefaultPrevented : bool = "" [@@bs.send.pipe: t]; 398 | external stopPropagation : unit = "" [@@bs.send.pipe: t]; 399 | external isPropagationStopped : bool = "" [@@bs.send.pipe: t]; 400 | external target : t => Dom.element = "" [@@bs.get]; 401 | 402 | external timeStamp : t => float = "" [@@bs.get]; 403 | external _type : t => string = "type" [@@bs.get]; 404 | 405 | external persist : unit = "" [@@bs.send.pipe: t]; 406 | }; 407 | 408 | module Animation: { 409 | type tag; 410 | type t = synthetic tag; 411 | 412 | external bubbles : t => bool = "" [@@bs.get]; 413 | external cancelable : t => bool = "" [@@bs.get]; 414 | external currentTarget : t => Dom.element = "" [@@bs.get]; 415 | external defaultPrevented : t => bool = "" [@@bs.get]; 416 | external eventPhase : t => int = "" [@@bs.get]; 417 | external isTrusted : t => bool = "" [@@bs.get]; 418 | external nativeEvent : t => Js.t {..} = "" [@@bs.get]; 419 | 420 | external preventDefault : unit = "" [@@bs.send.pipe: t]; 421 | external isDefaultPrevented : bool = "" [@@bs.send.pipe: t]; 422 | external stopPropagation : unit = "" [@@bs.send.pipe: t]; 423 | external isPropagationStopped : bool = "" [@@bs.send.pipe: t]; 424 | external target : t => Dom.element = "" [@@bs.get]; 425 | 426 | external timeStamp : t => float = "" [@@bs.get]; 427 | external _type : t => string = "type" [@@bs.get]; 428 | 429 | external persist : unit = "" [@@bs.send.pipe: t]; 430 | 431 | external animationName : t => string = "" [@@bs.get]; 432 | external pseudoElement : t => string = "" [@@bs.get]; 433 | external elapsedTime : t => float = "" [@@bs.get]; 434 | }; 435 | 436 | module Transition: { 437 | type tag; 438 | type t = synthetic tag; 439 | 440 | external bubbles : t => bool = "" [@@bs.get]; 441 | external cancelable : t => bool = "" [@@bs.get]; 442 | external currentTarget : t => Dom.element = "" [@@bs.get]; 443 | external defaultPrevented : t => bool = "" [@@bs.get]; 444 | external eventPhase : t => int = "" [@@bs.get]; 445 | external isTrusted : t => bool = "" [@@bs.get]; 446 | external nativeEvent : t => Js.t {..} = "" [@@bs.get]; 447 | 448 | external preventDefault : unit = "" [@@bs.send.pipe: t]; 449 | external isDefaultPrevented : bool = "" [@@bs.send.pipe: t]; 450 | external stopPropagation : unit = "" [@@bs.send.pipe: t]; 451 | external isPropagationStopped : bool = "" [@@bs.send.pipe: t]; 452 | external target : t => Dom.element = "" [@@bs.get]; 453 | 454 | external timeStamp : t => float = "" [@@bs.get]; 455 | external _type : t => string = "type" [@@bs.get]; 456 | 457 | external persist : unit = "" [@@bs.send.pipe: t]; 458 | 459 | external propertyName : t => string = "" [@@bs.get]; 460 | external pseudoElement : t => string = "" [@@bs.get]; 461 | external elapsedTime : t => float = "" [@@bs.get]; 462 | }; 463 | -------------------------------------------------------------------------------- /src/reactRe.re: -------------------------------------------------------------------------------- 1 | /* ============================================ some types */ 2 | type reactClass; 3 | 4 | type reactElement; 5 | 6 | type reactRef; 7 | 8 | type reactJsChildren; 9 | 10 | external createElement : reactClass => props::Js.t {..}? => array reactElement => reactElement = 11 | "createElement" [@@bs.splice] [@@bs.val] [@@bs.module "react"]; 12 | 13 | external nullElement : reactElement = "null" [@@bs.val]; 14 | 15 | external stringToElement : string => reactElement = "%identity"; 16 | 17 | external arrayToElement : array reactElement => reactElement = "%identity"; 18 | 19 | let listToElement list => arrayToElement (Array.of_list list); 20 | 21 | external refToJsObj : reactRef => Js.t {..} = "%identity"; 22 | 23 | /* We wrap the props for reason->reason components, as a marker that "these props were passed from another 24 | reason component" */ 25 | external createCompositeElementInternalHack : 26 | reactClass => Js.t {.. reasonProps : 'props} => array reactElement => reactElement = 27 | "createElement" [@@bs.val] [@@bs.module "react"] [@@bs.splice]; 28 | 29 | let wrapPropsInternal 30 | comp 31 | props 32 | wrapPropsHow 33 | ::children 34 | ref::(ref: option (reactRef => unit))=? 35 | key::(key: option string)=? 36 | () => { 37 | let ref = 38 | switch ref { 39 | | None => Js.Undefined.empty 40 | | Some ref => Js.Undefined.return ref 41 | }; 42 | let key = 43 | switch key { 44 | | None => Js.Undefined.empty 45 | | Some key => Js.Undefined.return key 46 | }; 47 | let props = wrapPropsHow ::props ::ref ::key; 48 | /* "ok chenglou explain this crap, what are you doing?" 49 | 50 | React's runtime key warning (https://facebook.github.io/react/docs/lists-and-keys.html#keys) is 51 | important. We'll convert them to static warnings one day, but right now we want to piggyback on it. 52 | Unfortunately, the way React detects missing keys and warn, is by detecting that children's an array, and 53 | that the array items are missing `key`. It rightfully skips the warning when people do `
{foo} 54 | {bar}
` because that compiles to `React.createElement('div', null, foo, bar)`. See how children are 55 | not an array, but passed to `createElement` variadic-ally. 56 | 57 | From our side, if our bindings always pass an array as children, we'd always get the children key warning 58 | for something like `
foo bar baz
` (Reason JSX syntax) that hypothetically would naively desugar 59 | to `ReactRe.createElement "div" [|foo, bar, baz|]`. So, to skip the key warning from our side, we need to 60 | compile to variadic children too. BS supports variadic js call if we use the `bs.splice` external and if 61 | we pass the last argument as an array literal. Literal! Aka we can't just pass an array reference. So we 62 | pattern match on all the possibilities of an array up til a dozen (this compiles to efficient code too). 63 | 64 | */ 65 | switch children { 66 | | [] => createCompositeElementInternalHack comp props [||] 67 | | [a] => createCompositeElementInternalHack comp props [|a|] 68 | | [a, b] => createCompositeElementInternalHack comp props [|a, b|] 69 | | [a, b, c] => createCompositeElementInternalHack comp props [|a, b, c|] 70 | | [a, b, c, d] => createCompositeElementInternalHack comp props [|a, b, c, d|] 71 | | [a, b, c, d, e] => createCompositeElementInternalHack comp props [|a, b, c, d, e|] 72 | | [a, b, c, d, e, f] => createCompositeElementInternalHack comp props [|a, b, c, d, e, f|] 73 | | [a, b, c, d, e, f, g] => createCompositeElementInternalHack comp props [|a, b, c, d, e, f, g|] 74 | | [a, b, c, d, e, f, g, h] => 75 | createCompositeElementInternalHack comp props [|a, b, c, d, e, f, g, h|] 76 | | [a, b, c, d, e, f, g, h, i] => 77 | createCompositeElementInternalHack comp props [|a, b, c, d, e, f, g, h, i|] 78 | | [a, b, c, d, e, f, g, h, i, j] => 79 | createCompositeElementInternalHack comp props [|a, b, c, d, e, f, g, h, i, j|] 80 | | [a, b, c, d, e, f, g, h, i, j, k] => 81 | createCompositeElementInternalHack comp props [|a, b, c, d, e, f, g, h, i, j, k|] 82 | | [a, b, c, d, e, f, g, h, i, j, k, l] => 83 | createCompositeElementInternalHack comp props [|a, b, c, d, e, f, g, h, i, j, k, l|] 84 | | [a, b, c, d, e, f, g, h, i, j, k, l, m] => 85 | createCompositeElementInternalHack comp props [|a, b, c, d, e, f, g, h, i, j, k, l, m|] 86 | | [a, b, c, d, e, f, g, h, i, j, k, l, m, n] => 87 | createCompositeElementInternalHack comp props [|a, b, c, d, e, f, g, h, i, j, k, l, m, n|] 88 | | [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o] => 89 | createCompositeElementInternalHack comp props [|a, b, c, d, e, f, g, h, i, j, k, l, m, n, o|] 90 | | [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] => 91 | createCompositeElementInternalHack 92 | comp props [|a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p|] 93 | | [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q] => 94 | createCompositeElementInternalHack 95 | comp props [|a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q|] 96 | | _ => 97 | /* 16 is a good number because it fills the cache line. Just kidding, submit an issue if you want more */ 98 | let msg = 99 | "Reason allows up to 17 static children (but dynamic children in an array can be unlimited in size); You have " ^ 100 | string_of_int (List.length children) ^ ", please put them in an array and assign key to the elements. Sorry for the inconvenience!"; 101 | raise (Invalid_argument msg) 102 | } 103 | }; 104 | 105 | let jsChildrenToReason (children: Js.null_undefined reactJsChildren) :list reactElement => 106 | switch (Js.Null_undefined.to_opt children) { 107 | | None => [] 108 | | Some children => 109 | if (Js.Array.isArray children) { 110 | Array.to_list (Obj.magic children) 111 | } else { 112 | [Obj.magic children] 113 | } 114 | }; 115 | 116 | type jsState 'state = Js.t {. mlState : 'state}; 117 | 118 | module CommonLifecycle = { 119 | let componentDidMount _ => None; 120 | let componentWillUpdate _ nextProps::_ nextState::_ => None; 121 | let componentDidUpdate prevProps::_ prevState::_ _ => None; 122 | let componentWillReceiveProps _ nextProps::_ => None; 123 | let componentWillUnmount _ => (); 124 | }; 125 | 126 | module ComponentBase = { 127 | type componentBag 'state 'props 'instanceVars = { 128 | state: 'state, 129 | props: 'props, 130 | updater: 131 | 'dataPassedToHandler . 132 | (componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => option 'state) => 133 | 'dataPassedToHandler => 134 | unit, 135 | 136 | handler: 'dataPassedToHandler . 137 | (componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => unit) => 138 | 'dataPassedToHandler => 139 | unit, 140 | instanceVars: 'instanceVars, 141 | /** 142 | * Work in progress / prototype API for setState. This isn't sufficient for 143 | * every use case and there is some overlap with updater. In a world 144 | * without `this`, it is even more important that `setState` accept a 145 | * callback. With `ReactJS`, absence of the callback form often relies on 146 | * trapping `this` in scope, and relies on the fact that 147 | * `this.state`/`this.props` will be mutated to have the new 148 | * `state`/`props` on `this`. Still, this `setState` API doesn't do 149 | * everything we wish so it will likely change. 150 | */ 151 | setState: (componentBag 'state 'props 'instanceVars => 'state) => unit 152 | }; 153 | type jsComponentThis 'state 'props 'instanceVars = 154 | Js.t { 155 | . 156 | state : jsState 'state, 157 | props : Obj.t, 158 | setState : ((jsState 'state => 'props => jsState 'state) => unit) [@bs.meth] 159 | }; 160 | include CommonLifecycle; 161 | }; 162 | 163 | module type CompleteComponentSpec = { 164 | let name: string; 165 | type props; 166 | type state; 167 | type instanceVars; 168 | type jsProps; 169 | let getInstanceVars: unit => instanceVars; 170 | let getInitialState: props => state; 171 | 172 | /** 173 | * TODO: Preallocate a "returnNone", and then at runtime check for reference 174 | * equality to this function and avoid even invoking it. 175 | */ 176 | let componentDidMount: ComponentBase.componentBag state props instanceVars => option state; 177 | let componentWillReceiveProps: 178 | ComponentBase.componentBag state props instanceVars => nextProps::props => option state; 179 | let componentWillUpdate: 180 | ComponentBase.componentBag state props instanceVars => 181 | nextProps::props => 182 | nextState::state => 183 | option state; 184 | let componentDidUpdate: 185 | prevProps::props => 186 | prevState::state => 187 | ComponentBase.componentBag state props instanceVars => 188 | option state; 189 | let componentWillUnmount: ComponentBase.componentBag state props instanceVars => unit; 190 | let jsPropsToReasonProps: option (jsProps => props); 191 | let render: ComponentBase.componentBag state props instanceVars => reactElement; 192 | }; 193 | 194 | module type ReactComponent = { 195 | type props_; 196 | let comp: reactClass; 197 | let wrapProps: 198 | props_ => 199 | children::list reactElement => 200 | ref::(reactRef => unit)? => 201 | key::string? => 202 | unit => 203 | reactElement; 204 | }; 205 | 206 | /* writing this in full-stack bare-metal JS. OCaml for-loop doesn't have early return, and throwing exceptions & 207 | catching isn't great perf */ 208 | let findFirstCallback 209 | (type a) 210 | (type b) 211 | (callbacks: array (Js.null (a, b))) 212 | (callback: a) 213 | :Js.null b => 214 | [%bs.raw 215 | {| 216 | function(callbacks, callback) { 217 | for (var i = 0; i < callbacks.length; i++) { 218 | // we fill the slots from left to right. If there's a null then we can early stop. 219 | if (callbacks[i] == null) { 220 | return null; 221 | }; 222 | if (callbacks[i][0] === callback) { 223 | return callbacks[i][1]; 224 | } 225 | } 226 | return null; 227 | } 228 | |} 229 | ] 230 | callbacks 231 | callback 232 | [@bs]; 233 | 234 | module CreateComponent 235 | (CompleteComponentSpec: CompleteComponentSpec) 236 | :(ReactComponent with type props_ = CompleteComponentSpec.props) => { 237 | type props_ = CompleteComponentSpec.props; 238 | /* This part is the secret sauce that bridges to Reactjs. It's a bit verbose (but consistent) right now; We'll 239 | find a way to make it shorter in the future. */ 240 | let convertPropsIfTheyreFromJs props => { 241 | let props = Obj.magic props; 242 | switch (Js.Undefined.to_opt props##reasonProps, CompleteComponentSpec.jsPropsToReasonProps) { 243 | | (Some props, _) => props 244 | /* TODO: annotate with BS to avoid curry overhead */ 245 | | (None, Some toReasonProps) => toReasonProps props 246 | | (None, None) => 247 | raise ( 248 | Invalid_argument ( 249 | "A JS component called the Reason component " ^ 250 | CompleteComponentSpec.name ^ 251 | " which didn't implement the JS->Reason React props conversion. Did you forget to add `JsProps` to " ^ 252 | CompleteComponentSpec.name ^ "'s `include ReactRe.Component.*`?" 253 | ) 254 | ) 255 | } 256 | }; 257 | type jsComponentThis_ = 258 | ComponentBase.jsComponentThis 259 | CompleteComponentSpec.state CompleteComponentSpec.props CompleteComponentSpec.instanceVars; 260 | external createClassInternalHack : Js.t 'classSpec => reactClass = 261 | "createClass" [@@bs.val] [@@bs.module "react"]; 262 | let maxMemoizedCount = 30; 263 | let comp = 264 | createClassInternalHack ( 265 | { 266 | val displayName = CompleteComponentSpec.name; 267 | /* we dangerously initialize these as nulls but don't tell the system so; They're guaranteed not to be 268 | null when being passed around, as long as we initialize them in getInitialState. We also can't 269 | initialize them here because the values are only shallowly copied, so they'd be shared across 270 | instances. */ 271 | val mutable instanceVars = [%bs.raw "null"]; 272 | val mutable memoizedUpdaterCallbacks = [%bs.raw "null"]; 273 | val mutable memoizedUpdaterCount = 0; 274 | val mutable memoizedRefCallbacks = [%bs.raw "null"]; 275 | val mutable memoizedRefCount = 0; 276 | pub getInitialState () :jsState CompleteComponentSpec.state => { 277 | this##instanceVars#=(CompleteComponentSpec.getInstanceVars ()); 278 | this##memoizedUpdaterCallbacks#=(Array.make maxMemoizedCount Js.null); 279 | this##memoizedRefCallbacks#=(Array.make maxMemoizedCount Js.null); 280 | let that: jsComponentThis_ = [%bs.raw "this"]; 281 | let props = convertPropsIfTheyreFromJs that##props; 282 | let state = CompleteComponentSpec.getInitialState props; 283 | {"mlState": state} 284 | }; 285 | pub componentDidMount () => { 286 | let that: jsComponentThis_ = [%bs.raw "this"]; 287 | let currState = that##state##mlState; 288 | let newState = 289 | CompleteComponentSpec.componentDidMount { 290 | props: convertPropsIfTheyreFromJs that##props, 291 | state: currState, 292 | instanceVars: this##instanceVars, 293 | updater: Obj.magic this##updaterMethod, 294 | handler: Obj.magic this##handlerMethod, 295 | setState: Obj.magic this##setStateMethod 296 | }; 297 | switch newState { 298 | | None => () 299 | | Some state => that##setState (fun _ _ => {"mlState": state}) 300 | } 301 | }; 302 | pub componentWillUpdate nextProps nextState => { 303 | let that: jsComponentThis_ = [%bs.raw "this"]; 304 | let currState = that##state##mlState; 305 | let newState = 306 | CompleteComponentSpec.componentWillUpdate 307 | { 308 | props: convertPropsIfTheyreFromJs that##props, 309 | state: currState, 310 | instanceVars: this##instanceVars, 311 | updater: Obj.magic this##updaterMethod, 312 | handler: Obj.magic this##handlerMethod, 313 | setState: Obj.magic this##setStateMethod 314 | } 315 | nextProps::(convertPropsIfTheyreFromJs nextProps) 316 | nextState::nextState##mlState; 317 | switch newState { 318 | | None => () 319 | | Some state => that##setState (fun _ _ => {"mlState": state}) 320 | } 321 | }; 322 | pub componentDidUpdate prevProps prevState => { 323 | let that: jsComponentThis_ = [%bs.raw "this"]; 324 | let currState = that##state##mlState; 325 | let newState = 326 | CompleteComponentSpec.componentDidUpdate 327 | prevProps::(convertPropsIfTheyreFromJs prevProps) 328 | prevState::prevState##mlState 329 | { 330 | props: convertPropsIfTheyreFromJs that##props, 331 | state: currState, 332 | instanceVars: this##instanceVars, 333 | updater: Obj.magic this##updaterMethod, 334 | handler: Obj.magic this##handlerMethod, 335 | setState: Obj.magic this##setStateMethod 336 | }; 337 | switch newState { 338 | | None => () 339 | | Some state => that##setState (fun _ _ => {"mlState": state}) 340 | } 341 | }; 342 | pub componentWillReceiveProps nextProps => { 343 | let that: jsComponentThis_ = [%bs.raw "this"]; 344 | let currState = that##state##mlState; 345 | let newState = 346 | CompleteComponentSpec.componentWillReceiveProps 347 | { 348 | props: convertPropsIfTheyreFromJs that##props, 349 | state: currState, 350 | instanceVars: this##instanceVars, 351 | updater: Obj.magic this##updaterMethod, 352 | handler: Obj.magic this##handlerMethod, 353 | setState: Obj.magic this##setStateMethod 354 | } 355 | nextProps::(convertPropsIfTheyreFromJs nextProps); 356 | switch newState { 357 | | None => () 358 | | Some state => that##setState (fun _ _ => {"mlState": state}) 359 | } 360 | }; 361 | pub componentWillUnmount () => { 362 | let that: jsComponentThis_ = [%bs.raw "this"]; 363 | let currState = that##state##mlState; 364 | CompleteComponentSpec.componentWillUnmount { 365 | props: convertPropsIfTheyreFromJs that##props, 366 | state: currState, 367 | instanceVars: this##instanceVars, 368 | updater: Obj.magic this##updaterMethod, 369 | handler: Obj.magic this##handlerMethod, 370 | setState: Obj.magic this##setStateMethod 371 | } 372 | }; 373 | pub handlerMethod callback => 374 | switch ( 375 | this##memoizedRefCount, 376 | Js.Null.to_opt (findFirstCallback this##memoizedRefCallbacks callback) 377 | ) { 378 | | (_, Some memoized) => memoized 379 | | (count, None) => 380 | let that: jsComponentThis_ = [%bs.raw "this"]; 381 | let maybeMemoizedCallback callbackPayload => { 382 | let currState = that##state##mlState; 383 | callback 384 | { 385 | ComponentBase.props: convertPropsIfTheyreFromJs that##props, 386 | state: currState, 387 | instanceVars: this##instanceVars, 388 | updater: Obj.magic this##updaterMethod, 389 | handler: Obj.magic this##handlerMethod, 390 | setState: Obj.magic this##setStateMethod 391 | } 392 | callbackPayload 393 | }; 394 | if (count < maxMemoizedCount) { 395 | let memoizedRefCallbacks = this##memoizedRefCallbacks; 396 | memoizedRefCallbacks.(count) = Js.Null.return (callback, maybeMemoizedCallback); 397 | this##memoizedRefCount#=(this##memoizedRefCount + 1) 398 | }; 399 | maybeMemoizedCallback 400 | }; 401 | pub setStateMethod cb => { 402 | let that: jsComponentThis_ = [%bs.raw "this"]; 403 | 404 | /** 405 | * Makes sense to adapt the API to the Reason API where you are often 406 | * passed the entire bag for every lifecycle/callback. 407 | */ 408 | that##setState ( 409 | fun prevState props => { 410 | let bag = { 411 | ComponentBase.props: convertPropsIfTheyreFromJs props, 412 | state: prevState##mlState, 413 | instanceVars: this##instanceVars, 414 | updater: Obj.magic this##updaterMethod, 415 | handler: Obj.magic this##handlerMethod, 416 | setState: Obj.magic this##setStateMethod 417 | }; 418 | {"mlState": cb bag} 419 | } 420 | ) 421 | }; 422 | pub updaterMethod callback => 423 | switch ( 424 | this##memoizedUpdaterCount, 425 | Js.Null.to_opt (findFirstCallback this##memoizedUpdaterCallbacks callback) 426 | ) { 427 | | (_, Some memoized) => memoized 428 | | (count, None) => 429 | let that: jsComponentThis_ = [%bs.raw "this"]; 430 | let maybeMemoizedCallback callbackPayload => { 431 | let currState = that##state##mlState; 432 | let newState = 433 | callback 434 | { 435 | ComponentBase.props: convertPropsIfTheyreFromJs that##props, 436 | state: currState, 437 | instanceVars: this##instanceVars, 438 | updater: Obj.magic this##updaterMethod, 439 | handler: Obj.magic this##handlerMethod, 440 | setState: Obj.magic this##setStateMethod 441 | } 442 | callbackPayload; 443 | switch newState { 444 | | None => () 445 | | Some state => that##setState (fun _ _ => {"mlState": state}) 446 | } 447 | }; 448 | if (count < maxMemoizedCount) { 449 | let memoizedUpdaterCallbacks = this##memoizedUpdaterCallbacks; 450 | memoizedUpdaterCallbacks.(count) = Js.Null.return (callback, maybeMemoizedCallback); 451 | this##memoizedUpdaterCount#=(this##memoizedUpdaterCount + 1) 452 | }; 453 | maybeMemoizedCallback 454 | }; 455 | pub render () => { 456 | let that: jsComponentThis_ = [%bs.raw "this"]; 457 | CompleteComponentSpec.render { 458 | props: convertPropsIfTheyreFromJs that##props, 459 | state: that##state##mlState, 460 | instanceVars: this##instanceVars, 461 | updater: Obj.magic this##updaterMethod, 462 | handler: Obj.magic this##handlerMethod, 463 | setState: Obj.magic this##setStateMethod 464 | } 465 | } 466 | } 467 | [@bs] 468 | ); 469 | let wrapPropsAndPutIndicatorThatItComesFromReason ::props ::ref ::key => { 470 | "reasonProps": props, 471 | "ref": ref, 472 | "key": key 473 | }; 474 | /* fully apply this to avoid currying overhead */ 475 | let wrapProps (props: props_) ::children ::ref=? ::key=? () => 476 | wrapPropsInternal 477 | comp props wrapPropsAndPutIndicatorThatItComesFromReason ::children ::?key ::?ref (); 478 | }; 479 | 480 | let wrapPropsAndPutRefAndKey ::props ::ref ::key => 481 | Js.Obj.assign (Js.Obj.assign (Js.Obj.empty ()) props) {"ref": ref, "key": key}; 482 | 483 | /* fully apply this to avoid currying overhead */ 484 | let wrapPropsShamelessly comp props ::children ::ref=? ::key=? () => 485 | wrapPropsInternal comp props wrapPropsAndPutRefAndKey ::children ::?key ::?ref (); 486 | 487 | module Component = { 488 | include ComponentBase; 489 | type jsProps = unit; 490 | type instanceVars = unit; 491 | type state = unit; 492 | let getInstanceVars () => (); 493 | let jsPropsToReasonProps = None; 494 | let getInitialState _ => (); 495 | module Stateful = { 496 | include ComponentBase; 497 | type jsProps = unit; 498 | type instanceVars = unit; 499 | let getInstanceVars () => (); 500 | let jsPropsToReasonProps = None; 501 | module JsProps = { 502 | include ComponentBase; 503 | type instanceVars = unit; 504 | let getInstanceVars () => (); 505 | let jsPropsToReasonProps = None; 506 | }; 507 | module InstanceVars = { 508 | include ComponentBase; 509 | type jsProps = unit; 510 | let jsPropsToReasonProps = None; 511 | module JsProps = { 512 | include ComponentBase; 513 | }; 514 | }; 515 | }; 516 | module JsProps = { 517 | include ComponentBase; 518 | type instanceVars = unit; 519 | type state = unit; 520 | let getInstanceVars () => (); 521 | let getInitialState _ => (); 522 | }; 523 | module InstanceVars = { 524 | include ComponentBase; 525 | type jsProps = unit; 526 | type state = unit; 527 | let getInitialState _ => (); 528 | let jsPropsToReasonProps = None; 529 | module JsProps = { 530 | include ComponentBase; 531 | type state = unit; 532 | let getInitialState _ => (); 533 | }; 534 | }; 535 | }; 536 | -------------------------------------------------------------------------------- /src/reactRe.rei: -------------------------------------------------------------------------------- 1 | type reactClass; 2 | 3 | type reactElement; 4 | 5 | type reactRef; 6 | 7 | type reactJsChildren; 8 | 9 | external createElement : reactClass => props::Js.t {..}? => array reactElement => reactElement = 10 | "createElement" [@@bs.splice] [@@bs.val] [@@bs.module "react"]; 11 | 12 | external nullElement : reactElement = "null" [@@bs.val]; 13 | 14 | external stringToElement : string => reactElement = "%identity"; 15 | 16 | external arrayToElement : array reactElement => reactElement = "%identity"; 17 | 18 | let listToElement : list reactElement => reactElement; 19 | 20 | external refToJsObj : reactRef => Js.t {..} = "%identity"; 21 | 22 | let jsChildrenToReason: Js.null_undefined reactJsChildren => list reactElement; 23 | 24 | module ComponentBase: { 25 | type componentBag 'state 'props 'instanceVars = { 26 | state: 'state, 27 | props: 'props, 28 | updater: 29 | 'dataPassedToHandler . 30 | (componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => option 'state) => 31 | 'dataPassedToHandler => 32 | unit, 33 | 34 | handler: 'dataPassedToHandler . (componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => unit) => 'dataPassedToHandler => unit, 35 | instanceVars: 'instanceVars, 36 | setState: (componentBag 'state 'props 'instanceVars => 'state) => unit 37 | }; 38 | }; 39 | 40 | module type CompleteComponentSpec = { 41 | let name: string; 42 | type props; 43 | type state; 44 | type instanceVars; 45 | type jsProps; 46 | let getInstanceVars: unit => instanceVars; 47 | let getInitialState: props => state; 48 | let componentDidMount: ComponentBase.componentBag state props instanceVars => option state; 49 | let componentWillReceiveProps: 50 | ComponentBase.componentBag state props instanceVars => nextProps::props => option state; 51 | let componentWillUpdate: 52 | ComponentBase.componentBag state props instanceVars => 53 | nextProps::props => 54 | nextState::state => 55 | option state; 56 | let componentDidUpdate: 57 | prevProps::props => 58 | prevState::state => 59 | ComponentBase.componentBag state props instanceVars => 60 | option state; 61 | let componentWillUnmount: ComponentBase.componentBag state props instanceVars => unit; 62 | let jsPropsToReasonProps: option (jsProps => props); 63 | let render: ComponentBase.componentBag state props instanceVars => reactElement; 64 | }; 65 | 66 | module CreateComponent: 67 | (CompleteComponentSpec: CompleteComponentSpec) => 68 | { 69 | let comp: reactClass; 70 | let wrapProps: 71 | CompleteComponentSpec.props => 72 | children::list reactElement => 73 | ref::(reactRef => unit)? => 74 | key::string? => 75 | unit => 76 | reactElement; 77 | }; 78 | 79 | let wrapPropsShamelessly: 80 | reactClass => 81 | Js.t {..} => 82 | children::list reactElement => 83 | ref::(reactRef => unit)? => 84 | key::string? => 85 | unit => 86 | reactElement; 87 | 88 | /* Don't be scared off by this signature's size! Notice that you will only ever use one of the modules or 89 | submodule in a component. */ 90 | module Component: { 91 | type componentBag 'state 'props 'instanceVars = 92 | ComponentBase.componentBag 'state 'props 'instanceVars = 93 | { 94 | state: 'state, 95 | props: 'props, 96 | updater: 97 | 'dataPassedToHandler . 98 | (componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => option 'state) => 99 | 'dataPassedToHandler => 100 | unit, 101 | 102 | handler: 103 | 'dataPassedToHandler . 104 | (componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => unit) => 'dataPassedToHandler => unit, 105 | instanceVars: 'instanceVars, 106 | setState: (componentBag 'state 'props 'instanceVars => 'state) => unit 107 | }; 108 | let componentDidMount: 'a => option 'b; 109 | let componentWillUpdate: 'a => nextProps::'b => nextState::'c => option 'd; 110 | let componentDidUpdate: prevProps::'a => prevState::'b => 'c => option 'd; 111 | let componentWillReceiveProps: 'a => nextProps::'b => option 'c; 112 | let componentWillUnmount: 'a => unit; 113 | type jsProps = unit; 114 | type instanceVars = unit; 115 | type state = unit; 116 | let getInstanceVars: unit => unit; 117 | let jsPropsToReasonProps: option 'a; 118 | let getInitialState: 'a => unit; 119 | module Stateful: { 120 | type componentBag 'state 'props 'instanceVars = 121 | ComponentBase.componentBag 'state 'props 'instanceVars = 122 | { 123 | state: 'state, 124 | props: 'props, 125 | updater: 126 | 'dataPassedToHandler . 127 | (componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => option 'state) => 128 | 'dataPassedToHandler => 129 | unit, 130 | 131 | handler: 132 | 'dataPassedToHandler . 133 | (componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => unit) => 'dataPassedToHandler => unit, 134 | instanceVars: 'instanceVars, 135 | setState: (componentBag 'state 'props 'instanceVars => 'state) => unit 136 | }; 137 | let componentDidMount: 'a => option 'b; 138 | let componentWillUpdate: 'a => nextProps::'b => nextState::'c => option 'd; 139 | let componentDidUpdate: prevProps::'a => prevState::'b => 'c => option 'd; 140 | let componentWillReceiveProps: 'a => nextProps::'b => option 'c; 141 | let componentWillUnmount: 'a => unit; 142 | type jsProps = unit; 143 | type instanceVars = unit; 144 | let getInstanceVars: unit => unit; 145 | let jsPropsToReasonProps: option 'a; 146 | module JsProps: { 147 | type componentBag 'state 'props 'instanceVars = 148 | ComponentBase.componentBag 'state 'props 'instanceVars = 149 | { 150 | state: 'state, 151 | props: 'props, 152 | updater: 153 | 'dataPassedToHandler . 154 | ( 155 | componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => option 'state 156 | ) => 157 | 'dataPassedToHandler => 158 | unit, 159 | 160 | handler: 161 | 'dataPassedToHandler . 162 | (componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => unit) => 'dataPassedToHandler => unit, 163 | instanceVars: 'instanceVars, 164 | setState: (componentBag 'state 'props 'instanceVars => 'state) => unit 165 | }; 166 | let componentDidMount: 'a => option 'b; 167 | let componentWillUpdate: 'a => nextProps::'b => nextState::'c => option 'd; 168 | let componentDidUpdate: prevProps::'a => prevState::'b => 'c => option 'd; 169 | let componentWillReceiveProps: 'a => nextProps::'b => option 'c; 170 | let componentWillUnmount: 'a => unit; 171 | type instanceVars = unit; 172 | let getInstanceVars: unit => unit; 173 | let jsPropsToReasonProps: option 'a; 174 | }; 175 | module InstanceVars: { 176 | type componentBag 'state 'props 'instanceVars = 177 | ComponentBase.componentBag 'state 'props 'instanceVars = 178 | { 179 | state: 'state, 180 | props: 'props, 181 | updater: 182 | 'dataPassedToHandler . 183 | ( 184 | componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => option 'state 185 | ) => 186 | 'dataPassedToHandler => 187 | unit, 188 | 189 | handler: 190 | 'dataPassedToHandler . 191 | (componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => unit) => 'dataPassedToHandler => unit, 192 | instanceVars: 'instanceVars, 193 | setState: (componentBag 'state 'props 'instanceVars => 'state) => unit 194 | }; 195 | let componentDidMount: 'a => option 'b; 196 | let componentWillUpdate: 'a => nextProps::'b => nextState::'c => option 'd; 197 | let componentDidUpdate: prevProps::'a => prevState::'b => 'c => option 'd; 198 | let componentWillReceiveProps: 'a => nextProps::'b => option 'c; 199 | let componentWillUnmount: 'a => unit; 200 | type jsProps = unit; 201 | let jsPropsToReasonProps: option 'a; 202 | module JsProps: { 203 | type componentBag 'state 'props 'instanceVars = 204 | ComponentBase.componentBag 'state 'props 'instanceVars = 205 | { 206 | state: 'state, 207 | props: 'props, 208 | updater: 209 | 'dataPassedToHandler . 210 | ( 211 | componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => option 'state 212 | ) => 213 | 'dataPassedToHandler => 214 | unit, 215 | 216 | handler: 217 | 'dataPassedToHandler . 218 | (componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => unit) => 'dataPassedToHandler => unit, 219 | instanceVars: 'instanceVars, 220 | setState: (componentBag 'state 'props 'instanceVars => 'state) => unit 221 | }; 222 | let componentDidMount: 'a => option 'b; 223 | let componentWillUpdate: 'a => nextProps::'b => nextState::'c => option 'd; 224 | let componentDidUpdate: prevProps::'a => prevState::'b => 'c => option 'd; 225 | let componentWillReceiveProps: 'a => nextProps::'b => option 'c; 226 | let componentWillUnmount: 'a => unit; 227 | }; 228 | }; 229 | }; 230 | module JsProps: { 231 | type componentBag 'state 'props 'instanceVars = 232 | ComponentBase.componentBag 'state 'props 'instanceVars = 233 | { 234 | state: 'state, 235 | props: 'props, 236 | updater: 237 | 'dataPassedToHandler . 238 | (componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => option 'state) => 239 | 'dataPassedToHandler => 240 | unit, 241 | 242 | handler: 243 | 'dataPassedToHandler . 244 | (componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => unit) => 'dataPassedToHandler => unit, 245 | instanceVars: 'instanceVars, 246 | setState: (componentBag 'state 'props 'instanceVars => 'state) => unit 247 | }; 248 | let componentDidMount: 'a => option 'b; 249 | let componentWillUpdate: 'a => nextProps::'b => nextState::'c => option 'd; 250 | let componentDidUpdate: prevProps::'a => prevState::'b => 'c => option 'd; 251 | let componentWillReceiveProps: 'a => nextProps::'b => option 'c; 252 | let componentWillUnmount: 'a => unit; 253 | type instanceVars = unit; 254 | type state = unit; 255 | let getInstanceVars: unit => unit; 256 | let getInitialState: 'a => unit; 257 | }; 258 | module InstanceVars: { 259 | type componentBag 'state 'props 'instanceVars = 260 | ComponentBase.componentBag 'state 'props 'instanceVars = 261 | { 262 | state: 'state, 263 | props: 'props, 264 | updater: 265 | 'dataPassedToHandler . 266 | (componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => option 'state) => 267 | 'dataPassedToHandler => 268 | unit, 269 | 270 | handler: 271 | 'dataPassedToHandler . 272 | (componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => unit) => 'dataPassedToHandler => unit, 273 | instanceVars: 'instanceVars, 274 | setState: (componentBag 'state 'props 'instanceVars => 'state) => unit 275 | }; 276 | let componentDidMount: 'a => option 'b; 277 | let componentWillUpdate: 'a => nextProps::'b => nextState::'c => option 'd; 278 | let componentDidUpdate: prevProps::'a => prevState::'b => 'c => option 'd; 279 | let componentWillReceiveProps: 'a => nextProps::'b => option 'c; 280 | let componentWillUnmount: 'a => unit; 281 | type jsProps = unit; 282 | type state = unit; 283 | let getInitialState: 'a => unit; 284 | let jsPropsToReasonProps: option 'a; 285 | module JsProps: { 286 | type componentBag 'state 'props 'instanceVars = 287 | ComponentBase.componentBag 'state 'props 'instanceVars = 288 | { 289 | state: 'state, 290 | props: 'props, 291 | updater: 292 | 'dataPassedToHandler . 293 | ( 294 | componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => option 'state 295 | ) => 296 | 'dataPassedToHandler => 297 | unit, 298 | 299 | handler: 300 | 'dataPassedToHandler . 301 | (componentBag 'state 'props 'instanceVars => 'dataPassedToHandler => unit) => 'dataPassedToHandler => unit, 302 | instanceVars: 'instanceVars, 303 | setState: (componentBag 'state 'props 'instanceVars => 'state) => unit 304 | }; 305 | let componentDidMount: 'a => option 'b; 306 | let componentWillUpdate: 'a => nextProps::'b => nextState::'c => option 'd; 307 | let componentDidUpdate: prevProps::'a => prevState::'b => 'c => option 'd; 308 | let componentWillReceiveProps: 'a => nextProps::'b => option 'c; 309 | let componentWillUnmount: 'a => unit; 310 | type state = unit; 311 | let getInitialState: 'a => unit; 312 | }; 313 | }; 314 | }; 315 | --------------------------------------------------------------------------------