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,23 @@
1
+ module HyperStore
2
+ class StateWrapper < BaseStoreClass # < BasicObject
3
+
4
+ def [](state)
5
+ `#{__from__.instance_variable_get('@native')}.state[#{state}] || #{nil}`
6
+ end
7
+
8
+ def []=(state, new_value)
9
+ `#{__from__.instance_variable_get('@native')}.state[#{state}] = new_value`
10
+ end
11
+
12
+ alias pre_component_method_missing method_missing
13
+
14
+ def method_missing(method, *args)
15
+ if method.end_with?('!') && __from__.respond_to?(:deprecation_warning)
16
+ __from__.deprecation_warning("The mutator 'state.#{method}' has been deprecated. Use 'mutate.#{method.sub(/\!$/,'')}' instead.")
17
+ __from__.mutate.__send__(method.chop, *args)
18
+ else
19
+ pre_component_method_missing(method, *args)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ require 'react/test/session'
2
+ require 'react/test/dsl'
3
+
4
+ module React
5
+ module Test
6
+ class << self
7
+ def current_session
8
+ @current_session ||= Session.new
9
+ end
10
+
11
+ def reset_session!
12
+ @current_session = nil
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ require 'react/test'
2
+
3
+ module React
4
+ module Test
5
+ module DSL
6
+ def component
7
+ React::Test.current_session
8
+ end
9
+
10
+ Session::DSL_METHODS.each do |method|
11
+ define_method method do |*args, &block|
12
+ component.public_send(method, *args, &block)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,56 @@
1
+ module React
2
+ module Test
3
+ module Matchers
4
+ class RenderHTMLMatcher
5
+ def initialize(expected)
6
+ @expected = expected
7
+ @params = {}
8
+ end
9
+
10
+ def with_params(params)
11
+ @params = params
12
+ self
13
+ end
14
+
15
+ def matches?(component)
16
+ @component = component
17
+ @actual = render_to_html
18
+ @expected == @actual
19
+ end
20
+
21
+ def failure_message
22
+ failure_string
23
+ end
24
+
25
+ def failure_message_when_negated
26
+ failure_string(:negative)
27
+ end
28
+
29
+ alias negative_failure_message failure_message_when_negated
30
+
31
+ private
32
+
33
+ def render_to_html
34
+ element = React.create_element(@component, @params)
35
+ React::Server.render_to_static_markup(element)
36
+ end
37
+
38
+ def failure_string(negative = false)
39
+ str = "expected '#{@component.name}' with params '#{@params}' to "
40
+ str = str + "not " if negative
41
+ str = str + "render '#{@expected}', but '#{@actual}' was rendered."
42
+ str
43
+ end
44
+ end
45
+
46
+ def render_static_html(*args)
47
+ RenderHTMLMatcher.new(*args)
48
+ end
49
+
50
+ def render(*args)
51
+ %x{ console.error("Warning: `render` matcher is deprecated in favor of `render_static_html`."); }
52
+ RenderHTMLMatcher.new(*args)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,15 @@
1
+ require 'react/test/dsl'
2
+ require 'react/test/matchers/render_html_matcher'
3
+
4
+ RSpec.configure do |config|
5
+ config.include React::Test::DSL, type: :component
6
+ config.include React::Test::Matchers, type: :component
7
+
8
+ config.after do
9
+ React::Test.reset_session!
10
+ end
11
+
12
+ config.before do
13
+ # nothing yet
14
+ end
15
+ end
@@ -0,0 +1,37 @@
1
+ module React
2
+ module Test
3
+ class Session
4
+ DSL_METHODS = %i[mount instance update_params html].freeze
5
+
6
+ def mount(component_klass, params = {})
7
+ @element = React.create_element(component_klass, params)
8
+ instance
9
+ end
10
+
11
+ def instance
12
+ unless @instance
13
+ @container = `document.createElement('div')`
14
+ @instance = React.render(@element, @container)
15
+ end
16
+ @instance
17
+ end
18
+
19
+ def update_params(params, &block)
20
+ cloned_element = React::Element.new(`React.cloneElement(#{@element.to_n}, #{params.to_n})`)
21
+ React.render(cloned_element, @container, &block)
22
+ nil
23
+ end
24
+
25
+ def html
26
+ html = `#@container.innerHTML`
27
+ %x{
28
+ var REGEX_REMOVE_ROOT_IDS = /\s?data-reactroot="[^"]*"/g;
29
+ var REGEX_REMOVE_IDS = /\s?data-reactid="[^"]+"/g;
30
+ html = html.replace(REGEX_REMOVE_ROOT_IDS, '');
31
+ html = html.replace(REGEX_REMOVE_IDS, '');
32
+ }
33
+ return html
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,71 @@
1
+ module React
2
+ module Test
3
+ class Utils
4
+ def self.render_component_into_document(component, args = {})
5
+ element = React.create_element(component, args)
6
+ render_into_document(element)
7
+ end
8
+
9
+ def self.render_into_document(element)
10
+ raise "You should pass a valid React::Element" unless React.is_valid_element?(element)
11
+ dom_el = `document.body.querySelector('div[data-react-class="React.TopLevelRailsComponent"]').appendChild(document.createElement('div'))`
12
+ React.render(element, dom_el)
13
+ end
14
+
15
+ def self.simulate_click(element)
16
+ # element must be a component or a dom node or a element
17
+ el = if `typeof element.nodeType !== "undefined"`
18
+ element
19
+ elsif element.respond_to? :dom_node
20
+ element.dom_node
21
+ elsif element.is_a? React::Element
22
+ `ReactDOM.findDOMNode(#{element.to_n}.native)`
23
+ else
24
+ element
25
+ end
26
+ %x{
27
+ var evob = new MouseEvent('click', {
28
+ view: window,
29
+ bubbles: true,
30
+ cancelable: true
31
+ });
32
+ el.dispatchEvent(evob);
33
+ }
34
+ end
35
+
36
+ def self.simulate_keydown(element, key_name = "Enter")
37
+ # element must be a component or a dom node or a element
38
+ el = if `typeof element.nodeType !== "undefined"`
39
+ element
40
+ elsif element.respond_to? :dom_node
41
+ element.dom_node
42
+ elsif element.is_a? React::Element
43
+ `ReactDOM.findDOMNode(#{element.to_n}.native)`
44
+ else
45
+ element
46
+ end
47
+ %x{
48
+ var evob = new KeyboardEvent('keydown', { key: key_name, bubbles: true, cancelable: true });
49
+ el.dispatchEvent(evob);
50
+ }
51
+ end
52
+
53
+ def self.simulate_submit(element)
54
+ # element must be a component or a dom node or a element
55
+ el = if `typeof element.nodeType !== "undefined"`
56
+ element
57
+ elsif element.respond_to? :dom_node
58
+ element.dom_node
59
+ elsif element.is_a? React::Element
60
+ `ReactDOM.findDOMNode(#{element.to_n}.native)`
61
+ else
62
+ element
63
+ end
64
+ %x{
65
+ var evob = new Event('submit', { bubbles: true, cancelable: true });
66
+ el.dispatchEvent(evob);
67
+ }
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,26 @@
1
+ # to_key method returns a suitable unique id that can be used as
2
+ # a react `key`. Other classes may override to_key as needed
3
+ # for example hyper_mesh returns the object id of the internal
4
+ # backing record.
5
+ #
6
+ # to_key is automatically called on objects passed as keys for
7
+ # example Foo(key: my_object) results in Foo(key: my_object.to_key)
8
+ class Object
9
+ def to_key
10
+ object_id
11
+ end
12
+ end
13
+
14
+ # for Number to_key can just be the number itself
15
+ class Number
16
+ def to_key
17
+ self
18
+ end
19
+ end
20
+
21
+ # for Boolean to_key can be true or false
22
+ class Boolean
23
+ def to_key
24
+ self
25
+ end
26
+ end
@@ -0,0 +1,110 @@
1
+ require "native"
2
+ require 'active_support/core_ext/object/try'
3
+ require 'react/component/tags'
4
+ require 'react/component/base'
5
+
6
+ module React
7
+
8
+ ATTRIBUTES = %w(accept acceptCharset accessKey action allowFullScreen allowTransparency alt
9
+ async autoComplete autoPlay cellPadding cellSpacing charSet checked classID
10
+ className cols colSpan content contentEditable contextMenu controls coords
11
+ crossOrigin data dateTime defer dir disabled download draggable encType form
12
+ formAction formEncType formMethod formNoValidate formTarget frameBorder height
13
+ hidden href hrefLang htmlFor httpEquiv icon id label lang list loop manifest
14
+ marginHeight marginWidth max maxLength media mediaGroup method min multiple
15
+ muted name noValidate open pattern placeholder poster preload radioGroup
16
+ readOnly rel required role rows rowSpan sandbox scope scrolling seamless
17
+ selected shape size sizes span spellCheck src srcDoc srcSet start step style
18
+ tabIndex target title type useMap value width wmode dangerouslySetInnerHTML) +
19
+ #SVG ATTRIBUTES
20
+ %w(clipPath cx cy d dx dy fill fillOpacity fontFamily
21
+ fontSize fx fy gradientTransform gradientUnits markerEnd
22
+ markerMid markerStart offset opacity patternContentUnits
23
+ patternUnits points preserveAspectRatio r rx ry spreadMethod
24
+ stopColor stopOpacity stroke strokeDasharray strokeLinecap
25
+ strokeOpacity strokeWidth textAnchor transform version
26
+ viewBox x1 x2 x xlinkActuate xlinkArcrole xlinkHref xlinkRole
27
+ xlinkShow xlinkTitle xlinkType xmlBase xmlLang xmlSpace y1 y2 y)
28
+ HASH_ATTRIBUTES = %w(data aria)
29
+ HTML_TAGS = React::Component::Tags::HTML_TAGS
30
+
31
+ def self.html_tag?(name)
32
+ tags = HTML_TAGS
33
+ %x{
34
+ for(var i = 0; i < tags.length; i++) {
35
+ if(tags[i] === name)
36
+ return true;
37
+ }
38
+ return false;
39
+ }
40
+ end
41
+
42
+ def self.html_attr?(name)
43
+ attrs = ATTRIBUTES
44
+ %x{
45
+ for(var i = 0; i < attrs.length; i++) {
46
+ if(attrs[i] === name)
47
+ return true;
48
+ }
49
+ return false;
50
+ }
51
+ end
52
+
53
+ def self.create_element(type, properties = {}, &block)
54
+ React::API.create_element(type, properties, &block)
55
+ end
56
+
57
+ def self.render(element, container)
58
+ %x{
59
+ console.error(
60
+ "Warning: Using deprecated behavior of `React.render`,",
61
+ "require \"react/top_level_render\" to get the correct behavior."
62
+ );
63
+ }
64
+ container = `container.$$class ? container[0] : container`
65
+ if !(`typeof ReactDOM === 'undefined'`)
66
+ component = Native(`ReactDOM.render(#{element.to_n}, container, function(){#{yield if block_given?}})`) # v0.15+
67
+ else
68
+ raise "render is not defined. In React >= v15 you must import it with ReactDOM"
69
+ end
70
+
71
+ component.class.include(React::Component::API)
72
+ component
73
+ end
74
+
75
+ def self.is_valid_element(element)
76
+ %x{ console.error("Warning: `is_valid_element` is deprecated in favor of `is_valid_element?`."); }
77
+ element.kind_of?(React::Element) && `React.isValidElement(#{element.to_n})`
78
+ end
79
+
80
+ def self.is_valid_element?(element)
81
+ element.kind_of?(React::Element) && `React.isValidElement(#{element.to_n})`
82
+ end
83
+
84
+ def self.render_to_string(element)
85
+ %x{ console.error("Warning: `React.render_to_string` is deprecated in favor of `React::Server.render_to_string`."); }
86
+ if !(`typeof ReactDOMServer === 'undefined'`)
87
+ React::RenderingContext.build { `ReactDOMServer.renderToString(#{element.to_n})` } # v0.15+
88
+ else
89
+ raise "renderToString is not defined. In React >= v15 you must import it with ReactDOMServer"
90
+ end
91
+ end
92
+
93
+ def self.render_to_static_markup(element)
94
+ %x{ console.error("Warning: `React.render_to_static_markup` is deprecated in favor of `React::Server.render_to_static_markup`."); }
95
+ if !(`typeof ReactDOMServer === 'undefined'`)
96
+ React::RenderingContext.build { `ReactDOMServer.renderToStaticMarkup(#{element.to_n})` } # v0.15+
97
+ else
98
+ raise "renderToStaticMarkup is not defined. In React >= v15 you must import it with ReactDOMServer"
99
+ end
100
+ end
101
+
102
+ def self.unmount_component_at_node(node)
103
+ if !(`typeof ReactDOM === 'undefined'`)
104
+ `ReactDOM.unmountComponentAtNode(node.$$class ? node[0] : node)` # v0.15+
105
+ else
106
+ raise "unmountComponentAtNode is not defined. In React >= v15 you must import it with ReactDOM"
107
+ end
108
+ end
109
+
110
+ end
@@ -0,0 +1,28 @@
1
+ module React
2
+ def self.render(element, container)
3
+ raise "ReactDOM.render is not defined. In React >= v15 you must import it with ReactDOM" if (`typeof ReactDOM === 'undefined'`)
4
+
5
+ container = `container.$$class ? container[0] : container`
6
+
7
+ if block_given?
8
+ cb = %x{
9
+ function(){
10
+ setTimeout(function(){
11
+ #{yield}
12
+ }, 0)
13
+ }
14
+ }
15
+ native = `ReactDOM.render(#{element.to_n}, container, cb)`
16
+ else
17
+ native = `ReactDOM.render(#{element.to_n}, container)`
18
+ end
19
+
20
+ if `#{native}.__opalInstance !== undefined && #{native}.__opalInstance !== null`
21
+ `#{native}.__opalInstance`
22
+ elsif `ReactDOM.findDOMNode !== undefined && #{native}.nodeType === undefined`
23
+ `ReactDOM.findDOMNode(#{native})`
24
+ else
25
+ native
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,132 @@
1
+ module React
2
+ class Validator
3
+ attr_accessor :errors
4
+ attr_reader :props_wrapper
5
+ private :errors, :props_wrapper
6
+
7
+ def initialize(props_wrapper = Class.new(Component::PropsWrapper))
8
+ @props_wrapper = props_wrapper
9
+ end
10
+
11
+ def self.build(&block)
12
+ self.new.build(&block)
13
+ end
14
+
15
+ def build(&block)
16
+ instance_eval(&block)
17
+ self
18
+ end
19
+
20
+ def requires(name, options = {})
21
+ options[:required] = true
22
+ define_rule(name, options)
23
+ end
24
+
25
+ def optional(name, options = {})
26
+ options[:required] = false
27
+ define_rule(name, options)
28
+ end
29
+
30
+ def all_other_params(name)
31
+ @allow_undefined_props = true
32
+ props_wrapper.define_all_others(name) { |props| props.reject { |name, value| rules[name] } }
33
+ end
34
+
35
+ def validate(props)
36
+ self.errors = []
37
+ validate_undefined(props) unless allow_undefined_props?
38
+ props = coerce_native_hash_values(defined_props(props))
39
+ validate_required(props)
40
+ props.each do |name, value|
41
+ validate_types(name, value)
42
+ validate_allowed(name, value)
43
+ end
44
+ errors
45
+ end
46
+
47
+ def default_props
48
+ rules
49
+ .select {|key, value| value.keys.include?("default") }
50
+ .inject({}) {|memo, (k,v)| memo[k] = v[:default]; memo}
51
+ end
52
+
53
+ private
54
+
55
+ def defined_props(props)
56
+ props.select { |name| rules.keys.include?(name) }
57
+ end
58
+
59
+ def allow_undefined_props?
60
+ !!@allow_undefined_props
61
+ end
62
+
63
+ def rules
64
+ @rules ||= { children: { required: false } }
65
+ end
66
+
67
+ def define_rule(name, options = {})
68
+ rules[name] = coerce_native_hash_values(options)
69
+ props_wrapper.define_param(name, options[:type])
70
+ end
71
+
72
+ def errors
73
+ @errors ||= []
74
+ end
75
+
76
+ def validate_types(prop_name, value)
77
+ return unless klass = rules[prop_name][:type]
78
+ if !klass.is_a?(Array)
79
+ allow_nil = !!rules[prop_name][:allow_nil]
80
+ type_check("`#{prop_name}`", value, klass, allow_nil)
81
+ elsif klass.length > 0
82
+ validate_value_array(prop_name, value)
83
+ else
84
+ allow_nil = !!rules[prop_name][:allow_nil]
85
+ type_check("`#{prop_name}`", value, Array, allow_nil)
86
+ end
87
+ end
88
+
89
+ def type_check(prop_name, value, klass, allow_nil)
90
+ return if allow_nil && value.nil?
91
+ return if value.is_a?(klass)
92
+ return if klass.respond_to?(:_react_param_conversion) &&
93
+ klass._react_param_conversion(value, :validate_only)
94
+ errors << "Provided prop #{prop_name} could not be converted to #{klass}"
95
+ end
96
+
97
+ def validate_allowed(prop_name, value)
98
+ return unless values = rules[prop_name][:values]
99
+ return if values.include?(value)
100
+ errors << "Value `#{value}` for prop `#{prop_name}` is not an allowed value"
101
+ end
102
+
103
+ def validate_required(props)
104
+ (rules.keys - props.keys).each do |name|
105
+ next unless rules[name][:required]
106
+ errors << "Required prop `#{name}` was not specified"
107
+ end
108
+ end
109
+
110
+ def validate_undefined(props)
111
+ (props.keys - rules.keys).each do |prop_name|
112
+ errors << "Provided prop `#{prop_name}` not specified in spec"
113
+ end
114
+ end
115
+
116
+ def validate_value_array(name, value)
117
+ klass = rules[name][:type]
118
+ allow_nil = !!rules[name][:allow_nil]
119
+ value.each_with_index do |item, index|
120
+ type_check("`#{name}`[#{index}]", Native(item), klass[0], allow_nil)
121
+ end
122
+ rescue NoMethodError
123
+ errors << "Provided prop `#{name}` was not an Array"
124
+ end
125
+
126
+ def coerce_native_hash_values(hash)
127
+ hash.each do |key, value|
128
+ hash[key] = Native(value)
129
+ end
130
+ end
131
+ end
132
+ end