opal-vite 0.3.8 → 0.3.11

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ca2d82a8203a375d1c96b0cb26e8510d603bba49a5bb72e0d866c0ef0080029
4
- data.tar.gz: 3d30baaebef6c058c853d61abc772ada7e838ac7dc2371a5b918d55837a8b19c
3
+ metadata.gz: c5628cb3dc5134851617af485e740c1bea4450cd413afabcd998331337ea2441
4
+ data.tar.gz: 54dce35513cf4eae90c8365877d9147ecbd6dcd0f6ebfd3cc58dc333a94df403
5
5
  SHA512:
6
- metadata.gz: 83a4ca03cf9804aa8d1cdc939c1e2eb9096704a94d68324292752a550d0376e17950e5dea6d2d6a34ef5035c0311b84fc1d6b33741951d691f64786ae230bd90
7
- data.tar.gz: d32f860d30ba27ae3ce23b018cb3b40d53f258c8d438fd4c50a38fb38969dc12bda3c29b8f2bfa16095c9d335f9238c322ebe15936b52f9dea58b36faf2020f2
6
+ metadata.gz: f5173decf97b9880ed22b43b722951cb50f7c88ced4894929c926130f8edb99a322e663457ec324c376213c82c64da84032ab91aacb6e73e4a1e35ae96567c9e
7
+ data.tar.gz: 270053e539217d025395db5beb7eed0e4788176e68a849c251388a5c52be06c37db4af6b8f20e6ef60337865307d272bcdf91a8b4139ce125a53fbc6b8949cc2
@@ -1,5 +1,5 @@
1
1
  module Opal
2
2
  module Vite
3
- VERSION = "0.3.8"
3
+ VERSION = "0.3.11"
4
4
  end
5
5
  end
@@ -0,0 +1,245 @@
1
+ # backtick_javascript: true
2
+
3
+ module OpalVite
4
+ module Concerns
5
+ module V1
6
+ # FunctionalComponent - Create React functional components with hooks in Ruby
7
+ #
8
+ # This module provides a DSL to create React functional components
9
+ # while hiding backtick JavaScript internally.
10
+ #
11
+ # @example Basic usage with useState
12
+ # class CounterComponent
13
+ # extend FunctionalComponent
14
+ # extend ReactHelpers
15
+ #
16
+ # def self.to_n
17
+ # create_component do |hooks|
18
+ # count, set_count = hooks.use_state(0)
19
+ #
20
+ # div({ className: 'counter' },
21
+ # button({ onClick: set_count.with { |c| c - 1 } }, '-'),
22
+ # span(nil, count),
23
+ # button({ onClick: set_count.with { |c| c + 1 } }, '+')
24
+ # )
25
+ # end
26
+ # end
27
+ # end
28
+ #
29
+ module FunctionalComponent
30
+ # Create a React functional component with hooks support
31
+ #
32
+ # @yield [hooks] Block that receives hooks helper and returns React element
33
+ # @yieldparam hooks [Hooks] Hooks helper object
34
+ # @return [Native] JavaScript function component
35
+ def create_component(&block)
36
+ r = react
37
+ hooks_class = Hooks
38
+
39
+ `(function(React, HooksClass) {
40
+ return function(props) {
41
+ var hooks = HooksClass.$new(React, props);
42
+ var element = #{block.call(`hooks`)};
43
+ return element;
44
+ };
45
+ })(#{r}, #{hooks_class})`
46
+ end
47
+
48
+ # Hooks helper class - provides React hooks as Ruby methods
49
+ class Hooks
50
+ def initialize(react, props)
51
+ @react = react
52
+ @props = Native(props) if props
53
+ end
54
+
55
+ attr_reader :props
56
+
57
+ # React useState hook
58
+ #
59
+ # @param initial_value [Object] Initial state value
60
+ # @return [Array] [current_value, setter] pair
61
+ #
62
+ # @example
63
+ # count, set_count = hooks.use_state(0)
64
+ # set_count.call(5) # Set to specific value
65
+ # set_count.with { |c| c + 1 } # Functional update
66
+ def use_state(initial_value)
67
+ react = @react
68
+ result = `#{react}.useState(#{initial_value})`
69
+ current = `#{result}[0]`
70
+ setter_fn = `#{result}[1]`
71
+
72
+ setter = StateSetter.new(setter_fn)
73
+ [current, setter]
74
+ end
75
+
76
+ # React useEffect hook
77
+ #
78
+ # @param dependencies [Array, nil] Dependency array (nil = run every render, [] = run once)
79
+ # @yield Effect callback
80
+ # @yieldreturn [Proc, nil] Optional cleanup function
81
+ #
82
+ # @example Run on mount only
83
+ # hooks.use_effect([]) do
84
+ # console_log("Mounted!")
85
+ # -> { console_log("Unmounted!") }
86
+ # end
87
+ #
88
+ # @example Run when count changes
89
+ # hooks.use_effect([count]) do
90
+ # console_log("Count changed to", count)
91
+ # end
92
+ def use_effect(dependencies = nil, &block)
93
+ react = @react
94
+
95
+ effect_fn = `function() {
96
+ var result = #{block.call};
97
+ if (result && typeof result.$call === 'function') {
98
+ return function() { result.$call(); };
99
+ }
100
+ return result;
101
+ }`
102
+
103
+ if dependencies.nil?
104
+ `#{react}.useEffect(#{effect_fn})`
105
+ else
106
+ `#{react}.useEffect(#{effect_fn}, #{dependencies.to_a})`
107
+ end
108
+ end
109
+
110
+ # React useMemo hook
111
+ #
112
+ # @param dependencies [Array] Dependency array
113
+ # @yield Factory function
114
+ # @return [Object] Memoized value
115
+ #
116
+ # @example
117
+ # expensive_value = hooks.use_memo([input]) do
118
+ # compute_expensive(input)
119
+ # end
120
+ def use_memo(dependencies, &block)
121
+ react = @react
122
+ factory_fn = `function() { return #{block.call}; }`
123
+ `#{react}.useMemo(#{factory_fn}, #{dependencies.to_a})`
124
+ end
125
+
126
+ # React useCallback hook
127
+ #
128
+ # @param dependencies [Array] Dependency array
129
+ # @yield Callback function
130
+ # @return [Native] Memoized callback
131
+ #
132
+ # @example
133
+ # handle_click = hooks.use_callback([item_id]) do
134
+ # on_item_click(item_id)
135
+ # end
136
+ def use_callback(dependencies, &block)
137
+ react = @react
138
+ callback_fn = `function() { return #{block.call}; }`
139
+ `#{react}.useCallback(#{callback_fn}, #{dependencies.to_a})`
140
+ end
141
+
142
+ # React useRef hook
143
+ #
144
+ # @param initial_value [Object] Initial ref value
145
+ # @return [Native] Ref object with .current property
146
+ #
147
+ # @example
148
+ # input_ref = hooks.use_ref(nil)
149
+ # # In render: input({ ref: input_ref })
150
+ # # Later: input_ref.current.focus()
151
+ def use_ref(initial_value = nil)
152
+ react = @react
153
+ Native(`#{react}.useRef(#{initial_value})`)
154
+ end
155
+
156
+ # React useReducer hook
157
+ #
158
+ # @param reducer [Proc] Reducer function (state, action) -> new_state
159
+ # @param initial_state [Object] Initial state
160
+ # @return [Array] [state, dispatch] pair
161
+ #
162
+ # @example
163
+ # reducer = ->(state, action) {
164
+ # case action[:type]
165
+ # when 'increment' then { count: state[:count] + 1 }
166
+ # when 'decrement' then { count: state[:count] - 1 }
167
+ # else state
168
+ # end
169
+ # }
170
+ # state, dispatch = hooks.use_reducer(reducer, { count: 0 })
171
+ # dispatch.call({ type: 'increment' })
172
+ def use_reducer(reducer, initial_state)
173
+ react = @react
174
+
175
+ js_reducer = `function(state, action) {
176
+ return #{reducer.call(`state`, `action`)};
177
+ }`
178
+
179
+ result = `#{react}.useReducer(#{js_reducer}, #{initial_state.to_n})`
180
+ state = Native(`#{result}[0]`)
181
+ dispatch_fn = `#{result}[1]`
182
+
183
+ dispatch = ->(action) { `#{dispatch_fn}(#{action.to_n})` }
184
+ [state, dispatch]
185
+ end
186
+
187
+ # React useContext hook
188
+ #
189
+ # @param context [Native] React context object
190
+ # @return [Object] Current context value
191
+ def use_context(context)
192
+ react = @react
193
+ `#{react}.useContext(#{context})`
194
+ end
195
+ end
196
+
197
+ # State setter wrapper - provides functional update support
198
+ class StateSetter
199
+ def initialize(setter_fn)
200
+ @setter_fn = setter_fn
201
+ end
202
+
203
+ # Set state to a specific value
204
+ #
205
+ # @param value [Object] New state value
206
+ def call(value)
207
+ `#{@setter_fn}(#{value})`
208
+ end
209
+
210
+ # Functional state update
211
+ # Returns a callback function for use in onClick etc.
212
+ #
213
+ # @yield [current] Block receives current value and returns new value
214
+ # @return [Native] JavaScript callback function
215
+ #
216
+ # @example
217
+ # button({ onClick: set_count.with { |c| c + 1 } }, '+')
218
+ def with(&block)
219
+ setter_fn = @setter_fn
220
+ `function() {
221
+ #{setter_fn}(function(current) {
222
+ return #{block.call(`current`)};
223
+ });
224
+ }`
225
+ end
226
+
227
+ # Create a callback that sets to a specific value
228
+ #
229
+ # @param value [Object] Value to set
230
+ # @return [Native] JavaScript callback function
231
+ #
232
+ # @example
233
+ # button({ onClick: set_count.to(0) }, 'Reset')
234
+ def to(value)
235
+ setter_fn = @setter_fn
236
+ `function() { #{setter_fn}(#{value}); }`
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+ # Alias for easy access
245
+ FunctionalComponent = OpalVite::Concerns::V1::FunctionalComponent
@@ -6,6 +6,7 @@ require 'opal_vite/concerns/v1/storable'
6
6
  require 'opal_vite/concerns/v1/stimulus_helpers'
7
7
  require 'opal_vite/concerns/v1/vue_helpers'
8
8
  require 'opal_vite/concerns/v1/react_helpers'
9
+ require 'opal_vite/concerns/v1/functional_component'
9
10
  require 'opal_vite/concerns/v1/uri_helpers'
10
11
  require 'opal_vite/concerns/v1/base64_helpers'
11
12
  require 'opal_vite/concerns/v1/debug_helpers'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opal-vite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.8
4
+ version: 0.3.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - stofu1234
@@ -124,6 +124,7 @@ files:
124
124
  - opal/opal_vite/concerns/v1/base64_helpers.rb
125
125
  - opal/opal_vite/concerns/v1/debug_helpers.rb
126
126
  - opal/opal_vite/concerns/v1/dom_helpers.rb
127
+ - opal/opal_vite/concerns/v1/functional_component.rb
127
128
  - opal/opal_vite/concerns/v1/js_proxy_ex.rb
128
129
  - opal/opal_vite/concerns/v1/react_helpers.rb
129
130
  - opal/opal_vite/concerns/v1/stimulus_helpers.rb