hyper-react 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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