web_ruby_ui 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 390ac580582694ff163f766967f0b90aab3d3df6c70a8306a192f58e0889d0d1
4
+ data.tar.gz: 781d6b5303602c16d22564abe89b7c82c666fff7656e550195a1b27b5e1dfd6c
5
+ SHA512:
6
+ metadata.gz: 5b17c0c0917b850ad1265cee471e88f94e52d510488387f5075226d1a27b2217d04874149bd72ed95b76dd61e41419b9bb52ca26ca65e8e10be4d086e5f879e8
7
+ data.tar.gz: 302755ff2c233d61f2238169a73766cfd285a14be5a0330de84a08c8b286394a523f030e3632f29fff3f311c3db80540d308944cb7ac820a7ecc83e109220675
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aditya
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction: including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # web_ruby_ui πŸ’ŽπŸŒ
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/web_ruby_ui.svg)](https://badge.fury.io/rb/web_ruby_ui)
4
+ [![GEM Downloads](https://img.shields.io/gem/dt/web_ruby_ui.svg)](https://rubygems.org/gems/web_ruby_ui)
5
+ [![CI Status](https://github.com/aditya-8108/web_ruby_ui/actions/workflows/ci.yml/badge.svg)](https://github.com/aditya-8108/web_ruby_ui/actions/workflows/ci.yml)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ **The 100% Pure Ruby Frontend Web Framework in WebAssembly.**
9
+
10
+ Build interactive Single-Page Applications (SPAs) entirely in Ruby running inside the browser via `ruby.wasm` β€” **Zero JavaScript, Zero React, Zero Node, and Zero NPM dependencies.**
11
+
12
+ ---
13
+
14
+ ## Key Features πŸš€
15
+
16
+ - ⚑ **100% Pure Ruby Frontend**: Write interactive web components, event listeners, and state management in native Ruby.
17
+ - πŸ”„ **Reactive State Management**: Define `state :count, default: 0`. Updating state (`self.count += 1`) automatically re-renders component views.
18
+ - 🌳 **Virtual DOM Diffing**: In-memory `VNode` tree generation and diffing engine for optimal browser DOM rendering.
19
+ - πŸ—ΊοΈ **Client-Side SPA Routing**: Single-page application route matching (`Router`) for multi-view Ruby web apps.
20
+ - πŸ› οΈ **Dev Server & CLI Tooling**: `web_ruby_ui dev` launches a local WebAssembly dev server with instant browser hot-loading.
21
+
22
+ ---
23
+
24
+ ## Architecture Overview πŸ“
25
+
26
+ ```
27
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
28
+ BROWSER (WebAssembly Environment)
29
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
30
+ JS::Object (Browser DOM APIs)
31
+ β–²
32
+ β”‚ DOM Events (onclick, oninput) & Render Patches
33
+ β–Ό
34
+ WebRubyUI::DOM (Event Bridge & JS Interop)
35
+ β–²
36
+ β”‚ In-Memory VNode Tree & Patch Diffing
37
+ β–Ό
38
+ WebRubyUI::Component (Reactive State & HTML DSL)
39
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
40
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
41
+ ```
42
+
43
+ ---
44
+
45
+ ## Installation
46
+
47
+ Add this line to your application's Gemfile:
48
+
49
+ ```ruby
50
+ gem 'web_ruby_ui'
51
+ ```
52
+
53
+ And then execute:
54
+ ```bash
55
+ bundle install
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Quickstart (Todo App in Pure Ruby)
61
+
62
+ Create a reactive Single-Page Application without touching JavaScript:
63
+
64
+ ```ruby
65
+ require 'web_ruby_ui'
66
+
67
+ class TodoApp < WebRubyUI::Component
68
+ state :items, default: [
69
+ { id: 1, title: 'Learn Ruby WebAssembly', completed: true },
70
+ { id: 2, title: 'Build WebRubyUI frontend framework', completed: true }
71
+ ]
72
+ state :new_title, default: ''
73
+
74
+ def render
75
+ div(class: 'max-w-md mx-auto my-10 p-6 bg-white rounded-xl shadow-lg') do
76
+ h1(class: 'text-2xl font-bold text-indigo-600 mb-4') { 'RubyWasm Todo App πŸ’Ž' }
77
+
78
+ # Add Todo Form
79
+ div(class: 'flex mb-4 gap-2') do
80
+ input(
81
+ type: 'text',
82
+ class: 'flex-1 px-4 py-2 border rounded-lg',
83
+ placeholder: 'What needs to be done?',
84
+ value: new_title,
85
+ oninput: ->(e) { self.new_title = e[:target][:value] }
86
+ )
87
+ button(
88
+ class: 'px-4 py-2 bg-indigo-600 text-white rounded-lg font-semibold',
89
+ onclick: -> { add_todo }
90
+ ) { 'Add' }
91
+ end
92
+
93
+ # Todo List
94
+ ul(class: 'divide-y divide-gray-200') do
95
+ items.each do |item|
96
+ li(class: 'py-3 flex items-center justify-between') do
97
+ span(class: item[:completed] ? 'line-through text-gray-400' : 'text-gray-800') do
98
+ item[:title]
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ def add_todo
109
+ return if new_title.nil? || new_title.strip.empty?
110
+ self.items = items + [{ id: Time.now.to_i, title: new_title.strip, completed: false }]
111
+ self.new_title = ''
112
+ end
113
+ end
114
+ ```
115
+
116
+ ---
117
+
118
+ ## CLI Commands
119
+
120
+ | Command | Description |
121
+ | --- | --- |
122
+ | `web_ruby_ui new my_app` | Generates a new WebRubyUI project structure |
123
+ | `web_ruby_ui dev` | Launches a local WebAssembly web server at `http://localhost:8000` |
124
+ | `web_ruby_ui build` | Packages standalone `index.html` for production deployment |
125
+
126
+ ---
127
+
128
+ ## License
129
+
130
+ MIT License.
data/bin/web_ruby_ui ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require_relative '../lib/web_ruby_ui'
6
+
7
+ WebRubyUI::CLI.start(ARGV)
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'webrick'
5
+ require 'fileutils'
6
+
7
+ module WebRubyUI
8
+ class CLI < Thor
9
+ map 'new' => :new_project
10
+
11
+ desc 'dev [APP_DIR]', 'Start a local WebAssembly dev server for a WebRubyUI app'
12
+ method_option :port, type: :numeric, default: 8000, aliases: '-p', desc: 'Port to run dev server on'
13
+ def dev(app_dir = '.')
14
+ port = options[:port]
15
+ say "πŸš€ Starting WebRubyUI Wasm dev server on http://localhost:#{port}...", :green
16
+
17
+ target_dir = File.expand_path(app_dir)
18
+ index_path = File.join(target_dir, 'index.html')
19
+
20
+ ensure_index_html(index_path)
21
+
22
+ server = WEBrick::HTTPServer.new(
23
+ Port: port,
24
+ DocumentRoot: target_dir,
25
+ AccessLog: [],
26
+ Logger: WEBrick::Log.new(File::NULL)
27
+ )
28
+
29
+ trap('INT') { server.shutdown }
30
+ server.start
31
+ end
32
+
33
+ desc 'build [APP_DIR]', 'Build standalone HTML/Wasm package for production deployment'
34
+ def build(app_dir = '.')
35
+ target_dir = File.expand_path(app_dir)
36
+ index_path = File.join(target_dir, 'index.html')
37
+
38
+ ensure_index_html(index_path)
39
+ say "✨ Successfully built WebRubyUI Wasm package in #{target_dir}/index.html", :green
40
+ end
41
+
42
+ desc 'new APP_NAME', 'Generate a new WebRubyUI Single-Page App project'
43
+ def new_project(app_name)
44
+ target_dir = File.expand_path(app_name)
45
+ FileUtils.mkdir_p(File.join(target_dir, 'app'))
46
+
47
+ app_rb = <<~RUBY
48
+ # frozen_string_literal: true
49
+
50
+ require 'web_ruby_ui'
51
+
52
+ class App < WebRubyUI::Component
53
+ state :count, default: 0
54
+
55
+ def render
56
+ div(class: 'card') do
57
+ h1 { "Welcome to WebRubyUI!" }
58
+ p { "Count: \#{count}" }
59
+ button(onclick: -> { self.count += 1 }) { "Increment" }
60
+ end
61
+ end
62
+ end
63
+ RUBY
64
+
65
+ File.write(File.join(target_dir, 'app', 'app.rb'), app_rb)
66
+ ensure_index_html(File.join(target_dir, 'index.html'))
67
+ say "πŸŽ‰ Created new WebRubyUI project in #{target_dir}!", :green
68
+ end
69
+
70
+ private
71
+
72
+ def ensure_index_html(path)
73
+ return if File.exist?(path)
74
+
75
+ html = <<~HTML
76
+ <!DOCTYPE html>
77
+ <html>
78
+ <head>
79
+ <meta charset="utf-8">
80
+ <title>WebRubyUI Application</title>
81
+ <script src="https://cdn.jsdelivr.net/npm/@ruby/3.3-wasm-wasi@2.5.0/dist/browser.script.iife.js"></script>
82
+ </head>
83
+ <body>
84
+ <div id="app">Loading WebRubyUI Wasm...</div>
85
+ <script type="ruby" src="./app/app.rb"></script>
86
+ </body>
87
+ </html>
88
+ HTML
89
+
90
+ File.write(path, html)
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'vnode'
4
+ require_relative 'dom'
5
+
6
+ module WebRubyUI
7
+ # Base Component class providing reactive state tracking and HTML builder DSL.
8
+ class Component
9
+ class << self
10
+ def state(name, default: nil)
11
+ state_definitions[name] = default
12
+
13
+ define_method(name) do
14
+ @state_values[name]
15
+ end
16
+
17
+ define_method("#{name}=") do |new_val|
18
+ old_val = @state_values[name]
19
+ return unless old_val != new_val
20
+
21
+ @state_values[name] = new_val
22
+ re_render!
23
+ end
24
+ end
25
+
26
+ def state_definitions
27
+ @state_definitions ||= {}
28
+ end
29
+ end
30
+
31
+ attr_reader :mount_element_id
32
+
33
+ def initialize(props = {})
34
+ @props = props
35
+ @state_values = {}
36
+ self.class.state_definitions.each do |name, default|
37
+ @state_values[name] = default
38
+ end
39
+ @current_vnode = nil
40
+ @children_stack = []
41
+ end
42
+
43
+ def mount(element_id)
44
+ @mount_element_id = element_id
45
+ re_render!
46
+ end
47
+
48
+ def render
49
+ raise NotImplementedError, "#{self.class.name}#render must be implemented"
50
+ end
51
+
52
+ def re_render!
53
+ new_vnode = build_vnode_tree
54
+ @current_vnode = new_vnode
55
+
56
+ DOM.render_to_element(@mount_element_id, new_vnode) if @mount_element_id
57
+ new_vnode
58
+ end
59
+
60
+ # HTML Builder DSL methods
61
+ %i[div h1 h2 h3 p span button input ul li a section header footer form label].each do |tag_name|
62
+ define_method(tag_name) do |attrs = {}, &block|
63
+ element(tag_name, attrs, &block)
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def build_vnode_tree
70
+ @children_stack = []
71
+ vnode = render
72
+ vnode.is_a?(VNode) ? vnode : VNode.new(tag: :div, text_content: vnode.to_s)
73
+ end
74
+
75
+ def element(tag, attrs = {}, &block)
76
+ children = []
77
+ text_content = nil
78
+
79
+ if block
80
+ current_children = []
81
+ @children_stack.push(current_children)
82
+
83
+ result = instance_eval(&block)
84
+ text_content = result.to_s if current_children.empty? && result && !result.is_a?(VNode)
85
+
86
+ children = @children_stack.pop
87
+ end
88
+
89
+ node = VNode.new(tag: tag, attributes: attrs, children: children, text_content: text_content)
90
+ @children_stack.last << node if @children_stack.any?
91
+ node
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi'
4
+
5
+ module WebRubyUI
6
+ # Bridge to browser DOM via WebAssembly JS interop (JS::Object) or in-memory simulation.
7
+ class DOM
8
+ class << self
9
+ def event_registry
10
+ @event_registry ||= {}
11
+ end
12
+
13
+ def register_event(element_id, event_type, handler)
14
+ key = "#{element_id}_#{event_type}"
15
+ event_registry[key] = handler
16
+ end
17
+
18
+ def trigger_event(element_id, event_type, event_data = {})
19
+ key = "#{element_id}_#{event_type}"
20
+ handler = event_registry[key]
21
+ handler&.call(event_data)
22
+ end
23
+
24
+ def render_to_element(element_id, vnode)
25
+ if Object.const_defined?(:JS)
26
+ js_document = JS.global[:document]
27
+ js_target = js_document.getElementById(element_id.to_s)
28
+ if js_target
29
+ js_target[:innerHTML] = render_html_string(vnode)
30
+ bind_js_events(js_document, vnode)
31
+ end
32
+ end
33
+
34
+ render_html_string(vnode)
35
+ end
36
+
37
+ def render_html_string(vnode)
38
+ return CGI.escapeHTML(vnode.text_content.to_s) unless vnode.is_a?(VNode)
39
+
40
+ attrs = vnode.attributes.map do |k, v|
41
+ next if k.to_s.start_with?('on') # Skip event handlers in HTML attributes
42
+
43
+ "#{k}=\"#{v}\""
44
+ end.compact.join(' ')
45
+
46
+ attr_str = attrs.empty? ? '' : " #{attrs}"
47
+
48
+ if vnode.children.empty? && vnode.text_content
49
+ escaped_text = CGI.escapeHTML(vnode.text_content.to_s)
50
+ "<#{vnode.tag}#{attr_str}>#{escaped_text}</#{vnode.tag}>"
51
+ else
52
+ children_html = vnode.children.map { |c| render_html_string(c) }.join
53
+ "<#{vnode.tag}#{attr_str}>#{children_html}</#{vnode.tag}>"
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def bind_js_events(document, vnode)
60
+ return unless vnode.is_a?(VNode)
61
+
62
+ vnode.attributes.each do |attr_name, handler|
63
+ next unless attr_name.to_s.start_with?('on') && handler.is_a?(Proc)
64
+
65
+ event_type = attr_name.to_s.sub(/\Aon/, '')
66
+ elem_id = vnode.attributes[:id] || "wrui_#{vnode.object_id}"
67
+ register_event(elem_id, event_type, handler)
68
+
69
+ js_elem = document.getElementById(elem_id.to_s)
70
+ next unless js_elem
71
+
72
+ js_elem.addEventListener(event_type) do |e|
73
+ handler.call(e)
74
+ end
75
+ end
76
+
77
+ vnode.children.each { |c| bind_js_events(document, c) }
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebRubyUI
4
+ # Client-side router matching URL paths to WebRubyUI Components.
5
+ class Router
6
+ attr_reader :routes
7
+
8
+ def initialize
9
+ @routes = {}
10
+ end
11
+
12
+ def route(path, component_class)
13
+ @routes[path.to_s] = component_class
14
+ end
15
+
16
+ def match(path)
17
+ clean_path = path.to_s.empty? ? '/' : path.to_s
18
+ @routes[clean_path] || @routes['/']
19
+ end
20
+
21
+ def navigate(path, target_element_id = 'app')
22
+ component_class = match(path)
23
+ return nil unless component_class
24
+
25
+ component = component_class.new(path: path)
26
+ component.mount(target_element_id)
27
+ component
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebRubyUI
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebRubyUI
4
+ # Represents a virtual DOM node in pure Ruby memory.
5
+ class VNode
6
+ attr_reader :tag, :attributes, :children, :text_content
7
+
8
+ def initialize(tag:, attributes: {}, children: [], text_content: nil)
9
+ @tag = tag.to_s.downcase
10
+ @attributes = attributes || {}
11
+ @children = children || []
12
+ @text_content = text_content
13
+ end
14
+
15
+ def to_h
16
+ {
17
+ tag: @tag,
18
+ attributes: @attributes,
19
+ children: @children.map { |c| c.is_a?(VNode) ? c.to_h : c },
20
+ text_content: @text_content
21
+ }
22
+ end
23
+
24
+ def diff(other_vnode)
25
+ patches = []
26
+
27
+ return [{ type: :remove }] if other_vnode.nil?
28
+
29
+ return [{ type: :replace, new_node: other_vnode }] if tag != other_vnode.tag
30
+
31
+ patches << { type: :update_text, new_text: other_vnode.text_content } if text_content != other_vnode.text_content
32
+
33
+ # Attr diffing
34
+ attr_diff = diff_attributes(attributes, other_vnode.attributes)
35
+ patches << { type: :update_attrs, attrs: attr_diff } if attr_diff.any?
36
+
37
+ patches
38
+ end
39
+
40
+ private
41
+
42
+ def diff_attributes(old_attrs, new_attrs)
43
+ diffs = {}
44
+ (old_attrs.keys | new_attrs.keys).each do |k|
45
+ diffs[k] = new_attrs[k] if old_attrs[k] != new_attrs[k]
46
+ end
47
+ diffs
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'web_ruby_ui/version'
4
+ require_relative 'web_ruby_ui/vnode'
5
+ require_relative 'web_ruby_ui/dom'
6
+ require_relative 'web_ruby_ui/component'
7
+ require_relative 'web_ruby_ui/router'
8
+ require_relative 'web_ruby_ui/cli'
9
+
10
+ module WebRubyUI
11
+ class Error < StandardError; end
12
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: web_ruby_ui
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Aditya
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-08-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: webrick
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.8'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.8'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '13.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '13.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.50'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.50'
97
+ description: Build reactive, single-page web applications with components, state management,
98
+ and virtual DOM diffing in pure Ruby running on WebAssembly.
99
+ email:
100
+ executables:
101
+ - web_ruby_ui
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - LICENSE
106
+ - README.md
107
+ - bin/web_ruby_ui
108
+ - lib/web_ruby_ui.rb
109
+ - lib/web_ruby_ui/cli.rb
110
+ - lib/web_ruby_ui/component.rb
111
+ - lib/web_ruby_ui/dom.rb
112
+ - lib/web_ruby_ui/router.rb
113
+ - lib/web_ruby_ui/version.rb
114
+ - lib/web_ruby_ui/vnode.rb
115
+ homepage: https://github.com/aditya-8108/web_ruby_ui
116
+ licenses:
117
+ - MIT
118
+ metadata:
119
+ homepage_uri: https://github.com/aditya-8108/web_ruby_ui
120
+ source_code_uri: https://github.com/aditya-8108/web_ruby_ui
121
+ rubygems_mfa_required: 'true'
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: 3.0.0
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubygems_version: 3.3.5
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: 100% Pure Ruby Frontend Web Framework in WebAssembly
141
+ test_files: []