view_component_reflex 3.1.14.pre3 → 3.1.14.pre7

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.
data/Rakefile CHANGED
@@ -1,32 +1,32 @@
1
- begin
2
- require 'bundler/setup'
3
- rescue LoadError
4
- puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
- end
6
-
7
- require 'rdoc/task'
8
-
9
- RDoc::Task.new(:rdoc) do |rdoc|
10
- rdoc.rdoc_dir = 'rdoc'
11
- rdoc.title = 'ViewComponentReflex'
12
- rdoc.options << '--line-numbers'
13
- rdoc.rdoc_files.include('README.md')
14
- rdoc.rdoc_files.include('lib/**/*.rb')
15
- end
16
-
17
- APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
- load 'rails/tasks/engine.rake'
19
-
20
- load 'rails/tasks/statistics.rake'
21
-
22
- require 'bundler/gem_tasks'
23
-
24
- require 'rake/testtask'
25
-
26
- Rake::TestTask.new(:test) do |t|
27
- t.libs << 'test'
28
- t.pattern = 'test/**/*_test.rb'
29
- t.verbose = false
30
- end
31
-
32
- task default: :test
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'ViewComponentReflex'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
@@ -1,244 +1,256 @@
1
- module ViewComponentReflex
2
- class Component < ViewComponent::Base
3
- class_attribute :reflex_base_class, default: ViewComponentReflex::Reflex
4
- attr_reader :key
5
-
6
- class << self
7
- def init_stimulus_reflex
8
- factory = ViewComponentReflex::ReflexFactory.new(self)
9
- @stimulus_reflex ||= factory.reflex
10
- wire_up_callbacks if factory.new?
11
- end
12
-
13
- def queue_callback(key, args, blk)
14
- callbacks(key).push({
15
- args: args,
16
- blk: blk
17
- })
18
- end
19
-
20
- def callbacks(key)
21
- @callbacks ||= {}
22
- @callbacks[key] ||= []
23
- end
24
-
25
- def register_callbacks(key)
26
- callbacks(key).each do |cb|
27
- @stimulus_reflex.send("#{key}_reflex", *cb[:args], &cb[:blk])
28
- end
29
- end
30
-
31
- def before_reflex(*args, &blk)
32
- queue_callback(:before, args, blk)
33
- end
34
-
35
- def after_reflex(*args, &blk)
36
- queue_callback(:after, args, blk)
37
- end
38
-
39
- def around_reflex(*args, &blk)
40
- queue_callback(:around, args, blk)
41
- end
42
-
43
- def wire_up_callbacks
44
- register_callbacks(:before)
45
- register_callbacks(:after)
46
- register_callbacks(:around)
47
- end
48
- end
49
-
50
- def self.stimulus_controller
51
- name.chomp("Component").underscore.dasherize.gsub("/", "--")
52
- end
53
-
54
- def stimulus_reflex?
55
- helpers.controller.instance_variable_get(:@stimulus_reflex)
56
- end
57
-
58
- def component_controller(opts_or_tag = :div, opts = {}, &blk)
59
- initialize_component
60
-
61
- tag = :div
62
- options = if opts_or_tag.is_a? Hash
63
- opts_or_tag
64
- else
65
- tag = opts_or_tag
66
- opts
67
- end
68
-
69
- options[:data] = {
70
- controller: self.class.stimulus_controller,
71
- key: key,
72
- **(options[:data] || {})
73
- }
74
- content_tag tag, capture(&blk), options
75
- end
76
-
77
- def can_render_to_string?
78
- omitted_from_state.empty?
79
- end
80
-
81
- # We can't truly initialize the component without the view_context,
82
- # which isn't available in the `initialize` method. We require the
83
- # developer to wrap components in `component_controller`, so this is where
84
- # we truly initialize the component.
85
- # This method is overridden in reflex.rb when the component is re-rendered. The
86
- # override simply sets @key to element.dataset[:key]
87
- # We don't want it to initialize the state again, and since we're rendering the component
88
- # outside of the view, we need to skip the initialize_key method as well
89
- def initialize_component
90
- initialize_key
91
- initialize_state
92
- end
93
-
94
- # Note to self:
95
- # This has to be in the Component class because there are situations
96
- # where the controller is the one rendering the component
97
- # so we can't rely on the component created by the reflex
98
- def initialize_state
99
- return if state_initialized?
100
- adapter = ViewComponentReflex::Engine.state_adapter
101
-
102
- # newly mounted
103
- if !stimulus_reflex? || adapter.state(request, @key).empty?
104
-
105
- new_state = create_safe_state
106
-
107
- adapter.wrap_write_async do
108
- adapter.store_state(request, @key, new_state)
109
- adapter.store_state(request, "#{@key}_initial", new_state)
110
- end
111
-
112
- # updating a mounted component
113
- else
114
- initial_state = adapter.state(request, "#{@key}_initial")
115
-
116
- parameters_changed = []
117
- adapter.state(request, @key).each do |k, v|
118
- instance_value = instance_variable_get(k)
119
- if permit_parameter?(initial_state[k], instance_value)
120
- parameters_changed << k
121
- adapter.wrap_write_async do
122
- adapter.set_state(request, controller, "#{@key}_initial", {k => instance_value})
123
- adapter.set_state(request, controller, @key, {k => instance_value})
124
- end
125
- else
126
- instance_variable_set(k, v)
127
- end
128
- end
129
- after_state_initialized(parameters_changed)
130
- end
131
- @state_initialized = true
132
- end
133
-
134
- def state_initialized?
135
- @state_initialized
136
- end
137
-
138
- def initialize_key
139
- # we want the erb file that renders the component. `caller` gives the file name,
140
- # and line number, which should be unique. We hash it to make it a nice number
141
- erb_file = caller.select { |p| p.match? /.\.html\.(haml|erb|slim)/ }[1]
142
- key = if erb_file
143
- Digest::SHA2.hexdigest(erb_file.split(":in")[0])
144
- else
145
- ""
146
- end
147
- key += collection_key.to_s if collection_key
148
- @key = key
149
- end
150
-
151
- # Helper to use to create the proper reflex data attributes for an element
152
- def reflex_data_attributes(reflex)
153
- action, method = reflex.to_s.split("->")
154
- if method.nil?
155
- method = action
156
- action = "click"
157
- end
158
-
159
- {
160
- reflex: "#{action}->#{self.class.name}##{method}",
161
- key: key
162
- }
163
- end
164
-
165
- def reflex_tag(reflex, name, content_or_options_with_block = {}, options = {}, escape = true, &block)
166
- if content_or_options_with_block.is_a?(Hash)
167
- merge_data_attributes(content_or_options_with_block, reflex_data_attributes(reflex))
168
- else
169
- merge_data_attributes(options, reflex_data_attributes(reflex))
170
- end
171
- content_tag(name, content_or_options_with_block, options, escape, &block)
172
- end
173
-
174
- def collection_key
175
- nil
176
- end
177
-
178
- def permit_parameter?(initial_param, new_param)
179
- initial_param != new_param
180
- end
181
-
182
- def omitted_from_state
183
- []
184
- end
185
-
186
- def after_state_initialized(parameters_changed)
187
- # called after state component has been hydrated
188
- end
189
-
190
- # def receive_params(old_state, params)
191
- # # no op
192
- # end
193
-
194
- def safe_instance_variables
195
- instance_variables - unsafe_instance_variables - omitted_from_state
196
- end
197
-
198
- private
199
-
200
- def unsafe_instance_variables
201
- [
202
- :@view_context, :@lookup_context, :@view_renderer, :@view_flow,
203
- :@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
204
- :@helpers, :@controller, :@request, :@tag_builder, :@state_initialized,
205
- :@_content_evaluated, :@_render_in_block, :@__vc_controller, :@__vc_helpers,
206
- :@__cached_content, :@__vc_variant, :@__vc_content_evaluated, :@__vc_render_in_block,
207
- ]
208
- end
209
-
210
- def content
211
- if cached_content && !@_render_in_block
212
- cached_content
213
- else
214
- super
215
- end
216
- end
217
-
218
- def cached_content
219
- @__cached_content__
220
- end
221
-
222
- def create_safe_state
223
- new_state = {}
224
-
225
- # this will almost certainly break
226
- safe_instance_variables.each do |k|
227
- new_state[k] = instance_variable_get(k)
228
- end
229
-
230
- new_state[:@__cached_content__] = content
231
-
232
- new_state
233
- end
234
-
235
- def merge_data_attributes(options, attributes)
236
- data = options[:data]
237
- if data.nil?
238
- options[:data] = attributes
239
- else
240
- options[:data].merge! attributes
241
- end
242
- end
243
- end
244
- end
1
+ module ViewComponentReflex
2
+ class Component < ViewComponent::Base
3
+ class_attribute :reflex_base_class, default: ViewComponentReflex::Reflex
4
+ attr_reader :key
5
+
6
+ class << self
7
+ def init_stimulus_reflex
8
+ factory = ViewComponentReflex::ReflexFactory.new(self)
9
+ @stimulus_reflex ||= factory.reflex
10
+
11
+ # Always wire up new callbacks in development
12
+ if Rails.env.development?
13
+ reset_callbacks
14
+ wire_up_callbacks
15
+ elsif factory.new? # only wire up callbacks in production if they haven't been wired up yet
16
+ wire_up_callbacks
17
+ end
18
+ end
19
+
20
+ def queue_callback(key, args, blk)
21
+ callbacks(key).push({
22
+ args: args,
23
+ blk: blk
24
+ })
25
+ end
26
+
27
+ def callbacks(key)
28
+ @callbacks ||= {}
29
+ @callbacks[key] ||= []
30
+ end
31
+
32
+ def register_callbacks(key)
33
+ callbacks(key).each do |cb|
34
+ @stimulus_reflex.send("#{key}_reflex", *cb[:args], &cb[:blk])
35
+ end
36
+ end
37
+
38
+ def before_reflex(*args, &blk)
39
+ queue_callback(:before, args, blk)
40
+ end
41
+
42
+ def after_reflex(*args, &blk)
43
+ queue_callback(:after, args, blk)
44
+ end
45
+
46
+ def around_reflex(*args, &blk)
47
+ queue_callback(:around, args, blk)
48
+ end
49
+
50
+ def reset_callbacks
51
+ # SR uses :process as the underlying callback key
52
+ @stimulus_reflex.reset_callbacks(:process)
53
+ end
54
+
55
+ def wire_up_callbacks
56
+ register_callbacks(:before)
57
+ register_callbacks(:after)
58
+ register_callbacks(:around)
59
+ end
60
+ end
61
+
62
+ def self.stimulus_controller
63
+ name.chomp("Component").underscore.dasherize.gsub("/", "--")
64
+ end
65
+
66
+ def stimulus_reflex?
67
+ helpers.controller.instance_variable_get(:@stimulus_reflex)
68
+ end
69
+
70
+ def component_controller(opts_or_tag = :div, opts = {}, &blk)
71
+ initialize_component
72
+
73
+ tag = :div
74
+ options = if opts_or_tag.is_a? Hash
75
+ opts_or_tag
76
+ else
77
+ tag = opts_or_tag
78
+ opts
79
+ end
80
+
81
+ options[:data] = {
82
+ controller: self.class.stimulus_controller,
83
+ key: key,
84
+ **(options[:data] || {})
85
+ }
86
+ content_tag tag, capture(&blk), options
87
+ end
88
+
89
+ def can_render_to_string?
90
+ omitted_from_state.empty?
91
+ end
92
+
93
+ # We can't truly initialize the component without the view_context,
94
+ # which isn't available in the `initialize` method. We require the
95
+ # developer to wrap components in `component_controller`, so this is where
96
+ # we truly initialize the component.
97
+ # This method is overridden in reflex.rb when the component is re-rendered. The
98
+ # override simply sets @key to element.dataset[:key]
99
+ # We don't want it to initialize the state again, and since we're rendering the component
100
+ # outside of the view, we need to skip the initialize_key method as well
101
+ def initialize_component
102
+ initialize_key
103
+ initialize_state
104
+ end
105
+
106
+ # Note to self:
107
+ # This has to be in the Component class because there are situations
108
+ # where the controller is the one rendering the component
109
+ # so we can't rely on the component created by the reflex
110
+ def initialize_state
111
+ return if state_initialized?
112
+ adapter = ViewComponentReflex::Engine.state_adapter
113
+
114
+ # newly mounted
115
+ if !stimulus_reflex? || adapter.state(request, @key).empty?
116
+
117
+ new_state = create_safe_state
118
+
119
+ adapter.wrap_write_async do
120
+ adapter.store_state(request, @key, new_state)
121
+ adapter.store_state(request, "#{@key}_initial", new_state)
122
+ end
123
+
124
+ # updating a mounted component
125
+ else
126
+ initial_state = adapter.state(request, "#{@key}_initial")
127
+
128
+ parameters_changed = []
129
+ adapter.state(request, @key).each do |k, v|
130
+ instance_value = instance_variable_get(k)
131
+ if permit_parameter?(initial_state[k], instance_value)
132
+ parameters_changed << k
133
+ adapter.wrap_write_async do
134
+ adapter.set_state(request, controller, "#{@key}_initial", {k => instance_value})
135
+ adapter.set_state(request, controller, @key, {k => instance_value})
136
+ end
137
+ else
138
+ instance_variable_set(k, v)
139
+ end
140
+ end
141
+ after_state_initialized(parameters_changed)
142
+ end
143
+ @state_initialized = true
144
+ end
145
+
146
+ def state_initialized?
147
+ @state_initialized
148
+ end
149
+
150
+ def initialize_key
151
+ # we want the erb file that renders the component. `caller` gives the file name,
152
+ # and line number, which should be unique. We hash it to make it a nice number
153
+ erb_file = caller.select { |p| p.match? /.\.html\.(haml|erb|slim)/ }[1]
154
+ key = if erb_file
155
+ Digest::SHA2.hexdigest(erb_file.split(":in")[0])
156
+ else
157
+ ""
158
+ end
159
+ key += collection_key.to_s if collection_key
160
+ @key = key
161
+ end
162
+
163
+ # Helper to use to create the proper reflex data attributes for an element
164
+ def reflex_data_attributes(reflex)
165
+ action, method = reflex.to_s.split("->")
166
+ if method.nil?
167
+ method = action
168
+ action = "click"
169
+ end
170
+
171
+ {
172
+ reflex: "#{action}->#{self.class.name}##{method}",
173
+ key: key
174
+ }
175
+ end
176
+
177
+ def reflex_tag(reflex, name, content_or_options_with_block = {}, options = {}, escape = true, &block)
178
+ if content_or_options_with_block.is_a?(Hash)
179
+ merge_data_attributes(content_or_options_with_block, reflex_data_attributes(reflex))
180
+ else
181
+ merge_data_attributes(options, reflex_data_attributes(reflex))
182
+ end
183
+ content_tag(name, content_or_options_with_block, options, escape, &block)
184
+ end
185
+
186
+ def collection_key
187
+ nil
188
+ end
189
+
190
+ def permit_parameter?(initial_param, new_param)
191
+ initial_param != new_param
192
+ end
193
+
194
+ def omitted_from_state
195
+ []
196
+ end
197
+
198
+ def after_state_initialized(parameters_changed)
199
+ # called after state component has been hydrated
200
+ end
201
+
202
+ # def receive_params(old_state, params)
203
+ # # no op
204
+ # end
205
+
206
+ def safe_instance_variables
207
+ instance_variables.reject { |ivar| ivar.start_with?("@__vc") } - unsafe_instance_variables - omitted_from_state
208
+ end
209
+
210
+ private
211
+
212
+ def unsafe_instance_variables
213
+ [
214
+ :@view_context, :@lookup_context, :@view_renderer, :@view_flow,
215
+ :@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
216
+ :@helpers, :@controller, :@request, :@tag_builder, :@state_initialized,
217
+ :@_content_evaluated, :@_render_in_block, :@__cached_content,
218
+ :@original_view_context,
219
+ ]
220
+ end
221
+
222
+ def content
223
+ if cached_content && !@_render_in_block
224
+ cached_content
225
+ else
226
+ super
227
+ end
228
+ end
229
+
230
+ def cached_content
231
+ @__cached_content__
232
+ end
233
+
234
+ def create_safe_state
235
+ new_state = {}
236
+
237
+ # this will almost certainly break
238
+ safe_instance_variables.each do |k|
239
+ new_state[k] = instance_variable_get(k)
240
+ end
241
+
242
+ new_state[:@__cached_content__] = content
243
+
244
+ new_state
245
+ end
246
+
247
+ def merge_data_attributes(options, attributes)
248
+ data = options[:data]
249
+ if data.nil?
250
+ options[:data] = attributes
251
+ else
252
+ options[:data].merge! attributes
253
+ end
254
+ end
255
+ end
256
+ end
@@ -1,37 +1,40 @@
1
- module ViewComponentReflex
2
- class Engine < ::Rails::Engine
3
-
4
- mattr_accessor :state_adapter
5
- Engine.state_adapter = StateAdapter::Session
6
-
7
- config.to_prepare do
8
- StimulusReflex::Channel.class_eval do
9
- unless instance_methods.include?(:receive_original)
10
- alias_method :receive_original, :receive
11
- def receive(data)
12
- target = data["target"].to_s
13
- reflex_name, _ = target.split("#")
14
- reflex_name = reflex_name.camelize
15
- component_name = reflex_name.end_with?("Reflex") ? reflex_name[0...-6] : reflex_name
16
- component = begin
17
- component_name.constantize
18
- rescue
19
- # Since every reflex runs through this monkey patch, we're just going to ignore the ones that aren't for components
20
- end
21
-
22
- if component&.respond_to?(:init_stimulus_reflex)
23
- component.init_stimulus_reflex
24
- else
25
- Rails.logger.info "Tried to initialize view_component_reflex on #{component_name}, but it's not a view_component_reflex"
26
- end
27
- receive_original(data)
28
- end
29
- end
30
- end
31
- end
32
-
33
- def self.configure
34
- yield self if block_given?
35
- end
36
- end
37
- end
1
+ module ViewComponentReflex
2
+ class Engine < ::Rails::Engine
3
+
4
+ mattr_accessor :state_adapter
5
+ Engine.state_adapter = StateAdapter::Session
6
+
7
+ config.to_prepare do
8
+ StimulusReflex::Channel.class_eval do
9
+ unless instance_methods.include?(:receive_original)
10
+ alias_method :receive_original, :receive
11
+ def receive(data)
12
+ target = data["target"].to_s
13
+ reflex_name, _ = target.split("#")
14
+ reflex_name = reflex_name.camelize
15
+ component_name = reflex_name.end_with?("Reflex") ? reflex_name[0...-6] : reflex_name
16
+ component = begin
17
+ component_name.constantize
18
+ rescue
19
+ # Since every reflex runs through this monkey patch, we're just going to ignore the ones that aren't for components
20
+ end
21
+
22
+ if component
23
+ if component.respond_to?(:init_stimulus_reflex)
24
+ component.init_stimulus_reflex
25
+ else
26
+ Rails.logger.info "Tried to initialize view_component_reflex on #{component_name}, but it's not a view_component_reflex"
27
+ end
28
+ end
29
+
30
+ receive_original(data)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def self.configure
37
+ yield self if block_given?
38
+ end
39
+ end
40
+ end