hyper-react 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +27 -0
  3. data/.gitignore +36 -0
  4. data/.rubocop.yml +1159 -0
  5. data/.travis.yml +29 -0
  6. data/Appraisals +20 -0
  7. data/CHANGELOG.md +93 -0
  8. data/Gemfile +6 -0
  9. data/LICENSE +19 -0
  10. data/README.md +121 -0
  11. data/Rakefile +33 -0
  12. data/UPGRADING.md +24 -0
  13. data/component-name-lookup.md +145 -0
  14. data/config.ru +25 -0
  15. data/gemfiles/opal_0.8_react_13.gemfile +13 -0
  16. data/gemfiles/opal_0.8_react_14.gemfile +13 -0
  17. data/gemfiles/opal_0.8_react_15.gemfile +13 -0
  18. data/gemfiles/opal_0.9_react_13.gemfile +13 -0
  19. data/gemfiles/opal_0.9_react_14.gemfile +13 -0
  20. data/gemfiles/opal_0.9_react_15.gemfile +13 -0
  21. data/hyper-react.gemspec +43 -0
  22. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +4 -0
  23. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +2 -0
  24. data/lib/generators/reactive_ruby/test_app/templates/boot.rb.erb +6 -0
  25. data/lib/generators/reactive_ruby/test_app/templates/script/rails +5 -0
  26. data/lib/generators/reactive_ruby/test_app/templates/test_application.rb.erb +13 -0
  27. data/lib/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
  28. data/lib/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
  29. data/lib/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
  30. data/lib/generators/reactive_ruby/test_app/test_app_generator.rb +109 -0
  31. data/lib/hyper-react.rb +52 -0
  32. data/lib/rails-helpers/top_level_rails_component.rb +54 -0
  33. data/lib/react-sources/react-server.js +2 -0
  34. data/lib/react/api.rb +162 -0
  35. data/lib/react/callbacks.rb +42 -0
  36. data/lib/react/children.rb +30 -0
  37. data/lib/react/component.rb +139 -0
  38. data/lib/react/component/api.rb +50 -0
  39. data/lib/react/component/base.rb +9 -0
  40. data/lib/react/component/class_methods.rb +214 -0
  41. data/lib/react/component/dsl_instance_methods.rb +27 -0
  42. data/lib/react/component/params.rb +6 -0
  43. data/lib/react/component/props_wrapper.rb +83 -0
  44. data/lib/react/component/should_component_update.rb +98 -0
  45. data/lib/react/component/tags.rb +144 -0
  46. data/lib/react/element.rb +168 -0
  47. data/lib/react/event.rb +76 -0
  48. data/lib/react/ext/hash.rb +9 -0
  49. data/lib/react/ext/string.rb +8 -0
  50. data/lib/react/hash.rb +13 -0
  51. data/lib/react/native_library.rb +92 -0
  52. data/lib/react/object.rb +15 -0
  53. data/lib/react/observable.rb +29 -0
  54. data/lib/react/react-source.rb +9 -0
  55. data/lib/react/rendering_context.rb +142 -0
  56. data/lib/react/state.rb +190 -0
  57. data/lib/react/test.rb +16 -0
  58. data/lib/react/test/dsl.rb +17 -0
  59. data/lib/react/test/matchers/render_html_matcher.rb +49 -0
  60. data/lib/react/test/rspec.rb +15 -0
  61. data/lib/react/test/session.rb +46 -0
  62. data/lib/react/top_level.rb +132 -0
  63. data/lib/react/validator.rb +136 -0
  64. data/lib/reactive-ruby/component_loader.rb +49 -0
  65. data/lib/reactive-ruby/isomorphic_helpers.rb +197 -0
  66. data/lib/reactive-ruby/rails.rb +7 -0
  67. data/lib/reactive-ruby/rails/component_mount.rb +46 -0
  68. data/lib/reactive-ruby/rails/controller_helper.rb +15 -0
  69. data/lib/reactive-ruby/rails/railtie.rb +14 -0
  70. data/lib/reactive-ruby/serializers.rb +15 -0
  71. data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +42 -0
  72. data/lib/reactive-ruby/version.rb +3 -0
  73. data/lib/reactrb/auto-import.rb +32 -0
  74. data/lib/reactrb/deep-compare.rb +24 -0
  75. data/lib/reactrb/new-event-name-convention.rb +11 -0
  76. data/lib/sources/react-latest.js +21169 -0
  77. data/lib/sources/react-v13.js +21645 -0
  78. data/lib/sources/react-v14.js +20821 -0
  79. data/lib/sources/react-v15.js +21170 -0
  80. data/logo1.png +0 -0
  81. data/logo2.png +0 -0
  82. data/logo3.png +0 -0
  83. data/path_release_steps.md +9 -0
  84. data/spec/controller_helper_spec.rb +34 -0
  85. data/spec/index.html.erb +10 -0
  86. data/spec/react/callbacks_spec.rb +106 -0
  87. data/spec/react/children_spec.rb +76 -0
  88. data/spec/react/component/base_spec.rb +32 -0
  89. data/spec/react/component_spec.rb +872 -0
  90. data/spec/react/dsl_spec.rb +296 -0
  91. data/spec/react/element_spec.rb +136 -0
  92. data/spec/react/event_spec.rb +24 -0
  93. data/spec/react/native_library_spec.rb +344 -0
  94. data/spec/react/observable_spec.rb +7 -0
  95. data/spec/react/opal_jquery_extensions_spec.rb +66 -0
  96. data/spec/react/param_declaration_spec.rb +258 -0
  97. data/spec/react/react_spec.rb +209 -0
  98. data/spec/react/state_spec.rb +55 -0
  99. data/spec/react/test/dsl_spec.rb +43 -0
  100. data/spec/react/test/matchers/render_html_matcher_spec.rb +83 -0
  101. data/spec/react/test/rspec_spec.rb +62 -0
  102. data/spec/react/test/session_spec.rb +100 -0
  103. data/spec/react/test/utils_spec.rb +45 -0
  104. data/spec/react/top_level_component_spec.rb +96 -0
  105. data/spec/react/tutorial/tutorial_spec.rb +36 -0
  106. data/spec/react/validator_spec.rb +124 -0
  107. data/spec/reactive-ruby/component_loader_spec.rb +71 -0
  108. data/spec/reactive-ruby/isomorphic_helpers_spec.rb +155 -0
  109. data/spec/reactive-ruby/rails/asset_pipeline_spec.rb +10 -0
  110. data/spec/reactive-ruby/rails/component_mount_spec.rb +66 -0
  111. data/spec/reactive-ruby/server_rendering/contextual_renderer_spec.rb +35 -0
  112. data/spec/spec_helper.rb +115 -0
  113. data/spec/support/react/spec_helpers.rb +64 -0
  114. data/spec/vendor/es5-shim.min.js +6 -0
  115. data/spec/vendor/jquery-2.2.4.min.js +4 -0
  116. metadata +387 -0
@@ -0,0 +1,2 @@
1
+ // A placeholder file to prevent file not found error of requireing
2
+ // `react-server` in react/react-source
@@ -0,0 +1,162 @@
1
+ require 'react/native_library'
2
+
3
+ module React
4
+ # Provides the internal mechanisms to interface between reactrb and native components
5
+ # the code will attempt to create a js component wrapper on any rb class that has a
6
+ # render (or possibly _render_wrapper) method. The mapping between rb and js components
7
+ # is kept in the @@component_classes hash.
8
+
9
+ # Also provides the mechanism to build react elements
10
+
11
+ # TOOO - the code to deal with components should be moved to a module that will be included
12
+ # in a class which will then create the JS component for that class. That module will then
13
+ # be included in React::Component, but can be used by any class wanting to become a react
14
+ # component (but without other DSL characteristics.)
15
+ class API
16
+ @@component_classes = {}
17
+
18
+ def self.import_native_component(opal_class, native_class)
19
+ opal_class.instance_variable_set("@native_import", true)
20
+ @@component_classes[opal_class] = native_class
21
+ end
22
+
23
+ def self.eval_native_react_component(name)
24
+ component = `eval(name)`
25
+ raise "#{name} is not defined" if `#{component} === undefined`
26
+ is_component_class = `#{component}.prototype !== undefined` &&
27
+ (`!!#{component}.prototype.isReactComponent` ||
28
+ `!!#{component}.prototype.render`)
29
+ is_functional_component = `typeof #{component} === "function"`
30
+ is_not_using_react_v13 = `!window.React.version.match(/0\.13/)`
31
+ unless is_component_class || (is_not_using_react_v13 && is_functional_component)
32
+ raise 'does not appear to be a native react component'
33
+ end
34
+ component
35
+ end
36
+
37
+ def self.native_react_component?(name)
38
+ eval_native_react_component(name)
39
+ rescue
40
+ nil
41
+ end
42
+
43
+ def self.create_native_react_class(type)
44
+ raise "Provided class should define `render` method" if !(type.method_defined? :render)
45
+ render_fn = (type.method_defined? :_render_wrapper) ? :_render_wrapper : :render
46
+ # this was hashing type.to_s, not sure why but .to_s does not work as it Foo::Bar::View.to_s just returns "View"
47
+ @@component_classes[type] ||= %x{
48
+ React.createClass({
49
+ displayName: #{type.name},
50
+ propTypes: #{type.respond_to?(:prop_types) ? type.prop_types.to_n : `{}`},
51
+ getDefaultProps: function(){
52
+ return #{type.respond_to?(:default_props) ? type.default_props.to_n : `{}`};
53
+ },
54
+ mixins: #{type.respond_to?(:native_mixins) ? type.native_mixins : `[]`},
55
+ statics: #{type.respond_to?(:static_call_backs) ? type.static_call_backs.to_n : `{}`},
56
+ componentWillMount: function() {
57
+ var instance = this._getOpalInstance.apply(this);
58
+ return #{`instance`.component_will_mount if type.method_defined? :component_will_mount};
59
+ },
60
+ componentDidMount: function() {
61
+ var instance = this._getOpalInstance.apply(this);
62
+ return #{`instance`.component_did_mount if type.method_defined? :component_did_mount};
63
+ },
64
+ componentWillReceiveProps: function(next_props) {
65
+ var instance = this._getOpalInstance.apply(this);
66
+ return #{`instance`.component_will_receive_props(Hash.new(`next_props`)) if type.method_defined? :component_will_receive_props};
67
+ },
68
+ shouldComponentUpdate: function(next_props, next_state) {
69
+ var instance = this._getOpalInstance.apply(this);
70
+ return #{`instance`.should_component_update?(Hash.new(`next_props`), Hash.new(`next_state`)) if type.method_defined? :should_component_update?};
71
+ },
72
+ componentWillUpdate: function(next_props, next_state) {
73
+ var instance = this._getOpalInstance.apply(this);
74
+ return #{`instance`.component_will_update(Hash.new(`next_props`), Hash.new(`next_state`)) if type.method_defined? :component_will_update};
75
+ },
76
+ componentDidUpdate: function(prev_props, prev_state) {
77
+ var instance = this._getOpalInstance.apply(this);
78
+ return #{`instance`.component_did_update(Hash.new(`prev_props`), Hash.new(`prev_state`)) if type.method_defined? :component_did_update};
79
+ },
80
+ componentWillUnmount: function() {
81
+ var instance = this._getOpalInstance.apply(this);
82
+ return #{`instance`.component_will_unmount if type.method_defined? :component_will_unmount};
83
+ },
84
+ _getOpalInstance: function() {
85
+ if (this.__opalInstance == undefined) {
86
+ var instance = #{type.new(`this`)};
87
+ } else {
88
+ var instance = this.__opalInstance;
89
+ }
90
+ this.__opalInstance = instance;
91
+ return instance;
92
+ },
93
+ render: function() {
94
+ var instance = this._getOpalInstance.apply(this);
95
+ return #{`instance`.send(render_fn).to_n};
96
+ }
97
+ })
98
+ }
99
+ end
100
+
101
+ def self.create_element(type, properties = {}, &block)
102
+ params = []
103
+
104
+ # Component Spec, Normal DOM, String or Native Component
105
+ if @@component_classes[type]
106
+ params << @@component_classes[type]
107
+ elsif type.kind_of?(Class)
108
+ params << create_native_react_class(type)
109
+ elsif React::Component::Tags::HTML_TAGS.include?(type)
110
+ params << type
111
+ elsif type.is_a? String
112
+ return React::Element.new(type)
113
+ else
114
+ raise "#{type} not implemented"
115
+ end
116
+
117
+ # Convert Passed in properties
118
+ properties = convert_props(properties)
119
+ params << properties.shallow_to_n
120
+
121
+ # Children Nodes
122
+ if block_given?
123
+ [yield].flatten.each do |ele|
124
+ params << ele.to_n
125
+ end
126
+ end
127
+ React::Element.new(`React.createElement.apply(null, #{params})`, type, properties, block)
128
+ end
129
+
130
+ def self.clear_component_class_cache
131
+ @@component_classes = {}
132
+ end
133
+
134
+ def self.convert_props(properties)
135
+ raise "Component parameters must be a hash. Instead you sent #{properties}" unless properties.is_a? Hash
136
+ props = {}
137
+ properties.map do |key, value|
138
+ if key == "class_name" && value.is_a?(Hash)
139
+ props[lower_camelize(key)] = `React.addons.classSet(#{value.to_n})`
140
+ elsif key == "class"
141
+ props["className"] = value
142
+ elsif ["style", "dangerously_set_inner_HTML"].include? key
143
+ props[lower_camelize(key)] = value.to_n
144
+ elsif React::HASH_ATTRIBUTES.include?(key) && value.is_a?(Hash)
145
+ value.each { |k, v| props["#{key}-#{k.tr('_', '-')}"] = v.to_n }
146
+ else
147
+ props[React.html_attr?(lower_camelize(key)) ? lower_camelize(key) : key] = value
148
+ end
149
+ end
150
+ props
151
+ end
152
+
153
+ private
154
+
155
+ def self.lower_camelize(snake_cased_word)
156
+ words = snake_cased_word.split('_')
157
+ result = [words.first]
158
+ result.concat(words[1..-1].map {|word| word[0].upcase + word[1..-1] })
159
+ result.join('')
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,42 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+
3
+ module React
4
+ module Callbacks
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ def run_callback(name, *args)
10
+ self.class.callbacks_for(name).each do |callback|
11
+ if callback.is_a?(Proc)
12
+ instance_exec(*args, &callback)
13
+ else
14
+ send(callback, *args)
15
+ end
16
+ end
17
+ end
18
+
19
+ module ClassMethods
20
+ def define_callback(callback_name)
21
+ attribute_name = "_#{callback_name}_callbacks"
22
+ class_attribute(attribute_name)
23
+ self.send("#{attribute_name}=", [])
24
+ define_singleton_method(callback_name) do |*args, &block|
25
+ callbacks = self.send(attribute_name)
26
+ callbacks.concat(args)
27
+ callbacks.push(block) if block_given?
28
+ self.send("#{attribute_name}=", callbacks)
29
+ end
30
+ end
31
+
32
+ def callbacks_for(callback_name)
33
+ attribute_name = "_#{callback_name}_callbacks"
34
+ if superclass.respond_to? :callbacks_for
35
+ superclass.callbacks_for(callback_name)
36
+ else
37
+ []
38
+ end + self.send(attribute_name)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,30 @@
1
+ module React
2
+ class Children
3
+ include Enumerable
4
+
5
+ def initialize(children)
6
+ @children = children
7
+ end
8
+
9
+ def each(&block)
10
+ return to_enum(__callee__) { length } unless block_given?
11
+ return [] unless length > 0
12
+ collection = []
13
+ %x{
14
+ React.Children.forEach(#{@children}, function(context){
15
+ #{
16
+ element = React::Element.new(`context`)
17
+ block.call(element)
18
+ collection << element
19
+ }
20
+ })
21
+ }
22
+ collection
23
+ end
24
+
25
+ def length
26
+ @length ||= `React.Children.count(#{@children})`
27
+ end
28
+ alias_method :size, :length
29
+ end
30
+ end
@@ -0,0 +1,139 @@
1
+ require 'react/ext/string'
2
+ require 'react/ext/hash'
3
+ require 'active_support/core_ext/class/attribute'
4
+ require 'react/callbacks'
5
+ require 'react/rendering_context'
6
+ require 'react/observable'
7
+ require 'react/state'
8
+ require 'react/component/api'
9
+ require 'react/component/class_methods'
10
+ require 'react/component/props_wrapper'
11
+ require 'native'
12
+
13
+ module React
14
+ module Component
15
+ def self.included(base)
16
+ base.include(API)
17
+ base.include(Callbacks)
18
+ base.include(Tags)
19
+ base.include(DslInstanceMethods)
20
+ base.include(ShouldComponentUpdate)
21
+ base.class_eval do
22
+ class_attribute :initial_state
23
+ define_callback :before_mount
24
+ define_callback :after_mount
25
+ define_callback :before_receive_props
26
+ define_callback :before_update
27
+ define_callback :after_update
28
+ define_callback :before_unmount
29
+ end
30
+ base.extend(ClassMethods)
31
+ end
32
+
33
+ def self.deprecation_warning(message)
34
+ @deprecation_messages ||= []
35
+ message = "Warning: Deprecated feature used in #{name}. #{message}"
36
+ unless @deprecation_messages.include? message
37
+ @deprecation_messages << message
38
+ IsomorphicHelpers.log message, :warning
39
+ end
40
+ end
41
+
42
+ def initialize(native_element)
43
+ @native = native_element
44
+ end
45
+
46
+ def emit(event_name, *args)
47
+ params["_on#{event_name.to_s.event_camelize}"].call(*args)
48
+ end
49
+
50
+ def component_will_mount
51
+ IsomorphicHelpers.load_context(true) if IsomorphicHelpers.on_opal_client?
52
+ set_state! initial_state if initial_state
53
+ State.initialize_states(self, initial_state)
54
+ State.set_state_context_to(self) { run_callback(:before_mount) }
55
+ rescue Exception => e
56
+ self.class.process_exception(e, self)
57
+ end
58
+
59
+ def component_did_mount
60
+ State.set_state_context_to(self) do
61
+ run_callback(:after_mount)
62
+ State.update_states_to_observe
63
+ end
64
+ rescue Exception => e
65
+ self.class.process_exception(e, self)
66
+ end
67
+
68
+ def component_will_receive_props(next_props)
69
+ # need to rethink how this works in opal-react, or if its actually that useful within the react.rb environment
70
+ # for now we are just using it to clear processed_params
71
+ State.set_state_context_to(self) { self.run_callback(:before_receive_props, Hash.new(next_props)) }
72
+ rescue Exception => e
73
+ self.class.process_exception(e, self)
74
+ end
75
+
76
+ def component_will_update(next_props, next_state)
77
+ State.set_state_context_to(self) { self.run_callback(:before_update, Hash.new(next_props), Hash.new(next_state)) }
78
+ rescue Exception => e
79
+ self.class.process_exception(e, self)
80
+ end
81
+
82
+ def component_did_update(prev_props, prev_state)
83
+ State.set_state_context_to(self) do
84
+ self.run_callback(:after_update, Hash.new(prev_props), Hash.new(prev_state))
85
+ State.update_states_to_observe
86
+ end
87
+ rescue Exception => e
88
+ self.class.process_exception(e, self)
89
+ end
90
+
91
+ def component_will_unmount
92
+ State.set_state_context_to(self) do
93
+ self.run_callback(:before_unmount)
94
+ State.remove
95
+ end
96
+ rescue Exception => e
97
+ self.class.process_exception(e, self)
98
+ end
99
+
100
+ attr_reader :waiting_on_resources
101
+
102
+ def update_react_js_state(object, name, value)
103
+ if object
104
+ name = "#{object.class}.#{name}" unless object == self
105
+ set_state(
106
+ '***_state_updated_at-***' => Time.now.to_f,
107
+ name => value
108
+ )
109
+ else
110
+ set_state name => value
111
+ end
112
+ end
113
+
114
+ def render
115
+ raise 'no render defined'
116
+ end unless method_defined?(:render)
117
+
118
+ def _render_wrapper
119
+ State.set_state_context_to(self, true) do
120
+ element = React::RenderingContext.render(nil) { render || '' }
121
+ @waiting_on_resources =
122
+ element.waiting_on_resources if element.respond_to? :waiting_on_resources
123
+ element
124
+ end
125
+ # rubocop:disable Lint/RescueException # we want to catch all exceptions regardless
126
+ rescue Exception => e
127
+ # rubocop:enable Lint/RescueException
128
+ self.class.process_exception(e, self)
129
+ end
130
+
131
+ def watch(value, &on_change)
132
+ Observable.new(value, on_change)
133
+ end
134
+
135
+ def define_state(*args, &block)
136
+ State.initialize_states(self, self.class.define_state(*args, &block))
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,50 @@
1
+ module React
2
+ module Component
3
+ module API
4
+ def dom_node
5
+ if !(`typeof ReactDOM === 'undefined' || typeof ReactDOM.findDOMNode === 'undefined'`)
6
+ `ReactDOM.findDOMNode(#{self}.native)` # v0.14.0
7
+ elsif !(`typeof React.findDOMNode === 'undefined'`)
8
+ `React.findDOMNode(#{self}.native)` # v0.13.0
9
+ else
10
+ `#{self}.native.getDOMNode` # v0.12.0
11
+ end
12
+ end
13
+
14
+ def mounted?
15
+ `#{self}.native.isMounted()`
16
+ end
17
+
18
+ def force_update!
19
+ `#{self}.native.forceUpdate()`
20
+ end
21
+
22
+ def set_props(prop, &block)
23
+ set_or_replace_state_or_prop(prop, 'setProps', &block)
24
+ end
25
+
26
+ def set_props!(prop, &block)
27
+ set_or_replace_state_or_prop(prop, 'replaceProps', &block)
28
+ end
29
+
30
+ def set_state(state, &block)
31
+ set_or_replace_state_or_prop(state, 'setState', &block)
32
+ end
33
+
34
+ def set_state!(state, &block)
35
+ set_or_replace_state_or_prop(state, 'replaceState', &block)
36
+ end
37
+
38
+ private
39
+
40
+ def set_or_replace_state_or_prop(state_or_prop, method, &block)
41
+ raise "No native ReactComponent associated" unless @native
42
+ %x{
43
+ #{@native}[#{method}](#{state_or_prop.shallow_to_n}, function(){
44
+ #{block.call if block}
45
+ });
46
+ }
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,9 @@
1
+ module React
2
+ module Component
3
+ class Base
4
+ def self.inherited(child)
5
+ child.include(Component)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,214 @@
1
+ module React
2
+ module Component
3
+ # class level methods (macros) for components
4
+ module ClassMethods
5
+
6
+ def reactrb_component?
7
+ true
8
+ end
9
+
10
+ def backtrace(*args)
11
+ @dont_catch_exceptions = (args[0] == :none)
12
+ @backtrace_off = @dont_catch_exceptions || (args[0] == :off)
13
+ end
14
+
15
+ def process_exception(e, component, reraise = @dont_catch_exceptions)
16
+ unless @dont_catch_exceptions
17
+ message = ["Exception raised while rendering #{component}: #{e.message}"]
18
+ if e.backtrace && e.backtrace.length > 1 && !@backtrace_off
19
+ append_backtrace(message, e.backtrace)
20
+ end
21
+ `console.error(#{message.join("\n")})`
22
+ end
23
+ raise e if reraise
24
+ end
25
+
26
+ def append_backtrace(message_array, backtrace)
27
+ message_array << " #{backtrace[0]}"
28
+ backtrace[1..-1].each { |line| message_array << line }
29
+ end
30
+
31
+ def render(container = nil, params = {}, &block)
32
+ if container
33
+ container = container.type if container.is_a? React::Element
34
+ define_method :render do
35
+ React::RenderingContext.render(container, params) { instance_eval(&block) if block }
36
+ end
37
+ else
38
+ define_method(:render) { instance_eval(&block) }
39
+ end
40
+ end
41
+
42
+ # method missing will assume the method is a class name, and will treat this a render of
43
+ # of the component, i.e. Foo::Bar.baz === Foo::Bar().baz
44
+
45
+ def method_missing(name, *args, &children)
46
+ Object.method_missing(name, *args, &children) unless args.empty?
47
+ React::RenderingContext.render(
48
+ self, class: React::Element.haml_class_name(name), &children
49
+ )
50
+ end
51
+
52
+ def validator
53
+ @validator ||= Validator.new(props_wrapper)
54
+ end
55
+
56
+ def prop_types
57
+ if self.validator
58
+ {
59
+ _componentValidator: %x{
60
+ function(props, propName, componentName) {
61
+ var errors = #{validator.validate(Hash.new(`props`))};
62
+ var error = new Error(#{"In component `#{name}`\n" + `errors`.join("\n")});
63
+ return #{`errors`.count > 0 ? `error` : `undefined`};
64
+ }
65
+ }
66
+ }
67
+ else
68
+ {}
69
+ end
70
+ end
71
+
72
+ def default_props
73
+ validator.default_props
74
+ end
75
+
76
+ def params(&block)
77
+ validator.build(&block)
78
+ end
79
+
80
+ def props_wrapper
81
+ @props_wrapper ||= Class.new(PropsWrapper)
82
+ end
83
+
84
+ def param(*args)
85
+ if args[0].is_a? Hash
86
+ options = args[0]
87
+ name = options.first[0]
88
+ default = options.first[1]
89
+ options.delete(name)
90
+ options.merge!({default: default})
91
+ else
92
+ name = args[0]
93
+ options = args[1] || {}
94
+ end
95
+ if options[:default]
96
+ validator.optional(name, options)
97
+ else
98
+ validator.requires(name, options)
99
+ end
100
+ end
101
+
102
+ def collect_other_params_as(name)
103
+ validator.allow_undefined_props = true
104
+ validator_in_lexical_scope = validator
105
+ props_wrapper.define_method(name) do
106
+ @_all_others ||= validator_in_lexical_scope.undefined_props(props)
107
+ end
108
+
109
+ validator_in_lexial_scope = validator
110
+ props_wrapper.define_method(name) do
111
+ @_all_others ||= validator_in_lexial_scope.undefined_props(props)
112
+ end
113
+ end
114
+
115
+ def define_state(*states, &block)
116
+ default_initial_value = (block && block.arity == 0) ? yield : nil
117
+ states_hash = (states.last.is_a?(Hash)) ? states.pop : {}
118
+ states.each { |name| states_hash[name] = default_initial_value }
119
+ (self.initial_state ||= {}).merge! states_hash
120
+ states_hash.each do |name, initial_value|
121
+ define_state_methods(self, name, &block)
122
+ end
123
+ end
124
+
125
+ def export_state(*states, &block)
126
+ default_initial_value = (block && block.arity == 0) ? yield : nil
127
+ states_hash = (states.last.is_a?(Hash)) ? states.pop : {}
128
+ states.each { |name| states_hash[name] = default_initial_value }
129
+ State.initialize_states(self, states_hash)
130
+ states_hash.each do |name, initial_value|
131
+ define_state_methods(self, name, self, &block)
132
+ define_state_methods(singleton_class, name, self, &block)
133
+ end
134
+ end
135
+
136
+ def define_state_methods(this, name, from = nil, &block)
137
+ this.define_method("#{name}") do
138
+ React::Component.deprecation_warning "Direct access to state `#{name}`. Use `state.#{name}` instead." if from.nil? || from == this
139
+ State.get_state(from || self, name)
140
+ end
141
+ this.define_method("#{name}=") do |new_state|
142
+ React::Component.deprecation_warning "Direct assignment to state `#{name}`. Use `#{(from && from != this) ? from : 'state'}.#{name}!` instead."
143
+ yield name, State.get_state(from || self, name), new_state if block && block.arity > 0
144
+ State.set_state(from || self, name, new_state)
145
+ end
146
+ this.define_method("#{name}!") do |*args|
147
+ React::Component.deprecation_warning "Direct access to state `#{name}`. Use `state.#{name}` instead." if from.nil? or from == this
148
+ if args.count > 0
149
+ yield name, State.get_state(from || self, name), args[0] if block && block.arity > 0
150
+ current_value = State.get_state(from || self, name)
151
+ State.set_state(from || self, name, args[0])
152
+ current_value
153
+ else
154
+ current_state = State.get_state(from || self, name)
155
+ yield name, State.get_state(from || self, name), current_state if block && block.arity > 0
156
+ State.set_state(from || self, name, current_state)
157
+ Observable.new(current_state) do |update|
158
+ yield name, State.get_state(from || self, name), update if block && block.arity > 0
159
+ State.set_state(from || self, name, update)
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ def native_mixin(item)
166
+ native_mixins << item
167
+ end
168
+
169
+ def native_mixins
170
+ @native_mixins ||= []
171
+ end
172
+
173
+ def static_call_back(name, &block)
174
+ static_call_backs[name] = block
175
+ end
176
+
177
+ def static_call_backs
178
+ @static_call_backs ||= {}
179
+ end
180
+
181
+ def export_component(opts = {})
182
+ export_name = (opts[:as] || name).split('::')
183
+ first_name = export_name.first
184
+ Native(`window`)[first_name] = add_item_to_tree(
185
+ Native(`window`)[first_name],
186
+ [React::API.create_native_react_class(self)] + export_name[1..-1].reverse
187
+ ).to_n
188
+ end
189
+
190
+ def imports(component_name)
191
+ React::API.import_native_component(
192
+ self, React::API.eval_native_react_component(component_name)
193
+ )
194
+ define_method(:render) {} # define a dummy render method - will never be called...
195
+ rescue Exception => e # rubocop:disable Lint/RescueException : we need to catch everything!
196
+ raise "#{self} cannot import '#{component_name}': #{e.message}."
197
+ # rubocop:enable Lint/RescueException
198
+ ensure
199
+ self
200
+ end
201
+
202
+ def add_item_to_tree(current_tree, new_item)
203
+ if Native(current_tree).class != Native::Object || new_item.length == 1
204
+ new_item.inject { |a, e| { e => a } }
205
+ else
206
+ Native(current_tree)[new_item.last] = add_item_to_tree(
207
+ Native(current_tree)[new_item.last], new_item[0..-2]
208
+ )
209
+ current_tree
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end