module React class ShallowWrapper def initialize(native) @native = native end def [](key) `return #{@native}[key] || #{nil};` end end def self.fix_props(props) return `null` if props.nil? %x{ const jsProps = {}; #{ props.each { |key, value| if key.start_with?('on') chain = value value = -> (event) { chain.call(Native(event)) } elsif key == 'style' value = value.to_n end `jsProps[key] = value;` } } return jsProps; } end def self.fix_state(state) return `null` if state.nil? %x{ const jsState = {}; #{ state.each { |key, value| `jsState[key] = value;` } } return jsState; } end class Component include DOM include Window def self.inherited(subclass) super _class = ` class extends React.Component { static get defaultProps() { return #{React.fix_props(`#{subclass}.$default_props()`)}; } constructor(props) { super(props); this.self = #{subclass.new(`props`)}; this.state = #{React.fix_state(`this.self.$initial_state()`)}; } render() { return this.self.$bridge(this).self.$render(); } componentWillMount() { this.self.$bridge(this).self.$component_will_mount(); } componentDidMount() { this.self.$bridge(this).self.$component_did_mount(); } componentWillReceiveProps(nextProps) { this.self.$bridge(this).self.$component_will_receive_props(nextProps); } shouldComponentUpdate(nextProps, nextState) { return ("$should_component_update?" in this.self) ? this.self.$bridge(this).self["$should_component_update?"](nextProps, nextState) : shallowCompare(this, nextProps, nextState); } componentWillUpdate(nextProps, nextState) { this.self.$bridge(this).self.$component_will_update(nextProps, nextState); } componentDidUpdate(prevProps, prevState) { this.self.$bridge(this).self.$component_did_update(prevProps, prevState); } componentWillUnmount() { this.self.$bridge(this).self.$component_will_unmount(); } } ` _create = Proc.new { |props = nil, children = nil, &block| if children.nil? if `Array.isArray(props)` children = props props = nil elsif !props.nil? && `typeof props !== 'object'` children = [props] props = nil end end `return React.createElement( #{_class}, #{React.fix_props(props)}, #{children.to_n} );` } name = subclass.dsl_name(subclass.name) DOM.define_method(name, _create) subclass.class.define_method('run') do |props: nil, container: nil| if container ReactDOM.render(_create.call(props), container) else ReactDOM.render(_create.call(props), container = $$.document.createElement('div')) do $$.document.body.appendChild(container) end end end end def self.dsl_name(name) name.gsub( /Component$/, '') end def bridge(value) @this = value end def self.default_props nil end def initial_state nil end def prop_types nil end def component_will_mount() end def component_did_mount() end def component_will_receive_props(next_props) end # def should_component_update?(next_props, next_state) # true # end def component_will_update(next_props, next_state) end def component_did_update(next_props, next_state) end def component_will_unmount() end def render() end def props ShallowWrapper.new(`#{@this}.props`) end def state ShallowWrapper.new(`#{@this}.state`) end def set_state(next_state, &block) block = `null` if block.nil? `#{@this}.setState(#{React.fix_state(next_state)}, block);` end def replace_state(next_state, &block) block = `null` if block.nil? `#{@this}.replaceState(#{React.fix_state(next_state)}, block);` end def force_update `#{@this}.forceUpdate();` end # deprecated (more or less) def mounted? `return #{@this}.isMounted();` end def defer(ms=0) $$[:setTimeout].call( -> { yield if block_given? }, ms) end end end