opal-vite 0.2.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d4bc96b96e9f545db30dd5ca2d6b0fb0aea32f1a03e9a733c6e3c802dc616506
4
- data.tar.gz: d753caf10cfca04a2709bfe446f3dde148eafc3da279d5fbb38797ad17b000da
3
+ metadata.gz: a747844bb38869490030ea9a7649cdd6f5b665cf2a60da55f3cc12f479aa556b
4
+ data.tar.gz: 8e0de9d6b0b124f5ee0cb83cfd242199785c6a7ecdfb1f2e3ba0f7253bb5c663
5
5
  SHA512:
6
- metadata.gz: 7a660f33fceef3720e67a7530cbda406c6218978557056cefc782e7dce014e0c73784ea46397091cfff1a130e2d0edc9003f051694f88f52c4f0f568748cba7c
7
- data.tar.gz: 017e824fd2eefd1ecc9cdae4dd7677bd74e6dd14082a0c5fc755f22f7cf879d61d0a519e55988f02d93bdf1d74aeb37ffdc5f8822f88d0a3e99b1d6cf229f047
6
+ metadata.gz: d12af9028e4ca9537a4af9043a74fc3dd3cf043a0ed928ad199a5bf36625e2d3a0d392d7e45ac8f49832edb5c409664334ddb60571f794511156a4d50332644c
7
+ data.tar.gz: '083ab7d7c102a315c9253491790014b6a5af41996bf81190d4a3f2c56bce9e503a3d204a0013866d2f9a956d311b9136bc2f5b86a317350d9f9585fe9ffc0299'
@@ -1,5 +1,5 @@
1
1
  module Opal
2
2
  module Vite
3
- VERSION = "0.2.0"
3
+ VERSION = "0.2.1"
4
4
  end
5
5
  end
data/lib/opal-vite.rb CHANGED
@@ -21,9 +21,17 @@ module Opal
21
21
  yield(config) if block_given?
22
22
  end
23
23
 
24
+ # Returns the path to the opal/ directory in this gem
25
+ # Contains built-in concerns like StimulusHelpers
26
+ def opal_lib_path
27
+ File.expand_path('../../opal', __dir__)
28
+ end
29
+
24
30
  # CLI entry point for compilation
25
- def compile_for_vite(file_path)
26
- compiler = Compiler.new
31
+ # @param file_path [String] The path to the Ruby file to compile
32
+ # @param include_concerns [Boolean] Whether to include built-in concerns
33
+ def compile_for_vite(file_path, include_concerns: true)
34
+ compiler = Compiler.new(include_concerns: include_concerns)
27
35
  result = compiler.compile_file(file_path)
28
36
 
29
37
  # Output JSON to stdout for the Vite plugin to consume
@@ -0,0 +1,315 @@
1
+ # backtick_javascript: true
2
+
3
+ # ReactHelpers - DSL helpers for React applications with Opal
4
+ # Reduces backtick JavaScript usage in React components
5
+ module ReactHelpers
6
+ # ===================
7
+ # React Access
8
+ # ===================
9
+
10
+ # Get React from window
11
+ def react
12
+ Native(`window.React`)
13
+ end
14
+
15
+ # Get ReactDOM from window
16
+ def react_dom
17
+ Native(`window.ReactDOM`)
18
+ end
19
+
20
+ # ===================
21
+ # Window/Global Access
22
+ # ===================
23
+
24
+ # Get a property from window
25
+ def window_get(key)
26
+ `window[#{key}]`
27
+ end
28
+
29
+ # Set a property on window
30
+ def window_set(key, value)
31
+ `window[#{key}] = #{value}`
32
+ end
33
+
34
+ # Delete a property from window
35
+ def window_delete(key)
36
+ `delete window[#{key}]`
37
+ end
38
+
39
+ # ===================
40
+ # Console
41
+ # ===================
42
+
43
+ # Console log
44
+ def console_log(*args)
45
+ `console.log(...#{args})`
46
+ end
47
+
48
+ # Console warn
49
+ def console_warn(*args)
50
+ `console.warn(...#{args})`
51
+ end
52
+
53
+ # Console error
54
+ def console_error(*args)
55
+ `console.error(...#{args})`
56
+ end
57
+
58
+ # ===================
59
+ # Alerts/Dialogs
60
+ # ===================
61
+
62
+ # Show alert dialog
63
+ def alert_message(message)
64
+ `alert(#{message})`
65
+ end
66
+
67
+ # Show confirm dialog
68
+ def confirm_message(message)
69
+ `confirm(#{message})`
70
+ end
71
+
72
+ # Show prompt dialog
73
+ def prompt_message(message, default_value = '')
74
+ `prompt(#{message}, #{default_value})`
75
+ end
76
+
77
+ # ===================
78
+ # DOM Events
79
+ # ===================
80
+
81
+ # Execute block when DOM is ready
82
+ def on_dom_ready(&block)
83
+ `document.addEventListener('DOMContentLoaded', #{block})`
84
+ end
85
+
86
+ # Add event listener to window
87
+ def on_window_event(event_name, &block)
88
+ `window.addEventListener(#{event_name}, #{block})`
89
+ end
90
+
91
+ # Remove event listener from window
92
+ def off_window_event(event_name, handler)
93
+ `window.removeEventListener(#{event_name}, #{handler})`
94
+ end
95
+
96
+ # ===================
97
+ # DOM Query
98
+ # ===================
99
+
100
+ # Query single element
101
+ def query(selector)
102
+ `document.querySelector(#{selector})`
103
+ end
104
+
105
+ # Query all elements
106
+ def query_all(selector)
107
+ `Array.from(document.querySelectorAll(#{selector}))`
108
+ end
109
+
110
+ # Get element by ID
111
+ def get_element_by_id(id)
112
+ `document.getElementById(#{id})`
113
+ end
114
+
115
+ # ===================
116
+ # DOM Manipulation
117
+ # ===================
118
+
119
+ # Create element
120
+ def create_element(tag)
121
+ `document.createElement(#{tag})`
122
+ end
123
+
124
+ # Set innerHTML
125
+ def set_html(element, html)
126
+ `#{element}.innerHTML = #{html}`
127
+ end
128
+
129
+ # Set textContent
130
+ def set_text(element, text)
131
+ `#{element}.textContent = #{text}`
132
+ end
133
+
134
+ # Add class to element
135
+ def add_class(element, *classes)
136
+ `#{element}.classList.add(...#{classes})`
137
+ end
138
+
139
+ # Remove class from element
140
+ def remove_class(element, *classes)
141
+ `#{element}.classList.remove(...#{classes})`
142
+ end
143
+
144
+ # ===================
145
+ # React Element Creation Helpers
146
+ # ===================
147
+
148
+ # Create React element (shorthand)
149
+ def el(type, props = nil, *children)
150
+ react.createElement(type, props, *children)
151
+ end
152
+
153
+ # Create div element
154
+ def div(props = nil, *children, &block)
155
+ if block_given?
156
+ react.createElement('div', props, block.call)
157
+ else
158
+ react.createElement('div', props, *children)
159
+ end
160
+ end
161
+
162
+ # Create span element
163
+ def span(props = nil, *children, &block)
164
+ if block_given?
165
+ react.createElement('span', props, block.call)
166
+ else
167
+ react.createElement('span', props, *children)
168
+ end
169
+ end
170
+
171
+ # Create button element
172
+ def button(props = nil, *children, &block)
173
+ if block_given?
174
+ react.createElement('button', props, block.call)
175
+ else
176
+ react.createElement('button', props, *children)
177
+ end
178
+ end
179
+
180
+ # Create p element
181
+ def paragraph(props = nil, *children, &block)
182
+ if block_given?
183
+ react.createElement('p', props, block.call)
184
+ else
185
+ react.createElement('p', props, *children)
186
+ end
187
+ end
188
+
189
+ # Create heading elements
190
+ def h1(props = nil, *children)
191
+ react.createElement('h1', props, *children)
192
+ end
193
+
194
+ def h2(props = nil, *children)
195
+ react.createElement('h2', props, *children)
196
+ end
197
+
198
+ def h3(props = nil, *children)
199
+ react.createElement('h3', props, *children)
200
+ end
201
+
202
+ # ===================
203
+ # Timing
204
+ # ===================
205
+
206
+ # Set timeout
207
+ def set_timeout(delay_ms, &block)
208
+ `setTimeout(#{block}, #{delay_ms})`
209
+ end
210
+
211
+ # Set interval
212
+ def set_interval(interval_ms, &block)
213
+ `setInterval(#{block}, #{interval_ms})`
214
+ end
215
+
216
+ # Clear timeout
217
+ def clear_timeout(timeout_id)
218
+ `clearTimeout(#{timeout_id})`
219
+ end
220
+
221
+ # Clear interval
222
+ def clear_interval(interval_id)
223
+ `clearInterval(#{interval_id})`
224
+ end
225
+
226
+ # ===================
227
+ # LocalStorage
228
+ # ===================
229
+
230
+ # Get from localStorage
231
+ def storage_get(key)
232
+ `localStorage.getItem(#{key})`
233
+ end
234
+
235
+ # Set to localStorage
236
+ def storage_set(key, value)
237
+ `localStorage.setItem(#{key}, #{value})`
238
+ end
239
+
240
+ # Remove from localStorage
241
+ def storage_remove(key)
242
+ `localStorage.removeItem(#{key})`
243
+ end
244
+
245
+ # ===================
246
+ # Fetch API
247
+ # ===================
248
+
249
+ # Fetch with promise (returns Native promise)
250
+ def fetch_url(url, options = nil)
251
+ if options
252
+ Native(`fetch(#{url}, #{options.to_n})`)
253
+ else
254
+ Native(`fetch(#{url})`)
255
+ end
256
+ end
257
+
258
+ # ===================
259
+ # JSON
260
+ # ===================
261
+
262
+ # Parse JSON string
263
+ def parse_json(json_string)
264
+ `JSON.parse(#{json_string})`
265
+ end
266
+
267
+ # Stringify to JSON
268
+ def to_json(object)
269
+ `JSON.stringify(#{object})`
270
+ end
271
+
272
+ # ===================
273
+ # Type Conversion
274
+ # ===================
275
+
276
+ # Parse string to integer (wrapper for JavaScript parseInt)
277
+ # @param value [String, Number] Value to parse
278
+ # @param radix [Integer] Radix (default: 10)
279
+ # @return [Integer, NaN] Parsed integer
280
+ def parse_int(value, radix = 10)
281
+ `parseInt(#{value}, #{radix})`
282
+ end
283
+
284
+ # Parse string to float (wrapper for JavaScript parseFloat)
285
+ # @param value [String, Number] Value to parse
286
+ # @return [Float, NaN] Parsed float
287
+ def parse_float(value)
288
+ `parseFloat(#{value})`
289
+ end
290
+
291
+ # Check if value is NaN
292
+ # @param value [Number] Value to check
293
+ # @return [Boolean] true if NaN
294
+ def is_nan?(value)
295
+ `Number.isNaN(#{value})`
296
+ end
297
+
298
+ # Parse integer with default value (returns default if NaN)
299
+ # @param value [String, Number] Value to parse
300
+ # @param default_value [Integer] Default value if parsing fails
301
+ # @return [Integer] Parsed integer or default
302
+ def parse_int_or(value, default_value = 0)
303
+ result = parse_int(value)
304
+ is_nan?(result) ? default_value : result
305
+ end
306
+
307
+ # Parse float with default value (returns default if NaN)
308
+ # @param value [String, Number] Value to parse
309
+ # @param default_value [Float] Default value if parsing fails
310
+ # @return [Float] Parsed float or default
311
+ def parse_float_or(value, default_value = 0.0)
312
+ result = parse_float(value)
313
+ is_nan?(result) ? default_value : result
314
+ end
315
+ end
@@ -339,6 +339,391 @@ module OpalVite
339
339
  `#{array}[#{index}]`
340
340
  end
341
341
 
342
+ # ===== LocalStorage Methods =====
343
+
344
+ # Get item from localStorage
345
+ # @param key [String] Storage key
346
+ # @return [String, nil] Stored value or nil
347
+ def storage_get(key)
348
+ `localStorage.getItem(#{key})`
349
+ end
350
+
351
+ # Set item in localStorage
352
+ # @param key [String] Storage key
353
+ # @param value [String] Value to store
354
+ def storage_set(key, value)
355
+ `localStorage.setItem(#{key}, #{value})`
356
+ end
357
+
358
+ # Remove item from localStorage
359
+ # @param key [String] Storage key
360
+ def storage_remove(key)
361
+ `localStorage.removeItem(#{key})`
362
+ end
363
+
364
+ # Get JSON-parsed value from localStorage
365
+ # @param key [String] Storage key
366
+ # @param default [Object] Default value if key doesn't exist
367
+ # @return [Object] Parsed value or default
368
+ def storage_get_json(key, default = nil)
369
+ stored = storage_get(key)
370
+ return default if `#{stored} === null`
371
+ `JSON.parse(#{stored})`
372
+ end
373
+
374
+ # Set JSON-stringified value in localStorage
375
+ # @param key [String] Storage key
376
+ # @param value [Object] Value to store (will be JSON-stringified)
377
+ def storage_set_json(key, value)
378
+ `localStorage.setItem(#{key}, JSON.stringify(#{value.to_n}))`
379
+ end
380
+
381
+ # ===== Event Methods =====
382
+
383
+ # Dispatch a custom event on window
384
+ # @param name [String] Event name
385
+ # @param detail [Hash] Event detail data
386
+ def dispatch_window_event(name, detail = {})
387
+ native_detail = detail.is_a?(Hash) ? detail.to_n : detail
388
+ `
389
+ const event = new CustomEvent(#{name}, { detail: #{native_detail} });
390
+ window.dispatchEvent(event);
391
+ `
392
+ end
393
+
394
+ # Dispatch a custom event on the controller element
395
+ # @param name [String] Event name
396
+ # @param detail [Hash] Event detail data
397
+ def dispatch_event(name, detail = {})
398
+ native_detail = detail.is_a?(Hash) ? detail.to_n : detail
399
+ `
400
+ const event = new CustomEvent(#{name}, { detail: #{native_detail} });
401
+ this.element.dispatchEvent(event);
402
+ `
403
+ end
404
+
405
+ # Add window event listener
406
+ # @param name [String] Event name
407
+ # @yield Block to execute when event fires
408
+ def on_window_event(name, &block)
409
+ `window.addEventListener(#{name}, #{block})`
410
+ end
411
+
412
+ # Remove window event listener
413
+ # @param name [String] Event name
414
+ # @param handler [Native] Handler function to remove
415
+ def off_window_event(name, handler)
416
+ `window.removeEventListener(#{name}, #{handler})`
417
+ end
418
+
419
+ # Add document event listener
420
+ # @param name [String] Event name
421
+ # @yield Block to execute when event fires
422
+ def on_document_event(name, &block)
423
+ `document.addEventListener(#{name}, #{block})`
424
+ end
425
+
426
+ # Add event listener when DOM is ready
427
+ # @yield Block to execute when DOM is ready
428
+ def on_dom_ready(&block)
429
+ `document.addEventListener('DOMContentLoaded', #{block})`
430
+ end
431
+
432
+ # Add event listener to any element
433
+ # @param element [Native] DOM element
434
+ # @param name [String] Event name
435
+ # @yield Block to execute when event fires
436
+ def on_element_event(element, name, &block)
437
+ `#{element}.addEventListener(#{name}, #{block})`
438
+ end
439
+
440
+ # Remove event listener from element
441
+ # @param element [Native] DOM element
442
+ # @param name [String] Event name
443
+ # @param handler [Native] Handler function to remove
444
+ def off_element_event(element, name, handler)
445
+ `#{element}.removeEventListener(#{name}, #{handler})`
446
+ end
447
+
448
+ # Add event listener to controller's element (this.element)
449
+ # @param name [String] Event name
450
+ # @yield Block to execute when event fires
451
+ def on_controller_event(name, &block)
452
+ `
453
+ const handler = #{block};
454
+ this.element.addEventListener(#{name}, function(e) {
455
+ handler(e);
456
+ });
457
+ `
458
+ end
459
+
460
+ # Get the current event's target element
461
+ # @return [Native] Event target element
462
+ def event_target
463
+ `event.currentTarget`
464
+ end
465
+
466
+ # Get data attribute from current event target
467
+ # @param attr [String] Data attribute name (without 'data-' prefix)
468
+ # @return [String, nil] Attribute value
469
+ def event_data(attr)
470
+ `event.currentTarget.getAttribute('data-' + #{attr})`
471
+ end
472
+
473
+ # Get integer data attribute from current event target
474
+ # @param attr [String] Data attribute name (without 'data-' prefix)
475
+ # @return [Integer, nil] Parsed integer value
476
+ def event_data_int(attr)
477
+ parse_int(event_data(attr))
478
+ end
479
+
480
+ # Prevent default event behavior
481
+ def prevent_default
482
+ `event.preventDefault()`
483
+ end
484
+
485
+ # Get event key (for keyboard events)
486
+ # @return [String] Key name
487
+ def event_key
488
+ `event.key`
489
+ end
490
+
491
+ # ===== Element Methods =====
492
+
493
+ # Add class to element
494
+ # @param element [Native] DOM element
495
+ # @param class_name [String] CSS class to add
496
+ def add_class(element, class_name)
497
+ `#{element}.classList.add(#{class_name})`
498
+ end
499
+
500
+ # Remove class from element
501
+ # @param element [Native] DOM element
502
+ # @param class_name [String] CSS class to remove
503
+ def remove_class(element, class_name)
504
+ `#{element}.classList.remove(#{class_name})`
505
+ end
506
+
507
+ # Toggle class on element
508
+ # @param element [Native] DOM element
509
+ # @param class_name [String] CSS class to toggle
510
+ def toggle_class(element, class_name)
511
+ `#{element}.classList.toggle(#{class_name})`
512
+ end
513
+
514
+ # Check if element has class
515
+ # @param element [Native] DOM element
516
+ # @param class_name [String] CSS class to check
517
+ # @return [Boolean]
518
+ def has_class?(element, class_name)
519
+ `#{element}.classList.contains(#{class_name})`
520
+ end
521
+
522
+ # Set element attribute
523
+ # @param element [Native] DOM element
524
+ # @param attr [String] Attribute name
525
+ # @param value [String] Attribute value
526
+ def set_attr(element, attr, value)
527
+ `#{element}.setAttribute(#{attr}, #{value})`
528
+ end
529
+
530
+ # Get element attribute
531
+ # @param element [Native] DOM element
532
+ # @param attr [String] Attribute name
533
+ # @return [String, nil] Attribute value
534
+ def get_attr(element, attr)
535
+ `#{element}.getAttribute(#{attr})`
536
+ end
537
+
538
+ # Remove element attribute
539
+ # @param element [Native] DOM element
540
+ # @param attr [String] Attribute name
541
+ def remove_attr(element, attr)
542
+ `#{element}.removeAttribute(#{attr})`
543
+ end
544
+
545
+ # Set element style
546
+ # @param element [Native] DOM element
547
+ # @param property [String] CSS property
548
+ # @param value [String] CSS value
549
+ def set_style(element, property, value)
550
+ `#{element}.style[#{property}] = #{value}`
551
+ end
552
+
553
+ # Set element innerHTML
554
+ # @param element [Native] DOM element
555
+ # @param html [String] HTML content
556
+ def set_html(element, html)
557
+ `#{element}.innerHTML = #{html}`
558
+ end
559
+
560
+ # Set element textContent
561
+ # @param element [Native] DOM element
562
+ # @param text [String] Text content
563
+ def set_text(element, text)
564
+ `#{element}.textContent = #{text}`
565
+ end
566
+
567
+ # Get element value (for inputs)
568
+ # @param element [Native] DOM element
569
+ # @return [String] Element value
570
+ def get_value(element)
571
+ `#{element}.value`
572
+ end
573
+
574
+ # Set element value (for inputs)
575
+ # @param element [Native] DOM element
576
+ # @param value [String] Value to set
577
+ def set_value(element, value)
578
+ `#{element}.value = #{value}`
579
+ end
580
+
581
+ # Focus element
582
+ # @param element [Native] DOM element
583
+ def focus(element)
584
+ `#{element}.focus()`
585
+ end
586
+
587
+ # Check if element has attribute
588
+ # @param element [Native] DOM element
589
+ # @param attr [String] Attribute name
590
+ # @return [Boolean]
591
+ def has_attr?(element, attr)
592
+ `#{element}.hasAttribute(#{attr})`
593
+ end
594
+
595
+ # ===== DOM Creation Methods =====
596
+
597
+ # Create a new DOM element
598
+ # @param tag [String] HTML tag name
599
+ # @return [Native] Created element
600
+ def create_element(tag)
601
+ `document.createElement(#{tag})`
602
+ end
603
+
604
+ # Append child to element
605
+ # @param parent [Native] Parent element
606
+ # @param child [Native] Child element to append
607
+ def append_child(parent, child)
608
+ `#{parent}.appendChild(#{child})`
609
+ end
610
+
611
+ # Remove element from DOM
612
+ # @param element [Native] Element to remove
613
+ def remove_element(element)
614
+ `#{element}.remove()`
615
+ end
616
+
617
+ # Get next element sibling
618
+ # @param element [Native] DOM element
619
+ # @return [Native, nil] Next sibling element
620
+ def next_sibling(element)
621
+ `#{element}.nextElementSibling`
622
+ end
623
+
624
+ # Get previous element sibling
625
+ # @param element [Native] DOM element
626
+ # @return [Native, nil] Previous sibling element
627
+ def prev_sibling(element)
628
+ `#{element}.previousElementSibling`
629
+ end
630
+
631
+ # Get parent element
632
+ # @param element [Native] DOM element
633
+ # @return [Native, nil] Parent element
634
+ def parent(element)
635
+ `#{element}.parentElement`
636
+ end
637
+
638
+ # ===== DOM Query Methods =====
639
+
640
+ # Query selector on document
641
+ # @param selector [String] CSS selector
642
+ # @return [Native, nil] Element or nil
643
+ def query(selector)
644
+ `document.querySelector(#{selector})`
645
+ end
646
+
647
+ # Query selector all on document
648
+ # @param selector [String] CSS selector
649
+ # @return [Array] Array of elements
650
+ def query_all(selector)
651
+ `Array.from(document.querySelectorAll(#{selector}))`
652
+ end
653
+
654
+ # Query selector on controller element
655
+ # @param selector [String] CSS selector
656
+ # @return [Native, nil] Element or nil
657
+ def query_element(selector)
658
+ `this.element.querySelector(#{selector})`
659
+ end
660
+
661
+ # Query selector all on controller element
662
+ # @param selector [String] CSS selector
663
+ # @return [Array] Array of elements
664
+ def query_all_element(selector)
665
+ `Array.from(this.element.querySelectorAll(#{selector}))`
666
+ end
667
+
668
+ # ===== Document Methods =====
669
+
670
+ # Get document root element (html)
671
+ # @return [Native] HTML element
672
+ def doc_root
673
+ `document.documentElement`
674
+ end
675
+
676
+ # Set attribute on document root
677
+ # @param attr [String] Attribute name
678
+ # @param value [String] Attribute value
679
+ def set_root_attr(attr, value)
680
+ `document.documentElement.setAttribute(#{attr}, #{value})`
681
+ end
682
+
683
+ # Get attribute from document root
684
+ # @param attr [String] Attribute name
685
+ # @return [String, nil] Attribute value
686
+ def get_root_attr(attr)
687
+ `document.documentElement.getAttribute(#{attr})`
688
+ end
689
+
690
+ # ===== Template Methods =====
691
+
692
+ # Clone a template target's content
693
+ # @param name [Symbol, String] Template target name
694
+ # @return [Native] Cloned content
695
+ def clone_template(name)
696
+ method_name = "#{camelize(name, false)}Target"
697
+ `this[#{method_name}].content.cloneNode(true)`
698
+ end
699
+
700
+ # Get first element child from cloned template
701
+ # @param clone [Native] Cloned template content
702
+ # @return [Native, nil] First element child
703
+ def template_first_child(clone)
704
+ `#{clone}.firstElementChild`
705
+ end
706
+
707
+ # ===== Stimulus Controller Element Methods =====
708
+
709
+ # Add class to controller element
710
+ # @param class_name [String] CSS class to add
711
+ def element_add_class(class_name)
712
+ `this.element.classList.add(#{class_name})`
713
+ end
714
+
715
+ # Remove class from controller element
716
+ # @param class_name [String] CSS class to remove
717
+ def element_remove_class(class_name)
718
+ `this.element.classList.remove(#{class_name})`
719
+ end
720
+
721
+ # Toggle class on controller element
722
+ # @param class_name [String] CSS class to toggle
723
+ def element_toggle_class(class_name)
724
+ `this.element.classList.toggle(#{class_name})`
725
+ end
726
+
342
727
  # ===== Utility Methods =====
343
728
 
344
729
  # Generate a unique ID based on timestamp
@@ -363,6 +748,48 @@ module OpalVite
363
748
  `this[#{method_name}].blur()`
364
749
  end
365
750
 
751
+ # ===== Type Conversion Methods =====
752
+
753
+ # Parse string to integer (wrapper for JavaScript parseInt)
754
+ # @param value [String, Number] Value to parse
755
+ # @param radix [Integer] Radix (default: 10)
756
+ # @return [Integer, NaN] Parsed integer
757
+ def parse_int(value, radix = 10)
758
+ `parseInt(#{value}, #{radix})`
759
+ end
760
+
761
+ # Parse string to float (wrapper for JavaScript parseFloat)
762
+ # @param value [String, Number] Value to parse
763
+ # @return [Float, NaN] Parsed float
764
+ def parse_float(value)
765
+ `parseFloat(#{value})`
766
+ end
767
+
768
+ # Check if value is NaN
769
+ # @param value [Number] Value to check
770
+ # @return [Boolean] true if NaN
771
+ def is_nan?(value)
772
+ `Number.isNaN(#{value})`
773
+ end
774
+
775
+ # Parse integer with default value (returns default if NaN)
776
+ # @param value [String, Number] Value to parse
777
+ # @param default_value [Integer] Default value if parsing fails
778
+ # @return [Integer] Parsed integer or default
779
+ def parse_int_or(value, default_value = 0)
780
+ result = parse_int(value)
781
+ is_nan?(result) ? default_value : result
782
+ end
783
+
784
+ # Parse float with default value (returns default if NaN)
785
+ # @param value [String, Number] Value to parse
786
+ # @param default_value [Float] Default value if parsing fails
787
+ # @return [Float] Parsed float or default
788
+ def parse_float_or(value, default_value = 0.0)
789
+ result = parse_float(value)
790
+ is_nan?(result) ? default_value : result
791
+ end
792
+
366
793
  private
367
794
 
368
795
  # Convert snake_case to camelCase
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.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - stofu1234
@@ -114,6 +114,7 @@ files:
114
114
  - opal/opal_vite/concerns.rb
115
115
  - opal/opal_vite/concerns/dom_helpers.rb
116
116
  - opal/opal_vite/concerns/js_proxy_ex.rb
117
+ - opal/opal_vite/concerns/react_helpers.rb
117
118
  - opal/opal_vite/concerns/stimulus_helpers.rb
118
119
  - opal/opal_vite/concerns/storable.rb
119
120
  - opal/opal_vite/concerns/toastable.rb