hyper-react 0.99.6 → 1.0.0.lap21
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.codeclimate.yml +27 -0
- data/.gitignore +30 -37
- data/.rubocop.yml +1159 -0
- data/.travis.yml +32 -0
- data/Appraisals +31 -0
- data/CHANGELOG.md +143 -0
- data/DOCS.md +1515 -0
- data/Gemfile +2 -5
- data/LICENSE +19 -0
- data/README.md +5 -33
- data/Rakefile +25 -6
- data/UPGRADING.md +24 -0
- data/component-name-lookup.md +145 -0
- data/dciy.toml +3 -0
- data/dciy_prepare.sh +8 -0
- data/dciy_run.sh +10 -0
- data/hyper-react.gemspec +24 -18
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +3 -0
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/server_rendering.js +5 -0
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +2 -0
- data/lib/generators/reactive_ruby/test_app/templates/boot.rb.erb +6 -0
- data/lib/generators/reactive_ruby/test_app/templates/script/rails +5 -0
- data/lib/generators/reactive_ruby/test_app/templates/test_application.rb.erb +13 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
- data/lib/generators/reactive_ruby/test_app/test_app_generator.rb +117 -0
- data/lib/hyper-react.rb +66 -4
- data/lib/rails-helpers/top_level_rails_component.rb +75 -0
- data/lib/react/api.rb +203 -0
- data/lib/react/callbacks.rb +41 -0
- data/lib/react/children.rb +30 -0
- data/lib/react/component.rb +177 -0
- data/lib/react/component/api.rb +69 -0
- data/lib/react/component/base.rb +13 -0
- data/lib/react/component/class_methods.rb +181 -0
- data/lib/react/component/dsl_instance_methods.rb +23 -0
- data/lib/react/component/params.rb +6 -0
- data/lib/react/component/props_wrapper.rb +78 -0
- data/lib/react/component/should_component_update.rb +99 -0
- data/lib/react/component/tags.rb +108 -0
- data/lib/react/config.rb +5 -0
- data/lib/react/config/client.rb.erb +19 -0
- data/lib/react/config/server.rb +23 -0
- data/lib/react/element.rb +150 -0
- data/lib/react/event.rb +76 -0
- data/lib/react/ext/hash.rb +9 -0
- data/lib/react/ext/opal-jquery/element.rb +26 -0
- data/lib/react/ext/string.rb +8 -0
- data/lib/react/hash.rb +13 -0
- data/lib/react/native_library.rb +87 -0
- data/lib/react/object.rb +15 -0
- data/lib/react/react-source-browser.rb +3 -0
- data/lib/react/react-source-server.rb +3 -0
- data/lib/react/react-source.rb +16 -0
- data/lib/react/ref_callback.rb +31 -0
- data/lib/react/rendering_context.rb +146 -0
- data/lib/react/server.rb +19 -0
- data/lib/react/state_wrapper.rb +23 -0
- data/lib/react/test.rb +16 -0
- data/lib/react/test/dsl.rb +17 -0
- data/lib/react/test/matchers/render_html_matcher.rb +56 -0
- data/lib/react/test/rspec.rb +15 -0
- data/lib/react/test/session.rb +37 -0
- data/lib/react/test/utils.rb +71 -0
- data/lib/react/top_level.rb +110 -0
- data/lib/react/top_level_render.rb +28 -0
- data/lib/react/validator.rb +136 -0
- data/lib/reactive-ruby/component_loader.rb +43 -0
- data/lib/reactive-ruby/isomorphic_helpers.rb +235 -0
- data/lib/reactive-ruby/rails.rb +8 -0
- data/lib/reactive-ruby/rails/component_mount.rb +48 -0
- data/lib/reactive-ruby/rails/controller_helper.rb +14 -0
- data/lib/reactive-ruby/rails/railtie.rb +20 -0
- data/lib/reactive-ruby/serializers.rb +15 -0
- data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +41 -0
- data/lib/reactive-ruby/server_rendering/hyper_asset_container.rb +46 -0
- data/lib/reactive-ruby/version.rb +3 -0
- data/lib/reactrb/auto-import.rb +27 -0
- data/logo1.png +0 -0
- data/logo2.png +0 -0
- data/logo3.png +0 -0
- data/path_release_steps.md +9 -0
- data/spec/controller_helper_spec.rb +35 -0
- data/spec/index.html.erb +11 -0
- data/spec/react/callbacks_spec.rb +142 -0
- data/spec/react/children_spec.rb +132 -0
- data/spec/react/component/base_spec.rb +36 -0
- data/spec/react/component_spec.rb +1073 -0
- data/spec/react/dsl_spec.rb +323 -0
- data/spec/react/element_spec.rb +132 -0
- data/spec/react/event_spec.rb +39 -0
- data/spec/react/native_library_spec.rb +387 -0
- data/spec/react/observable_spec.rb +31 -0
- data/spec/react/opal_jquery_extensions_spec.rb +68 -0
- data/spec/react/param_declaration_spec.rb +253 -0
- data/spec/react/react_spec.rb +278 -0
- data/spec/react/refs_callback_spec.rb +65 -0
- data/spec/react/server_spec.rb +25 -0
- data/spec/react/state_spec.rb +52 -0
- data/spec/react/test/dsl_spec.rb +43 -0
- data/spec/react/test/matchers/render_html_matcher_spec.rb +83 -0
- data/spec/react/test/rspec_spec.rb +62 -0
- data/spec/react/test/session_spec.rb +88 -0
- data/spec/react/test/utils_spec.rb +28 -0
- data/spec/react/top_level_component_spec.rb +103 -0
- data/spec/react/tutorial/tutorial_spec.rb +42 -0
- data/spec/react/validator_spec.rb +134 -0
- data/spec/reactive-ruby/component_loader_spec.rb +74 -0
- data/spec/reactive-ruby/isomorphic_helpers_spec.rb +157 -0
- data/spec/reactive-ruby/rails/asset_pipeline_spec.rb +17 -0
- data/spec/reactive-ruby/rails/component_mount_spec.rb +64 -0
- data/spec/reactive-ruby/server_rendering/contextual_renderer_spec.rb +39 -0
- data/spec/spec_helper.rb +55 -0
- data/spec/test_app/README.md +24 -0
- data/spec/test_app/Rakefile +6 -0
- data/spec/test_app/app/assets/config/manifest.js +3 -0
- data/spec/test_app/app/assets/images/.keep +0 -0
- data/spec/test_app/app/assets/javascripts/application.rb +7 -0
- data/spec/test_app/app/assets/javascripts/cable.js +13 -0
- data/spec/test_app/app/assets/javascripts/channels/.keep +0 -0
- data/spec/test_app/app/assets/javascripts/server_rendering.js +5 -0
- data/spec/test_app/app/assets/stylesheets/application.css +15 -0
- data/spec/test_app/app/channels/application_cable/channel.rb +4 -0
- data/spec/test_app/app/channels/application_cable/connection.rb +4 -0
- data/spec/test_app/app/controllers/application_controller.rb +3 -0
- data/spec/test_app/app/controllers/concerns/.keep +0 -0
- data/spec/test_app/app/helpers/application_helper.rb +2 -0
- data/spec/test_app/app/jobs/application_job.rb +2 -0
- data/spec/test_app/app/mailers/application_mailer.rb +4 -0
- data/spec/test_app/app/models/application_record.rb +3 -0
- data/spec/test_app/app/models/concerns/.keep +0 -0
- data/spec/test_app/app/views/components.rb +11 -0
- data/spec/test_app/app/views/components/hello_world.rb +11 -0
- data/spec/test_app/app/views/components/todo.rb +14 -0
- data/spec/test_app/app/views/layouts/application.html.erb +14 -0
- data/spec/test_app/app/views/layouts/explicit_layout.html.erb +0 -0
- data/spec/test_app/app/views/layouts/mailer.html.erb +13 -0
- data/spec/test_app/app/views/layouts/mailer.text.erb +1 -0
- data/spec/test_app/app/views/layouts/test_layout.html.erb +0 -0
- data/spec/test_app/bin/bundle +3 -0
- data/spec/test_app/bin/rails +4 -0
- data/spec/test_app/bin/rake +4 -0
- data/spec/test_app/bin/setup +38 -0
- data/spec/test_app/bin/update +29 -0
- data/spec/test_app/bin/yarn +11 -0
- data/spec/test_app/config.ru +5 -0
- data/spec/test_app/config/application.rb +45 -0
- data/spec/test_app/config/boot.rb +6 -0
- data/spec/test_app/config/cable.yml +10 -0
- data/spec/test_app/config/database.yml +25 -0
- data/spec/test_app/config/environment.rb +5 -0
- data/spec/test_app/config/environments/development.rb +54 -0
- data/spec/test_app/config/environments/production.rb +91 -0
- data/spec/test_app/config/environments/test.rb +42 -0
- data/spec/test_app/config/initializers/application_controller_renderer.rb +8 -0
- data/spec/test_app/config/initializers/assets.rb +14 -0
- data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/test_app/config/initializers/cookies_serializer.rb +5 -0
- data/spec/test_app/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/test_app/config/initializers/inflections.rb +16 -0
- data/spec/test_app/config/initializers/mime_types.rb +4 -0
- data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/test_app/config/locales/en.yml +33 -0
- data/spec/test_app/config/puma.rb +56 -0
- data/spec/test_app/config/routes.rb +3 -0
- data/spec/test_app/config/secrets.yml +32 -0
- data/spec/test_app/config/spring.rb +6 -0
- data/spec/test_app/db/development.sqlite3 +0 -0
- data/spec/test_app/db/schema.rb +15 -0
- data/spec/test_app/db/seeds.rb +7 -0
- data/spec/test_app/db/test.sqlite3 +0 -0
- data/spec/test_app/lib/assets/.keep +0 -0
- data/spec/test_app/log/.keep +0 -0
- data/spec/test_app/package.json +5 -0
- data/spec/test_app/public/404.html +67 -0
- data/spec/test_app/public/422.html +67 -0
- data/spec/test_app/public/500.html +66 -0
- data/spec/test_app/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/test_app/public/apple-touch-icon.png +0 -0
- data/spec/test_app/public/favicon.ico +0 -0
- data/spec/vendor/es5-shim.min.js +7 -0
- data/spec/vendor/jquery-2.2.4.min.js +4 -0
- metadata +401 -61
- data/CODE_OF_CONDUCT.md +0 -49
- data/lib/react/version.rb +0 -3
data/lib/react/server.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module React
|
2
|
+
module Server
|
3
|
+
def self.render_to_string(element)
|
4
|
+
if !(`typeof ReactDOMServer === 'undefined'`)
|
5
|
+
React::RenderingContext.build { `ReactDOMServer.renderToString(#{element.to_n})` } # v0.15+
|
6
|
+
else
|
7
|
+
raise "renderToString is not defined. In React >= v15 you must import it with ReactDOMServer"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.render_to_static_markup(element)
|
12
|
+
if !(`typeof ReactDOMServer === 'undefined'`)
|
13
|
+
React::RenderingContext.build { `ReactDOMServer.renderToStaticMarkup(#{element.to_n})` } # v0.15+
|
14
|
+
else
|
15
|
+
raise "renderToStaticMarkup is not defined. In React >= v15 you must import it with ReactDOMServer"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -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
|
data/lib/react/test.rb
ADDED
@@ -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.is_a? React::Component
|
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.is_a? React::Component
|
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.is_a? React::Component
|
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,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,136 @@
|
|
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 allow_undefined_props=(allow)
|
31
|
+
@allow_undefined_props = allow
|
32
|
+
end
|
33
|
+
|
34
|
+
def undefined_props(props)
|
35
|
+
self.allow_undefined_props = true
|
36
|
+
props.reject { |name, value| rules[name] }
|
37
|
+
end
|
38
|
+
|
39
|
+
def validate(props)
|
40
|
+
self.errors = []
|
41
|
+
validate_undefined(props) unless allow_undefined_props?
|
42
|
+
props = coerce_native_hash_values(defined_props(props))
|
43
|
+
validate_required(props)
|
44
|
+
props.each do |name, value|
|
45
|
+
validate_types(name, value)
|
46
|
+
validate_allowed(name, value)
|
47
|
+
end
|
48
|
+
errors
|
49
|
+
end
|
50
|
+
|
51
|
+
def default_props
|
52
|
+
rules
|
53
|
+
.select {|key, value| value.keys.include?("default") }
|
54
|
+
.inject({}) {|memo, (k,v)| memo[k] = v[:default]; memo}
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def defined_props(props)
|
60
|
+
props.select { |name| rules.keys.include?(name) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def allow_undefined_props?
|
64
|
+
!!@allow_undefined_props
|
65
|
+
end
|
66
|
+
|
67
|
+
def rules
|
68
|
+
@rules ||= { children: { required: false } }
|
69
|
+
end
|
70
|
+
|
71
|
+
def define_rule(name, options = {})
|
72
|
+
rules[name] = coerce_native_hash_values(options)
|
73
|
+
props_wrapper.define_param(name, options[:type])
|
74
|
+
end
|
75
|
+
|
76
|
+
def errors
|
77
|
+
@errors ||= []
|
78
|
+
end
|
79
|
+
|
80
|
+
def validate_types(prop_name, value)
|
81
|
+
return unless klass = rules[prop_name][:type]
|
82
|
+
if !klass.is_a?(Array)
|
83
|
+
allow_nil = !!rules[prop_name][:allow_nil]
|
84
|
+
type_check("`#{prop_name}`", value, klass, allow_nil)
|
85
|
+
elsif klass.length > 0
|
86
|
+
validate_value_array(prop_name, value)
|
87
|
+
else
|
88
|
+
allow_nil = !!rules[prop_name][:allow_nil]
|
89
|
+
type_check("`#{prop_name}`", value, Array, allow_nil)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def type_check(prop_name, value, klass, allow_nil)
|
94
|
+
return if allow_nil && value.nil?
|
95
|
+
return if value.is_a?(klass)
|
96
|
+
return if klass.respond_to?(:_react_param_conversion) &&
|
97
|
+
klass._react_param_conversion(value, :validate_only)
|
98
|
+
errors << "Provided prop #{prop_name} could not be converted to #{klass}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def validate_allowed(prop_name, value)
|
102
|
+
return unless values = rules[prop_name][:values]
|
103
|
+
return if values.include?(value)
|
104
|
+
errors << "Value `#{value}` for prop `#{prop_name}` is not an allowed value"
|
105
|
+
end
|
106
|
+
|
107
|
+
def validate_required(props)
|
108
|
+
(rules.keys - props.keys).each do |name|
|
109
|
+
next unless rules[name][:required]
|
110
|
+
errors << "Required prop `#{name}` was not specified"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def validate_undefined(props)
|
115
|
+
(props.keys - rules.keys).each do |prop_name|
|
116
|
+
errors << "Provided prop `#{prop_name}` not specified in spec"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def validate_value_array(name, value)
|
121
|
+
klass = rules[name][:type]
|
122
|
+
allow_nil = !!rules[name][:allow_nil]
|
123
|
+
value.each_with_index do |item, index|
|
124
|
+
type_check("`#{name}`[#{index}]", Native(item), klass[0], allow_nil)
|
125
|
+
end
|
126
|
+
rescue NoMethodError
|
127
|
+
errors << "Provided prop `#{name}` was not an Array"
|
128
|
+
end
|
129
|
+
|
130
|
+
def coerce_native_hash_values(hash)
|
131
|
+
hash.each do |key, value|
|
132
|
+
hash[key] = Native(value)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|