isomorfeus-react 16.5.1

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 (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