hyper-component 0.12.3 → 0.99.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 (79) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +27 -0
  3. data/.gitignore +42 -41
  4. data/.travis.yml +29 -0
  5. data/CHANGELOG.md +143 -0
  6. data/DOCS.md +1515 -0
  7. data/Gemfile +5 -2
  8. data/Gemfile.lock +244 -193
  9. data/LICENSE +5 -7
  10. data/README.md +49 -0
  11. data/Rakefile +40 -0
  12. data/hyper-component.gemspec +41 -31
  13. data/lib/hyper-component.rb +44 -9
  14. data/lib/rails-helpers/top_level_rails_component.rb +79 -0
  15. data/lib/react/api.rb +270 -0
  16. data/lib/react/callbacks.rb +42 -0
  17. data/lib/react/children.rb +38 -0
  18. data/lib/react/component.rb +189 -0
  19. data/lib/react/component/api.rb +70 -0
  20. data/lib/react/component/base.rb +13 -0
  21. data/lib/react/component/class_methods.rb +175 -0
  22. data/lib/react/component/dsl_instance_methods.rb +23 -0
  23. data/lib/react/component/params.rb +6 -0
  24. data/lib/react/component/props_wrapper.rb +90 -0
  25. data/lib/react/component/should_component_update.rb +99 -0
  26. data/lib/react/component/tags.rb +116 -0
  27. data/lib/react/config.rb +5 -0
  28. data/lib/react/element.rb +159 -0
  29. data/lib/react/event.rb +76 -0
  30. data/lib/react/ext/hash.rb +9 -0
  31. data/lib/react/ext/opal-jquery/element.rb +37 -0
  32. data/lib/react/ext/string.rb +8 -0
  33. data/lib/react/native_library.rb +87 -0
  34. data/lib/react/object.rb +15 -0
  35. data/lib/react/react-source-server.rb +3 -0
  36. data/lib/react/react-source.rb +17 -0
  37. data/lib/react/ref_callback.rb +31 -0
  38. data/lib/react/rendering_context.rb +149 -0
  39. data/lib/react/server.rb +19 -0
  40. data/lib/react/state_wrapper.rb +23 -0
  41. data/lib/react/test.rb +16 -0
  42. data/lib/react/test/dsl.rb +17 -0
  43. data/lib/react/test/matchers/render_html_matcher.rb +56 -0
  44. data/lib/react/test/rspec.rb +15 -0
  45. data/lib/react/test/session.rb +37 -0
  46. data/lib/react/test/utils.rb +71 -0
  47. data/lib/react/to_key.rb +26 -0
  48. data/lib/react/top_level.rb +110 -0
  49. data/lib/react/top_level_render.rb +28 -0
  50. data/lib/react/validator.rb +132 -0
  51. data/lib/reactive-ruby/component_loader.rb +43 -0
  52. data/lib/reactive-ruby/isomorphic_helpers.rb +233 -0
  53. data/lib/reactive-ruby/rails.rb +8 -0
  54. data/lib/reactive-ruby/rails/component_mount.rb +48 -0
  55. data/lib/reactive-ruby/rails/controller_helper.rb +14 -0
  56. data/lib/reactive-ruby/rails/railtie.rb +20 -0
  57. data/lib/reactive-ruby/serializers.rb +23 -0
  58. data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +46 -0
  59. data/lib/reactive-ruby/server_rendering/hyper_asset_container.rb +46 -0
  60. data/lib/{hyperloop/component → reactive-ruby}/version.rb +1 -1
  61. data/lib/reactrb/auto-import.rb +27 -0
  62. data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +3 -0
  63. data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/server_rendering.js +5 -0
  64. data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +2 -0
  65. data/misc/generators/reactive_ruby/test_app/templates/boot.rb.erb +6 -0
  66. data/misc/generators/reactive_ruby/test_app/templates/script/rails +5 -0
  67. data/misc/generators/reactive_ruby/test_app/templates/test_application.rb.erb +13 -0
  68. data/misc/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
  69. data/misc/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
  70. data/misc/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
  71. data/misc/generators/reactive_ruby/test_app/test_app_generator.rb +121 -0
  72. data/misc/how-component-name-lookup-works.md +145 -0
  73. data/misc/hyperloop-logo-small-pink.png +0 -0
  74. data/misc/logo1.png +0 -0
  75. data/misc/logo2.png +0 -0
  76. data/misc/logo3.png +0 -0
  77. data/path_release_steps.md +9 -0
  78. metadata +260 -37
  79. data/CODE_OF_CONDUCT.md +0 -49
@@ -0,0 +1,42 @@
1
+ require 'hyperloop-config'
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, &after_define_hook)
21
+ wrapper_name = "_#{callback_name}_callbacks"
22
+ define_singleton_method(wrapper_name) do
23
+ Hyperloop::Context.set_var(self, "@#{wrapper_name}", force: true) { [] }
24
+ end
25
+ define_singleton_method(callback_name) do |*args, &block|
26
+ send(wrapper_name).concat(args)
27
+ send(wrapper_name).push(block) if block_given?
28
+ after_define_hook.call(*args, &block) if after_define_hook
29
+ end
30
+ end
31
+
32
+ def callbacks_for(callback_name)
33
+ wrapper_name = "_#{callback_name}_callbacks"
34
+ if superclass.respond_to? :callbacks_for
35
+ superclass.callbacks_for(callback_name)
36
+ else
37
+ []
38
+ end + send(wrapper_name)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,38 @@
1
+ module React
2
+ class Children
3
+ include Enumerable
4
+
5
+ def initialize(children)
6
+ @children = children
7
+ end
8
+
9
+ def render
10
+ each(&:render)
11
+ end
12
+
13
+ def to_proc
14
+ -> () { render }
15
+ end
16
+
17
+ def each(&block)
18
+ return to_enum(__callee__) { length } unless block_given?
19
+ return [] unless length > 0
20
+ collection = []
21
+ %x{
22
+ React.Children.forEach(#{@children}, function(context){
23
+ #{
24
+ element = React::Element.new(`context`)
25
+ block.call(element)
26
+ collection << element
27
+ }
28
+ })
29
+ }
30
+ collection
31
+ end
32
+
33
+ def length
34
+ @length ||= `React.Children.count(#{@children})`
35
+ end
36
+ alias_method :size, :length
37
+ end
38
+ end
@@ -0,0 +1,189 @@
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 'hyper-store'
7
+ require 'react/state_wrapper'
8
+ require 'react/component/api'
9
+ require 'react/component/class_methods'
10
+ require 'react/component/props_wrapper'
11
+
12
+ module Hyperloop
13
+ class Component
14
+ class << self
15
+ def mounted_components
16
+ @mounted_components ||= Set.new
17
+ end
18
+
19
+ def force_update!
20
+ components = mounted_components.to_a
21
+ components.each do |comp|
22
+ next unless mounted_components.include? comp
23
+ comp.force_update!
24
+ end
25
+ end
26
+ end
27
+
28
+ module Mixin
29
+ def self.included(base)
30
+ base.include(Hyperloop::Store::Mixin)
31
+ base.include(React::Component::API)
32
+ base.include(React::Callbacks)
33
+ base.include(React::Component::Tags)
34
+ base.include(React::Component::DslInstanceMethods)
35
+ base.include(React::Component::ShouldComponentUpdate)
36
+ base.class_eval do
37
+ class_attribute :initial_state
38
+ define_callback :before_mount
39
+ define_callback :after_mount
40
+ define_callback :before_receive_props
41
+ define_callback :before_update
42
+ define_callback :after_update
43
+ define_callback :before_unmount
44
+ define_callback(:after_error) { React::API.add_after_error_hook(base) }
45
+ end
46
+ base.extend(React::Component::ClassMethods)
47
+ end
48
+
49
+ def self.deprecation_warning(message)
50
+ React::Component.deprecation_warning(name, message)
51
+ end
52
+
53
+ def deprecation_warning(message)
54
+ React::Component.deprecation_warning(self.class.name, message)
55
+ end
56
+
57
+ def initialize(native_element)
58
+ @native = native_element
59
+ init_store
60
+ end
61
+
62
+ def emit(event_name, *args)
63
+ if React::Event::BUILT_IN_EVENTS.include?(built_in_event_name = "on#{event_name.to_s.event_camelize}")
64
+ params[built_in_event_name].call(*args)
65
+ else
66
+ params["on_#{event_name}"].call(*args)
67
+ end
68
+ end
69
+
70
+ def component_will_mount
71
+ React::IsomorphicHelpers.load_context(true) if React::IsomorphicHelpers.on_opal_client?
72
+ React::State.set_state_context_to(self) do
73
+ Hyperloop::Component.mounted_components << self
74
+ run_callback(:before_mount)
75
+ end
76
+ end
77
+
78
+ def component_did_mount
79
+ React::State.set_state_context_to(self) do
80
+ run_callback(:after_mount)
81
+ React::State.update_states_to_observe
82
+ end
83
+ end
84
+
85
+ def component_will_receive_props(next_props)
86
+ # need to rethink how this works in opal-react, or if its actually that useful within the react.rb environment
87
+ # for now we are just using it to clear processed_params
88
+ React::State.set_state_context_to(self) { run_callback(:before_receive_props, next_props) }
89
+ @_receiving_props = true
90
+ end
91
+
92
+ def component_will_update(next_props, next_state)
93
+ React::State.set_state_context_to(self) { run_callback(:before_update, next_props, next_state) }
94
+ params._reset_all_others_cache if @_receiving_props
95
+ @_receiving_props = false
96
+ end
97
+
98
+ def component_did_update(prev_props, prev_state)
99
+ React::State.set_state_context_to(self) do
100
+ run_callback(:after_update, prev_props, prev_state)
101
+ React::State.update_states_to_observe
102
+ end
103
+ end
104
+
105
+ def component_will_unmount
106
+ React::State.set_state_context_to(self) do
107
+ run_callback(:before_unmount)
108
+ React::State.remove
109
+ Hyperloop::Component.mounted_components.delete self
110
+ end
111
+ end
112
+
113
+ def component_did_catch(error, info)
114
+ React::State.set_state_context_to(self) do
115
+ run_callback(:after_error, error, info)
116
+ end
117
+ end
118
+
119
+ attr_reader :waiting_on_resources
120
+
121
+ def update_react_js_state(object, name, value)
122
+ if object
123
+ name = "#{object.class}.#{name}" unless object == self
124
+ # Date.now() has only millisecond precision, if several notifications of
125
+ # observer happen within a millisecond, updates may get lost.
126
+ # to mitigate this the Math.random() appends some random number
127
+ # this way notifactions will happen as expected by the rest of hyperloop
128
+ set_state(
129
+ '***_state_updated_at-***' => `Date.now() + Math.random()`,
130
+ name => value
131
+ )
132
+ else
133
+ set_state name => value
134
+ end
135
+ end
136
+
137
+ def set_state_synchronously?
138
+ @native.JS[:__opalInstanceSyncSetState]
139
+ end
140
+
141
+ def render
142
+ raise 'no render defined'
143
+ end unless method_defined?(:render)
144
+
145
+ def _render_wrapper
146
+ React::State.set_state_context_to(self, true) do
147
+ element = React::RenderingContext.render(nil) { render || '' }
148
+ @waiting_on_resources =
149
+ element.waiting_on_resources if element.respond_to? :waiting_on_resources
150
+ element
151
+ end
152
+ end
153
+
154
+ def watch(value, &on_change)
155
+ Observable.new(value, on_change)
156
+ end
157
+
158
+ def define_state(*args, &block)
159
+ React::State.initialize_states(self, self.class.define_state(*args, &block))
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ module React
166
+ module Component
167
+ def self.included(base)
168
+ # note this is turned off during old style testing: See the spec_helper
169
+ deprecation_warning base, "The module name React::Component has been deprecated. Use Hyperloop::Component::Mixin instead."
170
+ base.include Hyperloop::Component::Mixin
171
+ end
172
+ def self.deprecation_warning(name, message)
173
+ @deprecation_messages ||= []
174
+ message = "Warning: Deprecated feature used in #{name}. #{message}"
175
+ unless @deprecation_messages.include? message
176
+ @deprecation_messages << message
177
+ React::IsomorphicHelpers.log message, :warning
178
+ end
179
+ end
180
+ end
181
+ module ComponentNoNotice
182
+ def self.included(base)
183
+ base.include Hyperloop::Component::Mixin
184
+ end
185
+ end
186
+ end
187
+
188
+ module React
189
+ end
@@ -0,0 +1,70 @@
1
+ module React
2
+ module Component
3
+ module API
4
+ def dom_node
5
+ `ReactDOM.findDOMNode(#{self}.native)` # react >= v0.15.0
6
+ end
7
+
8
+ def mounted?
9
+ `(#{self}.is_mounted === undefined) ? false : #{self}.is_mounted`
10
+ end
11
+
12
+ def force_update!
13
+ `#{self}.native.forceUpdate()`
14
+ self
15
+ end
16
+
17
+ def set_props(prop, &block)
18
+ raise "set_props: setProps() is no longer supported by react"
19
+ end
20
+ alias :set_props! :set_props
21
+
22
+ def set_state(state, &block)
23
+ set_or_replace_state_or_prop(state, 'setState', &block)
24
+ end
25
+
26
+ def set_state!(state, &block)
27
+ set_or_replace_state_or_prop(state, 'setState', &block)
28
+ `#{self}.native.forceUpdate()`
29
+ end
30
+
31
+ private
32
+
33
+ def set_or_replace_state_or_prop(state_or_prop, method, &block)
34
+ raise "No native ReactComponent associated" unless @native
35
+ `var state_prop_n = #{state_or_prop.shallow_to_n}`
36
+ # the state object is initalized when the ruby component is instantiated
37
+ # this is detected by self.native.__opalInstanceInitializedState
38
+ # which is set in the native component constructor in react/api.rb
39
+ # the setState update callback is not called when initalizing initial state
40
+ if block
41
+ %x{
42
+ if (#{@native}.__opalInstanceInitializedState === true) {
43
+ #{@native}[method](state_prop_n, function(){
44
+ block.$call();
45
+ });
46
+ } else {
47
+ for (var sp in state_prop_n) {
48
+ if (state_prop_n.hasOwnProperty(sp)) {
49
+ #{@native}.state[sp] = state_prop_n[sp];
50
+ }
51
+ }
52
+ }
53
+ }
54
+ else
55
+ %x{
56
+ if (#{@native}.__opalInstanceInitializedState === true) {
57
+ #{@native}[method](state_prop_n);
58
+ } else {
59
+ for (var sp in state_prop_n) {
60
+ if (state_prop_n.hasOwnProperty(sp)) {
61
+ #{@native}.state[sp] = state_prop_n[sp];
62
+ }
63
+ }
64
+ }
65
+ }
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,13 @@
1
+ module React
2
+ module Component
3
+ class Base
4
+ def self.inherited(child)
5
+ # note this is turned off during old style testing: See the spec_helper
6
+ unless child.to_s == "React::Component::HyperTestDummy"
7
+ React::Component.deprecation_warning child, "The class name React::Component::Base has been deprecated. Use Hyperloop::Component instead."
8
+ end
9
+ child.include(ComponentNoNotice)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,175 @@
1
+ module React
2
+ module Component
3
+ # class level methods (macros) for components
4
+ module ClassMethods
5
+
6
+ def deprecation_warning(message)
7
+ React::Component.deprecation_warning(self, message)
8
+ end
9
+
10
+ def reactrb_component?
11
+ true
12
+ end
13
+
14
+ def backtrace(*args)
15
+ @dont_catch_exceptions = (args[0] == :none)
16
+ @backtrace_off = @dont_catch_exceptions || (args[0] == :off)
17
+ end
18
+
19
+ def append_backtrace(message_array, backtrace)
20
+ message_array << " #{backtrace[0]}"
21
+ backtrace[1..-1].each { |line| message_array << line }
22
+ end
23
+
24
+ def render(container = nil, params = {}, &block)
25
+ if container
26
+ container = container.type if container.is_a? React::Element
27
+ define_method :render do
28
+ React::RenderingContext.render(container, params) { instance_eval(&block) if block }
29
+ end
30
+ else
31
+ define_method(:render) { instance_eval(&block) }
32
+ end
33
+ end
34
+
35
+ # method missing will assume the method is a class name, and will treat this a render of
36
+ # of the component, i.e. Foo::Bar.baz === Foo::Bar().baz
37
+
38
+ def method_missing(name, *args, &children)
39
+ Object.method_missing(name, *args, &children) unless args.empty?
40
+ React::RenderingContext.render(
41
+ self, class: React::Element.haml_class_name(name), &children
42
+ )
43
+ end
44
+
45
+ def validator
46
+ @validator ||= Validator.new(props_wrapper)
47
+ end
48
+
49
+ def prop_types
50
+ if self.validator
51
+ {
52
+ _componentValidator: %x{
53
+ function(props, propName, componentName) {
54
+ var errors = #{validator.validate(Hash.new(`props`))};
55
+ return #{`errors`.count > 0 ? `new Error(#{"In component `#{name}`\n" + `errors`.join("\n")})` : `undefined`};
56
+ }
57
+ }
58
+ }
59
+ else
60
+ {}
61
+ end
62
+ end
63
+
64
+ def default_props
65
+ validator.default_props
66
+ end
67
+
68
+ def params(&block)
69
+ validator.build(&block)
70
+ end
71
+
72
+ def props_wrapper
73
+ @props_wrapper ||= Class.new(PropsWrapper)
74
+ end
75
+
76
+ def param(*args)
77
+ if args[0].is_a? Hash
78
+ options = args[0]
79
+ name = options.first[0]
80
+ default = options.first[1]
81
+ options.delete(name)
82
+ options.merge!({default: default})
83
+ else
84
+ name = args[0]
85
+ options = args[1] || {}
86
+ end
87
+ if options[:default]
88
+ validator.optional(name, options)
89
+ else
90
+ validator.requires(name, options)
91
+ end
92
+ end
93
+
94
+ def collect_other_params_as(name)
95
+ validator.all_other_params(name) { props }
96
+ end
97
+
98
+ alias other_params collect_other_params_as
99
+ alias others collect_other_params_as
100
+
101
+ def define_state(*states, &block)
102
+ deprecation_warning "'define_state' is deprecated. Use the 'state' macro to declare states."
103
+ default_initial_value = (block && block.arity == 0) ? yield : nil
104
+ states_hash = (states.last.is_a?(Hash)) ? states.pop : {}
105
+ states.each { |name| state(name => default_initial_value) } # was states_hash[name] = default_initial_value
106
+ states_hash.each { |name, value| state(name => value) }
107
+ end
108
+
109
+ def export_state(*states, &block)
110
+ deprecation_warning "'export_state' is deprecated. Use the 'state' macro to declare states."
111
+ default_initial_value = (block && block.arity == 0) ? yield : nil
112
+ states_hash = (states.last.is_a?(Hash)) ? states.pop : {}
113
+ states.each { |name| states_hash[name] = default_initial_value }
114
+ states_hash.each do |name, value|
115
+ state(name => value, scope: :class, reader: true)
116
+ singleton_class.define_method("#{name}!") do |*args|
117
+ mutate.__send__(name, *args)
118
+ end
119
+ end
120
+ end
121
+
122
+ def native_mixin(item)
123
+ native_mixins << item
124
+ end
125
+
126
+ def native_mixins
127
+ @native_mixins ||= []
128
+ end
129
+
130
+ def static_call_back(name, &block)
131
+ static_call_backs[name] = block
132
+ end
133
+
134
+ def static_call_backs
135
+ @static_call_backs ||= {}
136
+ end
137
+
138
+ def export_component(opts = {})
139
+ export_name = (opts[:as] || name).split('::')
140
+ first_name = export_name.first
141
+ Native(`Opal.global`)[first_name] = add_item_to_tree(
142
+ Native(`Opal.global`)[first_name],
143
+ [React::API.create_native_react_class(self)] + export_name[1..-1].reverse
144
+ ).to_n
145
+ end
146
+
147
+ def imports(component_name)
148
+ React::API.import_native_component(
149
+ self, React::API.eval_native_react_component(component_name)
150
+ )
151
+ define_method(:render) {} # define a dummy render method - will never be called...
152
+ rescue Exception => e # rubocop:disable Lint/RescueException : we need to catch everything!
153
+ raise "#{self} cannot import '#{component_name}': #{e.message}."
154
+ # rubocop:enable Lint/RescueException
155
+ ensure
156
+ self
157
+ end
158
+
159
+ def add_item_to_tree(current_tree, new_item)
160
+ if Native(current_tree).class != Native::Object || new_item.length == 1
161
+ new_item.inject { |a, e| { e => a } }
162
+ else
163
+ Native(current_tree)[new_item.last] = add_item_to_tree(
164
+ Native(current_tree)[new_item.last], new_item[0..-2]
165
+ )
166
+ current_tree
167
+ end
168
+ end
169
+
170
+ def to_n
171
+ React::API.class_eval('@@component_classes')[self]
172
+ end
173
+ end
174
+ end
175
+ end