isomorfeus-react 16.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +1 -0
  3. data/README.md +620 -0
  4. data/isomorfeus-react.gemspec +23 -0
  5. data/lib/isomorfeus-react.rb +131 -0
  6. data/lib/isomorfeus/config.rb +84 -0
  7. data/lib/isomorfeus/top_level.rb +48 -0
  8. data/lib/isomorfeus/view_helpers.rb +38 -0
  9. data/lib/lucid_app/api.rb +22 -0
  10. data/lib/lucid_app/base.rb +7 -0
  11. data/lib/lucid_app/context.rb +7 -0
  12. data/lib/lucid_app/mixin.rb +17 -0
  13. data/lib/lucid_app/native_component_constructor.rb +70 -0
  14. data/lib/lucid_component/api.rb +97 -0
  15. data/lib/lucid_component/base.rb +7 -0
  16. data/lib/lucid_component/event_handler.rb +17 -0
  17. data/lib/lucid_component/initializer.rb +12 -0
  18. data/lib/lucid_component/mixin.rb +17 -0
  19. data/lib/lucid_component/native_component_constructor.rb +131 -0
  20. data/lib/react.rb +147 -0
  21. data/lib/react/active_support_support.rb +13 -0
  22. data/lib/react/component/api.rb +226 -0
  23. data/lib/react/component/base.rb +9 -0
  24. data/lib/react/component/elements.rb +78 -0
  25. data/lib/react/component/event_handler.rb +19 -0
  26. data/lib/react/component/features.rb +47 -0
  27. data/lib/react/component/history.rb +36 -0
  28. data/lib/react/component/initializer.rb +11 -0
  29. data/lib/react/component/location.rb +15 -0
  30. data/lib/react/component/match.rb +31 -0
  31. data/lib/react/component/mixin.rb +19 -0
  32. data/lib/react/component/native_component_constructor.rb +76 -0
  33. data/lib/react/component/native_component_validate_prop.rb +37 -0
  34. data/lib/react/component/props.rb +49 -0
  35. data/lib/react/component/resolution.rb +71 -0
  36. data/lib/react/component/should_component_update.rb +14 -0
  37. data/lib/react/component/state.rb +52 -0
  38. data/lib/react/component/unsafe_api.rb +33 -0
  39. data/lib/react/context_wrapper.rb +47 -0
  40. data/lib/react/function_component/creator.rb +47 -0
  41. data/lib/react/function_component/resolution.rb +61 -0
  42. data/lib/react/function_component/runner.rb +19 -0
  43. data/lib/react/native_constant_wrapper.rb +34 -0
  44. data/lib/react/pure_component/base.rb +9 -0
  45. data/lib/react/pure_component/mixin.rb +17 -0
  46. data/lib/react/redux_component/api.rb +132 -0
  47. data/lib/react/redux_component/app_store_defaults.rb +38 -0
  48. data/lib/react/redux_component/app_store_proxy.rb +46 -0
  49. data/lib/react/redux_component/base.rb +9 -0
  50. data/lib/react/redux_component/class_store_proxy.rb +50 -0
  51. data/lib/react/redux_component/component_class_store_defaults.rb +40 -0
  52. data/lib/react/redux_component/component_instance_store_defaults.rb +41 -0
  53. data/lib/react/redux_component/initializer.rb +14 -0
  54. data/lib/react/redux_component/instance_store_proxy.rb +50 -0
  55. data/lib/react/redux_component/mixin.rb +18 -0
  56. data/lib/react/redux_component/native_component_constructor.rb +119 -0
  57. data/lib/react/redux_component/reducers.rb +53 -0
  58. data/lib/react/ref.rb +19 -0
  59. data/lib/react/synthetic_event.rb +53 -0
  60. data/lib/react/version.rb +3 -0
  61. data/lib/react_dom.rb +31 -0
  62. data/lib/react_dom_server.rb +17 -0
  63. metadata +167 -0
@@ -0,0 +1,97 @@
1
+ module LucidComponent
2
+ module API
3
+ def self.included(base)
4
+ base.instance_exec do
5
+ def app_store
6
+ @default_app_store_defined = true
7
+ @default_app_store ||= ::React::ReduxComponent::AppStoreDefaults.new(default_props)
8
+ end
9
+
10
+ def class_store
11
+ @default_class_store_defined = true
12
+ @default_class_store ||= ::React::ReduxComponent::ComponentClassStoreDefaults.new(default_props, self.to_s)
13
+ end
14
+
15
+ def store
16
+ @default_instance_store_defined = true
17
+ @default_class_store ||= ::React::ReduxComponent::ComponentInstanceStoreDefaults.new(default_props, self.to_s)
18
+ end
19
+
20
+ def prop(name, options = `null`)
21
+ name = `Opal.React.lower_camelize(name)`
22
+ if options
23
+ if options.has_key?(:default)
24
+ %x{
25
+ if (typeof self.lucid_react_component.defaultProps == "undefined") {
26
+ self.lucid_react_component.defaultProps = { isomorfeus_store: Opal.Hash.$new() };
27
+ }
28
+ self.lucid_react_component.defaultProps[name] = options.$fetch("default");
29
+ }
30
+ end
31
+ if options.has_key?(:class)
32
+ %x{
33
+ if (typeof self.lucid_react_component.propTypes == "undefined") {
34
+ self.lucid_react_component.propTypes = {};
35
+ self.lucid_react_component.propValidations = {};
36
+ self.lucid_react_component.propValidations[name] = {};
37
+ }
38
+ self.lucid_react_component.propTypes[name] = self.lucid_react_component.prototype.validateProp;
39
+ self.lucid_react_component.propValidations[name].ruby_class = options.$fetch("class");
40
+ }
41
+ elsif options.has_key?(:is_a)
42
+ %x{
43
+ if (typeof self.lucid_react_component.propTypes == "undefined") {
44
+ self.lucid_react_component.propTypes = {};
45
+ self.lucid_react_component.propValidations = {};
46
+ self.lucid_react_component.propValidations[name] = {};
47
+ }
48
+ self.lucid_react_component.propTypes[name] = self.lucid_react_component.prototype.validateProp;
49
+ self.lucid_react_component.propValidations[name].is_a = options.$fetch("is_a");
50
+ }
51
+ end
52
+ if options.has_key?(:required)
53
+ %x{
54
+ if (typeof self.lucid_react_component.propTypes == "undefined") {
55
+ self.lucid_react_component.propTypes = {};
56
+ self.lucid_react_component.propValidations = {};
57
+ self.lucid_react_component.propValidations[name] = {};
58
+ }
59
+ self.lucid_react_component.propTypes[name] = self.lucid_react_component.prototype.validateProp;
60
+ self.lucid_react_component.propValidations[name].required = options.$fetch("required");
61
+ }
62
+ elsif !options.has_key?(:default)
63
+ %x{
64
+ if (typeof self.lucid_react_component.propTypes == "undefined") {
65
+ self.lucid_react_component.propTypes = {};
66
+ self.lucid_react_component.propValidations = {};
67
+ }
68
+ self.lucid_react_component.propTypes[name] = self.lucid_react_component.prototype.validateProp;
69
+ self.lucid_react_component.propValidations[name].required = true;
70
+ }
71
+ end
72
+ else
73
+ %x{
74
+ if (typeof self.lucid_react_component.propTypes == "undefined") {
75
+ self.lucid_react_component.propTypes = {};
76
+ self.lucid_react_component.propValidations = {};
77
+ self.lucid_react_component.propValidations[name] = {};
78
+ }
79
+ self.lucid_react_component.propTypes[name] = self.lucid_react_component.prototype.validateProp;
80
+ self.lucid_react_component.propValidations[name].required = options.$fetch("required");
81
+ }
82
+ end
83
+ end
84
+
85
+ def default_props
86
+ return @default_props if @default_props
87
+ %x{
88
+ if (typeof self.lucid_react_component.defaultProps == "undefined") {
89
+ self.lucid_react_component.defaultProps = { isomorfeus_store: Opal.Hash.$new() };
90
+ }
91
+ }
92
+ @default_props = React::Component::Props.new(`self.lucid_react_component.defaultProps`)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,7 @@
1
+ module LucidComponent
2
+ class Base
3
+ def self.inherited(base)
4
+ base.include(::LucidComponent::Mixin)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ module LucidComponent
2
+ module EventHandler
3
+ def event_handlers
4
+ @event_handlers ||= []
5
+ end
6
+
7
+ def event_handler(name, &block)
8
+ event_handlers << name
9
+ %x{
10
+ self.lucid_react_component.prototype[name] = function(event, info) {
11
+ #{ruby_event = ::React::SyntheticEvent.new(`event`)};
12
+ #{`this.__ruby_instance`.instance_exec(ruby_event, `info`, &block)};
13
+ }
14
+ }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ module LucidComponent
2
+ module Initializer
3
+ def initialize(native_component)
4
+ @native = native_component
5
+ @app_store = ::React::ReduxComponent::AppStoreProxy.new(self, 'props')
6
+ @class_store = ::React::ReduxComponent::ClassStoreProxy.new(self, 'props')
7
+ @props = ::React::Component::Props.new(@native.JS[:props])
8
+ @state = ::React::Component::State.new(@native)
9
+ @store = ::React::ReduxComponent::InstanceStoreProxy.new(self, 'props')
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ module LucidComponent
2
+ module Mixin
3
+ def self.included(base)
4
+ base.include(::Native::Wrapper)
5
+ base.extend(::LucidComponent::NativeComponentConstructor)
6
+ base.extend(::React::Component::NativeComponentValidateProp)
7
+ base.extend(::LucidComponent::EventHandler)
8
+ base.include(::React::Component::Elements)
9
+ base.include(::React::Component::API)
10
+ base.include(::React::ReduxComponent::API)
11
+ base.include(::LucidComponent::API)
12
+ base.include(::LucidComponent::Initializer)
13
+ base.include(::React::Component::Features)
14
+ base.include(::React::Component::Resolution)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,131 @@
1
+ module LucidComponent
2
+ module NativeComponentConstructor
3
+ # for should_component_update we apply ruby semantics for comparing props
4
+ # to do so, we convert the props to ruby hashes and then compare
5
+ # this makes sure, that for example rubys Nil object gets handled properly
6
+ def self.extended(base)
7
+ component_name = base.to_s
8
+ # language=JS
9
+ %x{
10
+ base.react_component = function(props) {
11
+ return React.createElement(LucidApplicationContext.Consumer, null, function(store) {
12
+ var store_props = Object.assign({}, props, { isomorfeus_store: store });
13
+ return React.createElement(base.lucid_react_component, store_props);
14
+ });
15
+ }
16
+ base.lucid_react_component = class extends React.Component {
17
+ constructor(props) {
18
+ super(props);
19
+ if (base.$default_state_defined()) {
20
+ this.state = base.$state().$to_n();
21
+ } else {
22
+ this.state = {};
23
+ };
24
+ this.__ruby_instance = base.$new(this);
25
+ this.__object_id = this.__ruby_instance.$object_id().$to_s();
26
+ if (!this.state.component_state) {
27
+ this.state.component_state = {};
28
+ this.state.component_state[this.__object_id] = {};
29
+ };
30
+ var event_handlers = #{base.event_handlers};
31
+ for (var i = 0; i < event_handlers.length; i++) {
32
+ this[event_handlers[i]] = this[event_handlers[i]].bind(this);
33
+ }
34
+ var defined_refs = #{base.defined_refs};
35
+ for (var ref in defined_refs) {
36
+ if (defined_refs[ref] != null) {
37
+ this[ref] = function(element) {
38
+ #{`this.__ruby_instance`.instance_exec(React::Ref.new(`element`), `defined_refs[ref]`)}
39
+ }
40
+ this[ref] = this[ref].bind(this);
41
+ } else {
42
+ this[ref] = React.createRef();
43
+ }
44
+ }
45
+ }
46
+ data_access() {
47
+ return this.props.isomorfeus_store
48
+ }
49
+ static get displayName() {
50
+ return #{component_name};
51
+ }
52
+ register_used_store_path(path) {
53
+ this.used_store_paths.push(path);
54
+ }
55
+ shouldComponentUpdate(next_props, next_state) {
56
+ var next_props_keys = Object.keys(next_props);
57
+ var this_props_keys = Object.keys(this.props);
58
+ if (next_props_keys.length !== this_props_keys.length) { return true; }
59
+
60
+ var next_state_keys = Object.keys(next_state);
61
+ var this_state_keys = Object.keys(this.state);
62
+ if (next_state_keys.length !== this_state_keys.length) { return true; }
63
+
64
+ for (var property in next_props) {
65
+ if (property === "isomorfeus_store") {
66
+ var res = this.scu_for_used_store_paths(this, this.state.isomorfeus_store, next_state.isomorfeus_store);
67
+ if (res) { return true; }
68
+ }
69
+ if (next_props.hasOwnProperty(property)) {
70
+ if (!this.props.hasOwnProperty(property)) { return true; };
71
+ if (property == "children") { if (next_props.children !== this.props.children) { return true; }}
72
+ else if (typeof next_props[property] !== "undefined" && typeof next_props[property]['$!='] !== "undefined" && typeof this.props[property] !== "undefined" && typeof this.props[property]['$!='] !== "undefined") {
73
+ if (#{ !! (`next_props[property]` != `this.props[property]`) }) { return true; };
74
+ } else if (next_props[property] !== this.props[property]) { return true; };
75
+ }
76
+ }
77
+ for (var property in next_state) {
78
+ if (next_state.hasOwnProperty(property)) {
79
+ if (!this.state.hasOwnProperty(property)) { return true; };
80
+ if (typeof next_state[property]['$!='] !== "undefined" && typeof this.state[property]['$!='] !== "undefined") {
81
+ if (#{ !! (`next_state[property]` != `this.state[property]`) }) { return true };
82
+ } else if (next_state[property] !== this.state[property]) { return true };
83
+ }
84
+ }
85
+ return false;
86
+ }
87
+ scu_for_used_store_paths(self, current_state, next_state) {
88
+ var unique_used_store_paths = self.used_store_paths.filter(function(elem, pos) {
89
+ return (self.used_store_paths.indexOf(elem) === pos);
90
+ });
91
+ var used_length = unique_used_store_paths.length;
92
+ var store_path;
93
+ var current_value;
94
+ var next_value;
95
+ for (var i = 0; i < used_length; i++) {
96
+ store_path = unique_used_store_paths[i];
97
+ current_value = store_path.reduce(function(prev, curr) { return prev && prev[curr]; }, current_state);
98
+ next_value = store_path.reduce(function(prev, curr) { return prev && prev[curr]; }, next_state);
99
+ if (current_value !== next_value) { return true; };
100
+ }
101
+ return false;
102
+ }
103
+ validateProp(props, propName, componentName) {
104
+ if (propName === "isomorfeus_store") { return null };
105
+ var prop_data = base.lucid_react_component.propValidations[propName];
106
+ if (!prop_data) { return true; };
107
+ var value = props[propName];
108
+ var result;
109
+ if (typeof prop_data.ruby_class != "undefined") {
110
+ result = (value.$class() == prop_data.ruby_class);
111
+ if (!result) {
112
+ return new Error('Invalid prop ' + propName + '! Expected ' + prop_data.ruby_class.$to_s() + ' but was ' + value.$class().$to_s() + '!');
113
+ }
114
+ } else if (typeof prop_data.is_a != "undefined") {
115
+ result = value["$is_a?"](prop_data.is_a);
116
+ if (!result) {
117
+ return new Error('Invalid prop ' + propName + '! Expected a child of ' + prop_data.is_a.$to_s() + '!');
118
+ }
119
+ }
120
+ if (typeof prop_data.required != "undefined") {
121
+ if (prop_data.required && (typeof props[propName] == "undefined")) {
122
+ return new Error('Prop ' + propName + ' is required but not given!');
123
+ }
124
+ }
125
+ return null;
126
+ }
127
+ }
128
+ }
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,147 @@
1
+ module React
2
+ # to_native_react_props: the native_component params is used for event handlers, it keeps the event handlers
3
+ # it does not need to be component, can be a object with the event handlers
4
+ # language=JS
5
+ %x{
6
+ self.render_buffer = [];
7
+
8
+ self.lower_camelize = function(snake_cased_word) {
9
+ var parts = snake_cased_word.split('_');
10
+ var res = parts[0];
11
+
12
+ for (var i = 1; i < parts.length; i++) {
13
+ res += parts[i][0].toUpperCase() + parts[i].slice(1);
14
+ }
15
+ return res;
16
+ }
17
+
18
+ self.to_native_react_props = function(ruby_style_props) {
19
+ var result = {};
20
+ var keys = ruby_style_props.$keys();
21
+ var keys_length = keys.length;
22
+ for (var i = 0; i < keys_length; i++) {
23
+ if (keys[i].startsWith("on_")) {
24
+ var handler = ruby_style_props['$[]'](keys[i]);
25
+ if (typeof handler === "function") {
26
+ result[Opal.React.lower_camelize(keys[i])] = handler;
27
+ } else {
28
+ var active_component = Opal.React.active_component();
29
+ result[Opal.React.lower_camelize(keys[i])] = active_component[handler];
30
+ }
31
+ } else if (keys[i].startsWith("aria_")) {
32
+ result[keys[i].replace("_", "-")] = ruby_style_props['$[]'](keys[i]);
33
+ } else {
34
+ result[Opal.React.lower_camelize(keys[i])] = ruby_style_props['$[]'](keys[i]);
35
+ }
36
+ }
37
+ return result;
38
+ }
39
+
40
+ self.internal_render = function(component, props, block) {
41
+ var children;
42
+ var block_result;
43
+ var react_element;
44
+
45
+ if (block !== nil) {
46
+ Opal.React.render_buffer.push([]);
47
+ block_result = block.$call();
48
+ if (block_result && (block_result !== nil && (typeof block_result === "string" || typeof block_result.$$typeof === "symbol" ||
49
+ (typeof block_result.constructor !== "undefined" && block_result.constructor === Array && block_result[0] && typeof block_result[0].$$typeof === "symbol")
50
+ ))) {
51
+ Opal.React.render_buffer[Opal.React.render_buffer.length - 1].push(block_result);
52
+ }
53
+ children = Opal.React.render_buffer.pop();
54
+ if (children.length == 1) { children = children[0]; }
55
+ else if (children.length == 0) { children = null; }
56
+ }
57
+ react_element = React.createElement(component, props, children);
58
+ Opal.React.render_buffer[Opal.React.render_buffer.length - 1].push(react_element);
59
+ };
60
+
61
+ self.active_components = [];
62
+
63
+ self.active_component = function() {
64
+ var length = Opal.React.active_components.length;
65
+ if (length === 0) { return null; };
66
+ return Opal.React.active_components[length-1];
67
+ };
68
+
69
+ self.active_redux_components = [];
70
+
71
+ self.active_redux_component = function() {
72
+ var length = Opal.React.active_redux_components.length;
73
+ if (length === 0) { return null; };
74
+ return Opal.React.active_redux_components[length-1];
75
+ };
76
+ }
77
+
78
+ def self.clone_element(ruby_react_element, props = nil, children = nil, &block)
79
+ block_result = `null`
80
+ if block_given?
81
+ block_result = block.call
82
+ block_result = `null` unless block_result
83
+ end
84
+ native_props = props ? `Opal.React.to_native_react_props(props)` : `null`
85
+ `React.cloneElement(ruby_react_element.$to_n(), native_props, block_result)`
86
+ end
87
+
88
+ def self.create_context(const_name, default_value)
89
+ %x{
90
+ Opal.global[const_name] = React.createContext(default_value);
91
+ var new_const = #{React::ContextWrapper.new(`Opal.global[const_name]`)};
92
+ #{Object.const_set(const_name, `new_const`)};
93
+ return new_const;
94
+ }
95
+ end
96
+
97
+ def self.create_element(type, props = nil, children = nil, &block)
98
+ %x{
99
+ var component = null;
100
+ var block_result = null;
101
+ var native_props = null;
102
+
103
+ if (typeof type.react_component == "function") {
104
+ component = type.react_component;
105
+ }
106
+ else {
107
+ component = type;
108
+ }
109
+
110
+ Opal.React.render_buffer.push([]);
111
+ #{
112
+ native_props = `Opal.React.to_native_react_props(props)` if props;
113
+ }
114
+ if (block !== nil) {
115
+ block_result = block.$call()
116
+ if (block_result && (block_result !== nil && (typeof block_result === "string" || typeof block_result.$$typeof === "symbol" ||
117
+ (typeof block_result.constructor !== "undefined" && block_result.constructor === Array && block_result[0] && typeof block_result[0].$$typeof === "symbol")
118
+ ))) {
119
+ Opal.React.render_buffer[Opal.React.render_buffer.length - 1].push(block_result);
120
+ }
121
+ children = Opal.React.render_buffer.pop()
122
+ if (children.length == 1) { children = children[0]; }
123
+ else if (children.length == 0) { children = null; }
124
+ }
125
+ return React.createElement(component, native_props, children);
126
+ }
127
+ end
128
+
129
+ def self.create_factory(type)
130
+ native_function = `React.createFactory(type)`
131
+ proc { `native_function.call()` }
132
+ end
133
+
134
+
135
+ def self.create_ref
136
+ React::Ref.new(`React.createRef()`)
137
+ end
138
+
139
+ def self.forwardRef(&block)
140
+ # TODO whats the return here? A React:Element?, doc says a React node, whats that?
141
+ `React.forwardRef( function(props, ref) { return block.$call().$to_n(); })`
142
+ end
143
+
144
+ def self.isValidElement(react_element)
145
+ `React.isValidElement(react_element)`
146
+ end
147
+ end
@@ -0,0 +1,13 @@
1
+ class String
2
+ def demodulize
3
+ if i = self.rindex("::")
4
+ self[(i + 2)..-1]
5
+ else
6
+ self
7
+ end
8
+ end
9
+
10
+ def deconstantize
11
+ self[0, self.rindex("::") || 0] # implementation based on the one in facets' Module#spacename
12
+ end
13
+ end