hyper-component 0.99.6 → 1.0.alpha1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -3
- data/Gemfile +4 -3
- data/Gemfile.lock +51 -36
- data/{misc/how-component-name-lookup-works.md → how-component-name-lookup-works.md} +1 -1
- data/hyper-component.gemspec +9 -8
- data/lib/hyper-component.rb +31 -43
- data/lib/hyperstack/component.rb +145 -0
- data/lib/hyperstack/component/auto-import.rb +44 -0
- data/lib/hyperstack/component/children.rb +40 -0
- data/lib/hyperstack/component/element.rb +129 -0
- data/lib/hyperstack/component/event.rb +78 -0
- data/lib/hyperstack/component/haml.rb +18 -0
- data/lib/hyperstack/component/isomorphic_helpers.rb +235 -0
- data/lib/hyperstack/component/jquery.rb +2 -0
- data/lib/hyperstack/component/native_library.rb +92 -0
- data/lib/hyperstack/component/react_api.rb +142 -0
- data/lib/hyperstack/component/server.rb +21 -0
- data/lib/hyperstack/component/version.rb +5 -0
- data/lib/hyperstack/ext/component/boolean.rb +14 -0
- data/lib/{react/ext/opal-jquery → hyperstack/ext/component}/element.rb +17 -12
- data/lib/{react/ext → hyperstack/ext/component}/hash.rb +0 -0
- data/lib/{react/to_key.rb → hyperstack/ext/component/number.rb} +0 -12
- data/lib/hyperstack/ext/component/object.rb +32 -0
- data/lib/{reactive-ruby → hyperstack/ext/component}/serializers.rb +0 -0
- data/lib/{react/ext → hyperstack/ext/component}/string.rb +0 -0
- data/lib/hyperstack/internal/component.rb +16 -0
- data/lib/hyperstack/internal/component/class_methods.rb +212 -0
- data/lib/hyperstack/internal/component/haml.rb +56 -0
- data/lib/hyperstack/internal/component/instance_methods.rb +92 -0
- data/lib/hyperstack/internal/component/props_wrapper.rb +125 -0
- data/lib/hyperstack/internal/component/rails.rb +11 -0
- data/lib/hyperstack/internal/component/rails/component_loader.rb +49 -0
- data/lib/hyperstack/internal/component/rails/component_mount.rb +52 -0
- data/lib/{reactive-ruby → hyperstack/internal/component}/rails/controller_helper.rb +0 -0
- data/lib/hyperstack/internal/component/rails/railtie.rb +24 -0
- data/lib/hyperstack/internal/component/rails/server_rendering/contextual_renderer.rb +52 -0
- data/lib/hyperstack/internal/component/rails/server_rendering/hyper_asset_container.rb +52 -0
- data/lib/hyperstack/internal/component/react_wrapper.rb +308 -0
- data/lib/hyperstack/internal/component/rendering_context.rb +165 -0
- data/lib/hyperstack/internal/component/should_component_update.rb +101 -0
- data/lib/hyperstack/internal/component/tags.rb +109 -0
- data/lib/hyperstack/internal/component/top_level_rails_component.rb +83 -0
- data/lib/hyperstack/internal/component/validator.rb +149 -0
- data/lib/react/react-source.rb +2 -2
- data/unmounting-objects.md +78 -0
- metadata +73 -85
- data/DOCS.md +0 -1515
- data/LICENSE +0 -19
- data/README.md +0 -49
- data/lib/hyper-component/jquery.rb +0 -2
- data/lib/rails-helpers/top_level_rails_component.rb +0 -79
- data/lib/react/api.rb +0 -272
- data/lib/react/callbacks.rb +0 -42
- data/lib/react/children.rb +0 -38
- data/lib/react/component.rb +0 -189
- data/lib/react/component/api.rb +0 -70
- data/lib/react/component/base.rb +0 -13
- data/lib/react/component/class_methods.rb +0 -175
- data/lib/react/component/dsl_instance_methods.rb +0 -23
- data/lib/react/component/params.rb +0 -6
- data/lib/react/component/props_wrapper.rb +0 -90
- data/lib/react/component/should_component_update.rb +0 -99
- data/lib/react/component/tags.rb +0 -116
- data/lib/react/config.rb +0 -5
- data/lib/react/element.rb +0 -167
- data/lib/react/event.rb +0 -76
- data/lib/react/native_library.rb +0 -87
- data/lib/react/object.rb +0 -15
- data/lib/react/ref_callback.rb +0 -31
- data/lib/react/rendering_context.rb +0 -149
- data/lib/react/server.rb +0 -19
- data/lib/react/state_wrapper.rb +0 -23
- data/lib/react/test.rb +0 -16
- data/lib/react/test/dsl.rb +0 -17
- data/lib/react/test/matchers/render_html_matcher.rb +0 -56
- data/lib/react/test/rspec.rb +0 -15
- data/lib/react/test/session.rb +0 -37
- data/lib/react/test/utils.rb +0 -71
- data/lib/react/top_level.rb +0 -110
- data/lib/react/top_level_render.rb +0 -30
- data/lib/react/validator.rb +0 -132
- data/lib/reactive-ruby/component_loader.rb +0 -43
- data/lib/reactive-ruby/isomorphic_helpers.rb +0 -233
- data/lib/reactive-ruby/rails.rb +0 -8
- data/lib/reactive-ruby/rails/component_mount.rb +0 -48
- data/lib/reactive-ruby/rails/railtie.rb +0 -20
- data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +0 -46
- data/lib/reactive-ruby/server_rendering/hyper_asset_container.rb +0 -46
- data/lib/reactive-ruby/version.rb +0 -5
- data/lib/reactrb/auto-import.rb +0 -27
- data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +0 -3
- data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/server_rendering.js +0 -5
- data/misc/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +0 -2
- data/misc/generators/reactive_ruby/test_app/templates/boot.rb.erb +0 -6
- data/misc/generators/reactive_ruby/test_app/templates/script/rails +0 -5
- data/misc/generators/reactive_ruby/test_app/templates/test_application.rb.erb +0 -13
- data/misc/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +0 -11
- data/misc/generators/reactive_ruby/test_app/templates/views/components/todo.rb +0 -14
- data/misc/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
- data/misc/generators/reactive_ruby/test_app/test_app_generator.rb +0 -121
- data/misc/hyperloop-logo-small-pink.png +0 -0
- data/misc/logo1.png +0 -0
- data/misc/logo2.png +0 -0
- data/misc/logo3.png +0 -0
- data/path_release_steps.md +0 -9
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
module Hyperstack
|
|
2
|
+
module Internal
|
|
3
|
+
module Component
|
|
4
|
+
#
|
|
5
|
+
# React assumes all components should update, unless a component explicitly overrides
|
|
6
|
+
# the shouldComponentUpdate method. Reactrb does an explicit check doing a shallow
|
|
7
|
+
# compare of params, and using a timestamp to determine if state has changed.
|
|
8
|
+
|
|
9
|
+
# If needed components can provide their own #needs_update? method which will be
|
|
10
|
+
# passed the next params and state opal hashes.
|
|
11
|
+
|
|
12
|
+
# Attached to these hashes is a #changed? method that returns whether the hash contains
|
|
13
|
+
# changes as calculated by the base mechanism. This way implementations of #needs_update?
|
|
14
|
+
# can use the base comparison mechanism as needed.
|
|
15
|
+
|
|
16
|
+
# For example
|
|
17
|
+
# def needs_update?(next_params, next_state)
|
|
18
|
+
# # use a special comparison method
|
|
19
|
+
# return false if next_state.changed? || next_params.changed?
|
|
20
|
+
# # do some other special checks
|
|
21
|
+
# end
|
|
22
|
+
|
|
23
|
+
# Note that beginning in 0.9 we will use standard ruby compare on all params further reducing
|
|
24
|
+
# the need for needs_update?
|
|
25
|
+
#
|
|
26
|
+
module ShouldComponentUpdate
|
|
27
|
+
def should_component_update?(next_props, next_state)
|
|
28
|
+
observing do
|
|
29
|
+
# rubocop:disable Style/DoubleNegation # we must return true/false to js land
|
|
30
|
+
if respond_to?(:needs_update?)
|
|
31
|
+
!!call_needs_update(next_props, next_state)
|
|
32
|
+
else
|
|
33
|
+
(props_changed?(next_props) || native_state_changed?(next_state))
|
|
34
|
+
end
|
|
35
|
+
# rubocop:enable Style/DoubleNegation
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# create opal hashes for next params and state, and attach
|
|
40
|
+
# the changed? method to each hash
|
|
41
|
+
|
|
42
|
+
def call_needs_update(next_params, next_state)
|
|
43
|
+
component = self
|
|
44
|
+
next_params.define_singleton_method(:changed?) do
|
|
45
|
+
component.props_changed?(self)
|
|
46
|
+
end
|
|
47
|
+
next_state.define_singleton_method(:changed?) do
|
|
48
|
+
component.native_state_changed?(next_state)
|
|
49
|
+
end
|
|
50
|
+
needs_update?(next_params, next_state)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Whenever state changes, reactrb updates a timestamp on the state object.
|
|
54
|
+
# We can rapidly check for state changes comparing the incoming state time_stamp
|
|
55
|
+
# with the current time stamp.
|
|
56
|
+
|
|
57
|
+
# we receive a Opal Ruby Hash here, always, so the Hash is either empty or filled
|
|
58
|
+
# Hash is converted to native object
|
|
59
|
+
# if the Hash was empty, the Object has no keys
|
|
60
|
+
|
|
61
|
+
# Different versions of react treat empty state differently, so we first
|
|
62
|
+
# convert anything that looks like an empty state to "false" for consistency.
|
|
63
|
+
|
|
64
|
+
# Then we test if one state is empty and the other is not, then we return false.
|
|
65
|
+
# Then we test if both states are empty we return true.
|
|
66
|
+
# If either state does not have a time stamp then we have to assume a change.
|
|
67
|
+
# Otherwise we check time stamps
|
|
68
|
+
|
|
69
|
+
# rubocop:disable Metrics/MethodLength # for effeciency we want this to be one method
|
|
70
|
+
def native_state_changed?(next_state_hash)
|
|
71
|
+
# next_state = next_state_hash.to_n
|
|
72
|
+
# %x{
|
|
73
|
+
# var current_state = #{@__hyperstack_component_native}.state
|
|
74
|
+
# var normalized_next_state =
|
|
75
|
+
# !next_state || Object.keys(next_state).length === 0 ? false : next_state
|
|
76
|
+
# var normalized_current_state =
|
|
77
|
+
# !current_state || Object.keys(current_state).length === 0 ? false : current_state
|
|
78
|
+
# if (!normalized_current_state != !normalized_next_state) return(true)
|
|
79
|
+
# if (!normalized_current_state && !normalized_next_state) return(false)
|
|
80
|
+
# if (!normalized_current_state['***_state_updated_at-***'] &&
|
|
81
|
+
# !normalized_next_state['***_state_updated_at-***']) return(false)
|
|
82
|
+
# if (!normalized_current_state['***_state_updated_at-***'] ||
|
|
83
|
+
# !normalized_next_state['***_state_updated_at-***']) return(true)
|
|
84
|
+
# return (normalized_current_state['***_state_updated_at-***'] !=
|
|
85
|
+
# normalized_next_state['***_state_updated_at-***'])
|
|
86
|
+
# }
|
|
87
|
+
state_hash = Hash.new(`#{@__hyperstack_component_native}.state`)
|
|
88
|
+
next_state_hash != state_hash
|
|
89
|
+
end
|
|
90
|
+
# rubocop:enable Metrics/MethodLength
|
|
91
|
+
|
|
92
|
+
# Do a shallow compare on the two hashes. Starting in 0.9 we will do a deep compare. ???
|
|
93
|
+
|
|
94
|
+
def props_changed?(next_props)
|
|
95
|
+
props = Hash.new(`#{@__hyperstack_component_native}.props`)
|
|
96
|
+
next_props != props
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
module Hyperstack
|
|
2
|
+
module Internal
|
|
3
|
+
module Component # contains the name of all HTML tags, and the mechanism to register a component
|
|
4
|
+
# class as a new tag
|
|
5
|
+
module Tags
|
|
6
|
+
HTML_TAGS = %w(a abbr address area article aside audio b base bdi bdo big blockquote body br
|
|
7
|
+
button canvas caption cite code col colgroup data datalist dd del details dfn
|
|
8
|
+
dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5
|
|
9
|
+
h6 head header hr html i iframe img input ins kbd keygen label legend li link
|
|
10
|
+
main map mark menu menuitem meta meter nav noscript object ol optgroup option
|
|
11
|
+
output p param picture pre progress q rp rt ruby s samp script section select
|
|
12
|
+
small source span strong style sub summary sup table tbody td textarea tfoot th
|
|
13
|
+
thead time title tr track u ul var video wbr) +
|
|
14
|
+
# The SVG Tags
|
|
15
|
+
%w(circle clipPath defs ellipse g line linearGradient mask path pattern polygon polyline
|
|
16
|
+
radialGradient rect stop svg text tspan)
|
|
17
|
+
|
|
18
|
+
# the present method is retained as a legacy behavior
|
|
19
|
+
# def present(component, *params, &children)
|
|
20
|
+
# RenderingContext.render(component, *params, &children)
|
|
21
|
+
# end
|
|
22
|
+
|
|
23
|
+
# define each predefined tag (upcase) as an instance method and a constant
|
|
24
|
+
# deprecated: define each predefined tag (downcase) as the alias of the instance method
|
|
25
|
+
|
|
26
|
+
HTML_TAGS.each do |tag|
|
|
27
|
+
|
|
28
|
+
define_method(tag.upcase) do |*params, &children|
|
|
29
|
+
RenderingContext.render(tag, *params, &children)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
const_set tag.upcase, tag
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# this is used for haml style (i.e. DIV.foo.bar) class tags which is deprecated
|
|
36
|
+
def self.html_tag_class_for(tag)
|
|
37
|
+
downcased_tag = tag.downcase
|
|
38
|
+
if tag =~ /^[A-Z]+$/ && HTML_TAGS.include?(downcased_tag)
|
|
39
|
+
Object.const_set tag, ReactWrapper.create_element(downcased_tag)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# use method_missing to look up component names in the form of "Foo(..)"
|
|
44
|
+
# where there is no preceeding scope.
|
|
45
|
+
|
|
46
|
+
def method_missing(name, *params, &children)
|
|
47
|
+
component = find_component(name)
|
|
48
|
+
return RenderingContext.render(component, *params, &children) if component
|
|
49
|
+
super
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# install methods with the same name as the component in the parent class/module
|
|
53
|
+
# thus component names in the form Foo::Bar(...) will work
|
|
54
|
+
|
|
55
|
+
class << self
|
|
56
|
+
def included(component)
|
|
57
|
+
name, parent = find_name_and_parent(component)
|
|
58
|
+
tag_names_module = Module.new do
|
|
59
|
+
define_method name do |*params, &children|
|
|
60
|
+
RenderingContext.render(component, *params, &children)
|
|
61
|
+
end
|
|
62
|
+
# handle deprecated _as_node style
|
|
63
|
+
define_method "#{name}_as_node" do |*params, &children|
|
|
64
|
+
RenderingContext.build_only(component, *params, &children)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
parent.extend(tag_names_module)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def find_name_and_parent(component)
|
|
73
|
+
split_name = component.name && component.name.split('::')
|
|
74
|
+
if split_name && split_name.length > 1
|
|
75
|
+
[split_name.last, split_name.inject([Module]) { |a, e| a + [a.last.const_get(e)] }[-2]]
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def find_component(name)
|
|
83
|
+
component = lookup_const(name)
|
|
84
|
+
if component && !component.method_defined?(:render)
|
|
85
|
+
raise "#{name} does not appear to be a react component."
|
|
86
|
+
end
|
|
87
|
+
component || Object._reactrb_import_component_class(name)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def lookup_const(name)
|
|
91
|
+
return nil unless name =~ /^[A-Z]/
|
|
92
|
+
scopes = self.class.name.to_s.split('::').inject([Module]) do |nesting, next_const|
|
|
93
|
+
nesting + [nesting.last.const_get(next_const)]
|
|
94
|
+
end.reverse
|
|
95
|
+
scope = scopes.detect { |s| s.const_defined?(name) }
|
|
96
|
+
scope.const_get(name) if scope
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
unless Object.respond_to? :_reactrb_import_component_class
|
|
104
|
+
class Object
|
|
105
|
+
def self._reactrb_import_component_class(_name)
|
|
106
|
+
nil
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module Hyperstack
|
|
2
|
+
module Internal
|
|
3
|
+
module Component
|
|
4
|
+
class TopLevelRailsComponent
|
|
5
|
+
include Hyperstack::Component
|
|
6
|
+
|
|
7
|
+
def self.search_path
|
|
8
|
+
@search_path ||= [Object]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
export_component
|
|
12
|
+
|
|
13
|
+
param :component_name
|
|
14
|
+
param :controller
|
|
15
|
+
param :render_params
|
|
16
|
+
|
|
17
|
+
backtrace :off
|
|
18
|
+
|
|
19
|
+
def render
|
|
20
|
+
top_level_render
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def top_level_render
|
|
24
|
+
paths_searched = []
|
|
25
|
+
component = nil
|
|
26
|
+
if @ComponentName.start_with?('::')
|
|
27
|
+
# if absolute path of component is given, look it up and fail if not found
|
|
28
|
+
paths_searched << @ComponentName
|
|
29
|
+
component = begin
|
|
30
|
+
Object.const_get(@ComponentName)
|
|
31
|
+
rescue NameError
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
# if relative path is given, look it up like this
|
|
36
|
+
# 1) we check each path + controller-name + component-name
|
|
37
|
+
# 2) if we can't find it there we check each path + component-name
|
|
38
|
+
# if we can't find it we just try const_get
|
|
39
|
+
# so (assuming controller name is Home)
|
|
40
|
+
# ::Foo::Bar will only resolve to some component named ::Foo::Bar
|
|
41
|
+
# but Foo::Bar will check (in this order) ::Home::Foo::Bar, ::Components::Home::Foo::Bar, ::Foo::Bar, ::Components::Foo::Bar
|
|
42
|
+
self.class.search_path.each do |scope|
|
|
43
|
+
paths_searched << "#{scope.name}::#{@Controller}::#{@ComponentName}"
|
|
44
|
+
component = begin
|
|
45
|
+
scope.const_get(@Controller, false).const_get(@ComponentName, false)
|
|
46
|
+
rescue NameError
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
break if component != nil
|
|
50
|
+
end
|
|
51
|
+
unless component
|
|
52
|
+
self.class.search_path.each do |scope|
|
|
53
|
+
paths_searched << "#{scope.name}::#{@ComponentName}"
|
|
54
|
+
component = begin
|
|
55
|
+
scope.const_get(@ComponentName, false)
|
|
56
|
+
rescue NameError
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
break if component != nil
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
return RenderingContext.render(component, @RenderParams) if component && component.method_defined?(:render)
|
|
64
|
+
raise "Could not find component class '#{@ComponentName}' for @Controller '#{@Controller}' in any component directory. Tried [#{paths_searched.join(", ")}]"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class Module
|
|
72
|
+
def add_to_react_search_path(replace_search_path = nil)
|
|
73
|
+
if replace_search_path
|
|
74
|
+
Hyperstack::Internal::Component::TopLevelRailsComponent.search_path = [self]
|
|
75
|
+
elsif !Hyperstack::Internal::Component::TopLevelRailsComponent.search_path.include? self
|
|
76
|
+
Hyperstack::Internal::Component::TopLevelRailsComponent.search_path << self
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
module Components
|
|
82
|
+
add_to_react_search_path
|
|
83
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
module Hyperstack
|
|
2
|
+
module Internal
|
|
3
|
+
module Component
|
|
4
|
+
class Validator
|
|
5
|
+
|
|
6
|
+
attr_accessor :errors
|
|
7
|
+
attr_reader :props_wrapper
|
|
8
|
+
private :errors, :props_wrapper
|
|
9
|
+
|
|
10
|
+
def copy(new_props_wrapper)
|
|
11
|
+
Validator.new(new_props_wrapper).tap do |c|
|
|
12
|
+
%i[@allow_undefined_props @rules @errors].each do |var|
|
|
13
|
+
c.instance_variable_set(var, instance_variable_get(var).dup)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(props_wrapper = Class.new(PropsWrapper))
|
|
19
|
+
@props_wrapper = props_wrapper
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.build(&block)
|
|
23
|
+
new.build(&block)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def build(&block)
|
|
27
|
+
instance_eval(&block)
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def requires(name, options = {})
|
|
32
|
+
options[:required] = true
|
|
33
|
+
define_rule(name, options)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def optional(name, options = {})
|
|
37
|
+
options[:required] = false
|
|
38
|
+
define_rule(name, options)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def event(name)
|
|
42
|
+
rules[name] = coerce_native_hash_values(default: nil, type: Proc, allow_nil: true)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def all_other_params(name)
|
|
46
|
+
@allow_undefined_props = true
|
|
47
|
+
props_wrapper.define_all_others(name) { |props| props.reject { |name, value| rules[name] } }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def validate(props)
|
|
51
|
+
self.errors = []
|
|
52
|
+
validate_undefined(props) unless allow_undefined_props?
|
|
53
|
+
props = coerce_native_hash_values(defined_props(props))
|
|
54
|
+
validate_required(props)
|
|
55
|
+
props.each do |name, value|
|
|
56
|
+
validate_types(name, value)
|
|
57
|
+
validate_allowed(name, value)
|
|
58
|
+
end
|
|
59
|
+
errors
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def default_props
|
|
63
|
+
rules
|
|
64
|
+
.select {|key, value| value.keys.include?("default") }
|
|
65
|
+
.inject({}) {|memo, (k,v)| memo[k] = v[:default]; memo}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def defined_props(props)
|
|
71
|
+
props.select { |name| rules.keys.include?(name) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def allow_undefined_props?
|
|
75
|
+
!!@allow_undefined_props
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def rules
|
|
79
|
+
@rules ||= { children: { required: false } }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def define_rule(name, options = {})
|
|
83
|
+
rules[name] = coerce_native_hash_values(options)
|
|
84
|
+
props_wrapper.define_param(name, options[:type], options[:alias])
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def errors
|
|
88
|
+
@errors ||= []
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def validate_types(prop_name, value)
|
|
92
|
+
return unless klass = rules[prop_name][:type]
|
|
93
|
+
if !klass.is_a?(Array)
|
|
94
|
+
allow_nil = !!rules[prop_name][:allow_nil]
|
|
95
|
+
type_check("`#{prop_name}`", value, klass, allow_nil)
|
|
96
|
+
elsif klass.length > 0
|
|
97
|
+
validate_value_array(prop_name, value)
|
|
98
|
+
else
|
|
99
|
+
allow_nil = !!rules[prop_name][:allow_nil]
|
|
100
|
+
type_check("`#{prop_name}`", value, Array, allow_nil)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def type_check(prop_name, value, klass, allow_nil)
|
|
105
|
+
return if allow_nil && value.nil?
|
|
106
|
+
return if value.is_a?(klass)
|
|
107
|
+
return if klass.respond_to?(:_react_param_conversion) &&
|
|
108
|
+
klass._react_param_conversion(value, :validate_only)
|
|
109
|
+
errors << "Provided prop #{prop_name} could not be converted to #{klass}"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def validate_allowed(prop_name, value)
|
|
113
|
+
return unless values = rules[prop_name][:values]
|
|
114
|
+
return if values.include?(value)
|
|
115
|
+
errors << "Value `#{value}` for prop `#{prop_name}` is not an allowed value"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def validate_required(props)
|
|
119
|
+
(rules.keys - props.keys).each do |name|
|
|
120
|
+
next unless rules[name][:required]
|
|
121
|
+
errors << "Required prop `#{name}` was not specified"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def validate_undefined(props)
|
|
126
|
+
(props.keys - rules.keys).each do |prop_name|
|
|
127
|
+
errors << "Provided prop `#{prop_name}` not specified in spec"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def validate_value_array(name, value)
|
|
132
|
+
klass = rules[name][:type]
|
|
133
|
+
allow_nil = !!rules[name][:allow_nil]
|
|
134
|
+
value.each_with_index do |item, index|
|
|
135
|
+
type_check("`#{name}`[#{index}]", Native(item), klass[0], allow_nil)
|
|
136
|
+
end
|
|
137
|
+
rescue NoMethodError
|
|
138
|
+
errors << "Provided prop `#{name}` was not an Array"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def coerce_native_hash_values(hash)
|
|
142
|
+
hash.each do |key, value|
|
|
143
|
+
hash[key] = Native(value)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
data/lib/react/react-source.rb
CHANGED
|
@@ -9,9 +9,9 @@ if RUBY_ENGINE == 'opal'
|
|
|
9
9
|
require 'react.js'
|
|
10
10
|
require "react-server.js"
|
|
11
11
|
else
|
|
12
|
-
require "
|
|
12
|
+
require "hyperstack/internal/component"
|
|
13
13
|
require "react/rails/asset_variant"
|
|
14
|
-
variant =
|
|
14
|
+
variant = Hyperstack.env.production? ? 'production' : 'development'
|
|
15
15
|
react_directory = React::Rails::AssetVariant.new({environment: variant}).react_directory
|
|
16
16
|
Opal.append_path react_directory.untaint
|
|
17
17
|
end
|