element_component 0.4.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb69cb724b6274a5f95967fbabef32f6e1f5fb9bbf2428775a3237708cc7d6da
4
- data.tar.gz: 50b2252d0c9315efc8c660910d0924dc6506552d09cbd1fc2f49e2309174f2ba
3
+ metadata.gz: a8b23fff0ff2bf1424f46a3db32fb706973f1601f153dd68f9786018979eb1ef
4
+ data.tar.gz: 631db1928c37d32d3a82190a0285e9fc85fb32693dc9fdba0fab9f118476fbcb
5
5
  SHA512:
6
- metadata.gz: 98abc01a0787f60a2998f7f62c611fe51a58eaa8ee629e4bfc885da7ccf6b3a11a9e1f2539d206cfa379222caf5d821206f31558e9f7c47afe1ffa9a3addcaf8
7
- data.tar.gz: bb664c8ad1a7f627f43f1f44898937efeb9c3fc916fca6a65c0b6ae01e1267326d0cff703e476f3748f41cb589b4c0f7c40f236b4787849efcb2029897b3ce78
6
+ metadata.gz: a1cf9210199d4eecaf263df1f99f8862541291cf0fa22fb9c6f92cf55cb1a68f60f72b154503dbce5889a5ffe3c42fd520ce915c7d3e08259d98008692b2d627
7
+ data.tar.gz: 248a8b0ad689bc29c4c8b9c05b42479c6c613789f87d8992ae34cfbc5d06710955117782eb0db683073e8239b2dfc339c65ffbeb4f46e60414427423e87d8c84
data/AGENTS.md ADDED
@@ -0,0 +1,126 @@
1
+ # ElementComponent - Agent Instructions
2
+
3
+ ## Project Overview
4
+ Ruby gem for building HTML programmatically with an object-oriented API. Provides dynamic attribute management, content nesting, and rendering hooks.
5
+
6
+ ## Architecture
7
+
8
+ ```
9
+ lib/
10
+ element_component.rb # Entry point, requires modules
11
+ element_component/
12
+ version.rb # VERSION constant
13
+ element.rb # Core Element class
14
+ components.rb # Component index, requires all components
15
+ components/
16
+ alert.rb # Alert component
17
+ alert/
18
+ heading.rb # AlertHeading component
19
+ link.rb # AlertLink component
20
+ close_button.rb # AlertCloseButton component
21
+ spec/
22
+ element_component_spec.rb # Version check
23
+ lib/
24
+ element_spec.rb # Element unit tests
25
+ components/
26
+ alert_spec.rb # Alert component tests
27
+ spec_helper.rb # RSpec config
28
+ examples/
29
+ alert_example.rb # Complete Alert usage examples
30
+ ```
31
+
32
+ ## Core Classes
33
+
34
+ ### `ElementComponent::Element`
35
+ - **Attributes**: `element` (tag name), `attributes` (Hash), `contents` (Array), `html` (rendered output)
36
+ - **Constructor**: `Element.new(tag_name, closing_tag: true, **attributes)`
37
+ - **Content methods**: `add_content`, `add_content!`, `add_content(&block)`
38
+ - **Attribute methods**: `add_attribute`, `add_attribute!`, `remove_attribute`, `remove_attribute_value`
39
+ - **Render**: `render` (with hooks: `before_render`, `after_render`, `around_render`)
40
+
41
+ ### Pre-built Components
42
+
43
+ Components live under `ElementComponent::Components`. Each component folder contains the main class and its sub-components in separate files.
44
+
45
+ | Component | Class | Description |
46
+ |---|---|---|
47
+ | Alert | `Components::Alert` | `.alert` container with context |
48
+ | Alert Heading | `Components::AlertHeading` | `.alert-heading` (`<h4>`) |
49
+ | Alert Link | `Components::AlertLink` | `.alert-link` (`<a>`) |
50
+ | Alert Close | `Components::AlertCloseButton` | `.btn-close` (self-closing `<button>`) |
51
+
52
+ **Alert constructor**: `Alert.new(context: :primary, dismissible: false, **attributes, &block)`
53
+ - `context`: one of `:primary`, `:secondary`, `:success`, `:danger`, `:warning`, `:info`, `:light`, `:dark`
54
+ - `dismissible`: adds `.alert-dismissible` class and appends a `CloseButton`
55
+ - `&block`: instance_eval DSL for adding content inside the element
56
+
57
+ **Component guidelines**:
58
+ - Always use `add_attribute()` instead of manipulating the `attributes` hash directly
59
+ - Each class in its own file under a component-named folder
60
+ - Call `super("tag_name", closing_tag: ..., &block)` first, then chain `add_attribute` calls
61
+ - `instance_eval(&block)` lives in `Element#initialize` — do NOT repeat it in components
62
+ - Pass user attributes last via `add_attribute(attributes)`
63
+
64
+ ### Sub-component example pattern:
65
+
66
+ ```ruby
67
+ class AlertHeading < Element
68
+ def initialize(**attributes, &block)
69
+ super("h4", &block)
70
+ add_attribute(class: "alert-heading")
71
+ add_attribute(attributes)
72
+ end
73
+ end
74
+ ```
75
+
76
+ ## Coding Conventions
77
+
78
+ - **Language**: Ruby 3.1+
79
+ - **Style**: Double quotes for strings
80
+ - **Testing**: RSpec with `expect` syntax, `subject` pattern
81
+ - **Linting**: RuboCop (run `bundle exec rubocop`)
82
+ - **Pattern matching**: Use `in` pattern matching (`case content; in Element; ...`)
83
+
84
+ ## Common Commands
85
+
86
+ ```bash
87
+ bin/setup # Install dependencies
88
+ bundle exec rspec # Run tests
89
+ bundle exec rubocop # Lint
90
+ bundle exec rake spec # Run tests via rake
91
+ bundle exec rake rubocop # Lint via rake
92
+ bundle exec rake # Default: spec + rubocop
93
+ bin/console # Interactive console
94
+ ruby examples/alert_example.rb # Run Alert examples
95
+ ```
96
+
97
+ ## Testing Patterns
98
+
99
+ - Use `subject` for the element under test
100
+ - Use `before` blocks to set up state
101
+ - Test render output as raw HTML strings
102
+ - Test attribute hashes directly
103
+ - Follow existing describe/context/it structure
104
+
105
+ ## Development Workflow
106
+
107
+ 1. Write/update specs first
108
+ 2. Implement in `lib/element_component/element.rb` or `lib/element_component/components/`
109
+ 3. Run `bundle exec rspec` to verify
110
+ 4. Run `bundle exec rubocop` for linting
111
+ 5. Run `bundle exec rake` for full check
112
+
113
+ ## Key Design Decisions
114
+
115
+ - Attributes stored as Hash of Symbol => Array of values (supports multiple values per attribute)
116
+ - Contents stored as Array (supports strings, Elements, and Procs/blocks)
117
+ - `!` suffix methods reset state before adding (e.g., `add_content!` clears then adds)
118
+ - Hooks (`before_render`, `after_render`, `around_render`) are optional, detected via `respond_to?`
119
+ - Self-closing tags controlled by `closing_tag:` parameter
120
+ - Component classes use `add_attribute()` instead of direct hash manipulation
121
+
122
+ ## Roadmap Features (from README)
123
+ - Caching support
124
+ - Pre-built Bulma components
125
+ - Pre-built Bootstrap components
126
+ - Enhanced DSL for nested structures
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2025 João Paulo Correia
3
+ Copyright (c) 2023 João Paulo Correia
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # ElementComponent
2
2
 
3
- HTML builder
3
+ A lightweight and flexible HTML builder for Ruby. `ElementComponent` provides a simple, object-oriented way to construct HTML structures programmatically, with dynamic attribute management, content nesting, rendering hooks, and pre-built components.
4
4
 
5
5
  ## Installation
6
6
 
7
- Install the gem and add to the application's Gemfile by executing:
7
+ Add to your application's Gemfile:
8
8
 
9
9
  ```bash
10
10
  bundle add element_component
11
11
  ```
12
12
 
13
- If bundler is not being used to manage dependencies, install the gem by executing:
13
+ Or install directly:
14
14
 
15
15
  ```bash
16
16
  gem install element_component
@@ -18,37 +18,187 @@ gem install element_component
18
18
 
19
19
  ## Usage
20
20
 
21
+ ### Creating Elements
22
+
23
+ ```ruby
24
+ p = ElementComponent::Element.new("p", class: "text-bold")
25
+ p.add_content("Hello, World!")
26
+ puts p.render
27
+ # => <p class="text-bold">Hello, World!</p>
28
+ ```
29
+
30
+ ### Block DSL
31
+
32
+ Use a block to add content inline:
33
+
34
+ ```ruby
35
+ div = ElementComponent::Element.new("div", class: "container") do
36
+ add_content("Welcome")
37
+ add_content(ElementComponent::Element.new("h1") { add_content("Title") })
38
+ end
39
+ puts div.render
40
+ # => <div class="container">Welcome<h1>Title</h1></div>
41
+ ```
42
+
43
+ ### The `new_element` Helper
44
+
45
+ Inside a block, use `new_element` as a shorthand:
46
+
47
+ ```ruby
48
+ div = ElementComponent::Element.new("div") do
49
+ add_content(new_element("h1") { add_content("Hello") })
50
+ add_content(new_element("p", class: "lead") { add_content("World") })
51
+ end
52
+ puts div.render
53
+ # => <div><h1>Hello</h1><p class="lead">World</p></div>
54
+ ```
55
+
56
+ ### Content Types
57
+
58
+ Content can be a string, an Element, or a block (Proc):
59
+
60
+ ```ruby
61
+ div = ElementComponent::Element.new("div")
62
+
63
+ # String
64
+ div.add_content("plain text")
65
+
66
+ # Element instance
67
+ div.add_content(ElementComponent::Element.new("span") { add_content("nested") })
68
+
69
+ # Block (evaluated at render time, has access to new_element)
70
+ div.add_content { new_element("em") { add_content("deferred") } }
71
+
72
+ puts div.render
73
+ # => <div>plain text<span>nested</span><em>deferred</em></div>
74
+ ```
75
+
76
+ ### Attribute Management
77
+
78
+ ```ruby
79
+ btn = ElementComponent::Element.new("button", class: "btn", type: "button")
80
+
81
+ # Add more values to an attribute
82
+ btn.add_attribute(class: "btn-primary")
83
+
84
+ # Reset attributes and set new ones
85
+ btn.add_attribute!(id: "submit-btn", type: "submit")
86
+
87
+ # Remove an attribute
88
+ btn.remove_attribute(:type)
89
+
90
+ # Remove a specific value from an attribute
91
+ btn.remove_attribute_value(:class, "btn-primary")
92
+ ```
93
+
94
+ ### Self-Closing Tags
95
+
96
+ ```ruby
97
+ img = ElementComponent::Element.new("img", closing_tag: false, src: "image.png", alt: "Logo")
98
+ puts img.render
99
+ # => <img src="image.png" alt="Logo">
100
+ ```
101
+
102
+ ### Rendering Hooks
103
+
104
+ `ElementComponent` supports `before_render`, `after_render`, and `around_render` hooks:
105
+
21
106
  ```ruby
22
- maker = ElementComponent::Core::Maker.new
23
- form = maker.form(attribute: { class: 'has-background-color', method: 'GET', action: '/', turbo: false }) do |form|
24
- input = maker.input(attribute: { type: 'text', name: 'email', value: nil })
25
- form.add_content input
107
+ div = ElementComponent::Element.new("div")
108
+ div.define_singleton_method(:before_render) { add_attribute(class: "dynamic") }
109
+ div.add_content("content")
110
+ puts div.render
111
+ # => <div class="dynamic">content</div>
112
+ ```
26
113
 
27
- button = maker.button(content: 'Save', attribute: { type: 'submit'})
28
- div = maker.div(content: button, attribute: { class: 'buttons' })
114
+ ## Pre-built Components
29
115
 
30
- form.add_content div
116
+ Components live under `ElementComponent::Components`.
117
+
118
+ ### Alert (Bootstrap 5)
119
+
120
+ ```ruby
121
+ alert = ElementComponent::Components::Alert.new(context: :success) do
122
+ add_content("Operation completed!")
31
123
  end
124
+ puts alert.render
125
+ # => <div class="alert alert-success" role="alert">Operation completed!</div>
126
+ ```
32
127
 
33
- puts form.build
128
+ **Contexts**: `:primary`, `:secondary`, `:success`, `:danger`, `:warning`, `:info`, `:light`, `:dark`
129
+
130
+ #### Dismissible Alert
131
+
132
+ ```ruby
133
+ alert = ElementComponent::Components::Alert.new(context: :warning, dismissible: true) do
134
+ add_content("This can be closed.")
135
+ end
136
+ puts alert.render
137
+ # => <div class="alert alert-warning alert-dismissible" role="alert">
138
+ # This can be closed.
139
+ # <button class="btn-close" data-bs-dismiss="alert" aria-label="Close">
140
+ # </div>
34
141
  ```
35
142
 
36
- ## Output
143
+ #### Alert with Heading and Link
37
144
 
38
- ```html
39
- <form class="has-background-color" method="GET" action="/" turbo="false"><input type="text" name="email" value=""></input><div class="buttons"><button type="submit">Save</button></div></form>
145
+ ```ruby
146
+ alert = ElementComponent::Components::Alert.new(context: :info) do
147
+ add_content(ElementComponent::Components::AlertHeading.new.tap { |h| h.add_content("Notice") })
148
+ add_content("Please review. ")
149
+ add_content(ElementComponent::Components::AlertLink.new(href: "/details").tap { |l| l.add_content("Details") })
150
+ end
151
+ puts alert.render
152
+ # => <div class="alert alert-info" role="alert">
153
+ # <h4 class="alert-heading">Notice</h4>
154
+ # Please review.
155
+ # <a class="alert-link" href="/details">Details</a>
156
+ # </div>
40
157
  ```
41
158
 
159
+ #### Sub-components
160
+
161
+ | Class | Tag | Class |
162
+ |---|---|---|
163
+ | `Alert` | `<div>` | `.alert .alert-{context}` |
164
+ | `AlertHeading` | `<h4>` | `.alert-heading` |
165
+ | `AlertLink` | `<a>` | `.alert-link` |
166
+ | `AlertCloseButton` | `<button>` (self-closing) | `.btn-close` |
167
+
42
168
  ## Development
43
169
 
44
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
170
+ ```bash
171
+ bin/setup # Install dependencies
172
+ bundle exec rspec # Run tests
173
+ bundle exec rubocop # Lint
174
+ bundle exec rake # Spec + RuboCop
175
+ bin/console # Interactive console
176
+ ruby examples/alert_example.rb # Run Alert examples
177
+ ```
178
+
179
+ ### Test Coverage
180
+
181
+ Run with coverage reporting:
182
+
183
+ ```bash
184
+ COVERAGE=true bundle exec rspec
185
+ ```
186
+
187
+ ### Release
188
+
189
+ ```bash
190
+ # Update version in lib/element_component/version.rb
191
+ bundle exec rake release
192
+ ```
193
+
194
+ Or push a version tag (e.g., `v0.6.0`) to trigger the automated release workflow.
45
195
 
46
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
196
+ ## Roadmap
47
197
 
48
- ## TODO
49
- [ ] - Cache
50
- [ ] - Bulma components
51
- [ ] - Bootstrap components
198
+ - [ ] Support for Caching
199
+ - [ ] Pre-built Bulma components
200
+ - [ ] Pre-built Bootstrap components (more)
201
+ - [ ] Enhanced DSL for nested structures
52
202
 
53
203
  ## Contributing
54
204
 
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../lib/element_component"
4
+
5
+ # =============================================================================
6
+ # Basic Alert
7
+ # =============================================================================
8
+ alert = ElementComponent::Components::Alert.new(context: :success)
9
+ alert.add_content("Operation completed successfully!")
10
+ puts "=== Basic Alert ==="
11
+ puts alert.render
12
+ puts
13
+
14
+ # =============================================================================
15
+ # All Contexts
16
+ # =============================================================================
17
+ puts "=== All Alert Contexts ==="
18
+ ElementComponent::Components::Alert::VALID_CONTEXTS.each do |context|
19
+ alert = ElementComponent::Components::Alert.new(context: context)
20
+ alert.add_content("#{context.capitalize} alert message here.")
21
+ puts alert.render
22
+ end
23
+ puts
24
+
25
+ # =============================================================================
26
+ # Dismissible Alert (use block DSL so close button is always last)
27
+ # =============================================================================
28
+ alert = ElementComponent::Components::Alert.new(context: :warning, dismissible: true) do
29
+ add_content("This alert can be dismissed.")
30
+ end
31
+ puts "=== Dismissible Alert ==="
32
+ puts alert.render
33
+ puts
34
+
35
+ # =============================================================================
36
+ # Alert with Custom Attributes
37
+ # =============================================================================
38
+ alert = ElementComponent::Components::Alert.new(
39
+ context: :primary,
40
+ id: "main-alert",
41
+ style: "margin-top: 20px;"
42
+ )
43
+ alert.add_content("This alert has a custom ID and inline style.")
44
+ puts "=== Alert with Custom Attributes ==="
45
+ puts alert.render
46
+ puts
47
+
48
+ # =============================================================================
49
+ # Alert with Heading and Link (block DSL)
50
+ # =============================================================================
51
+ alert = ElementComponent::Components::Alert.new(context: :info) do
52
+ add_content(ElementComponent::Components::AlertHeading.new.tap { |h| h.add_content("Information") })
53
+ add_content("This is an important notice. ")
54
+ add_content(ElementComponent::Components::AlertLink.new(href: "/details").tap { |l| l.add_content("View details") })
55
+ end
56
+ puts "=== Alert with Heading and Link ==="
57
+ puts alert.render
58
+ puts
59
+
60
+ # =============================================================================
61
+ # Dismissible Alert with Heading and Link
62
+ # =============================================================================
63
+ alert = ElementComponent::Components::Alert.new(context: :danger, dismissible: true) do
64
+ add_content(ElementComponent::Components::AlertHeading.new.tap { |h| h.add_content("Error!") })
65
+ add_content("Something went wrong. ")
66
+ add_content(ElementComponent::Components::AlertLink.new(href: "/support").tap do |l|
67
+ l.add_content("Contact support")
68
+ end)
69
+ end
70
+ puts "=== Dismissible Alert with Heading and Link ==="
71
+ puts alert.render
72
+ puts
73
+
74
+ # =============================================================================
75
+ # Chained API Usage
76
+ # =============================================================================
77
+ alert = ElementComponent::Components::Alert.new(context: :secondary)
78
+ .add_content("This alert was built using chained calls.")
79
+ puts "=== Chained API ==="
80
+ puts alert.render
81
+ puts
82
+
83
+ # =============================================================================
84
+ # Using Sub-Components Independently
85
+ # =============================================================================
86
+ heading = ElementComponent::Components::AlertHeading.new
87
+ heading.add_content("Standalone Heading")
88
+
89
+ link = ElementComponent::Components::AlertLink.new(href: "https://example.com")
90
+ link.add_content("Standalone Link")
91
+
92
+ close_btn = ElementComponent::Components::AlertCloseButton.new
93
+
94
+ puts "=== Sub-Components Rendered Independently ==="
95
+ puts heading.render
96
+ puts link.render
97
+ puts close_btn.render
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElementComponent
4
+ module Components
5
+ class AlertCloseButton < Element
6
+ def initialize(**attributes)
7
+ super("button", closing_tag: false)
8
+
9
+ add_attribute(class: "btn-close")
10
+ add_attribute(type: "button")
11
+ add_attribute("data-bs-dismiss": "alert")
12
+ add_attribute("aria-label": "Close")
13
+ add_attribute(attributes) unless attributes.empty?
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElementComponent
4
+ module Components
5
+ class AlertHeading < Element
6
+ def initialize(**attributes)
7
+ super("h4")
8
+
9
+ add_attribute(class: "alert-heading")
10
+ add_attribute(attributes) unless attributes.empty?
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElementComponent
4
+ module Components
5
+ class AlertLink < Element
6
+ def initialize(href: "#", **attributes)
7
+ super("a")
8
+
9
+ add_attribute(class: "alert-link")
10
+ add_attribute(href: href)
11
+ add_attribute(attributes) unless attributes.empty?
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "alert/heading"
4
+ require_relative "alert/link"
5
+ require_relative "alert/close_button"
6
+
7
+ module ElementComponent
8
+ module Components
9
+ class Alert < Element
10
+ VALID_CONTEXTS = %i[primary secondary success danger warning info light dark].freeze
11
+
12
+ def initialize(context: :primary, dismissible: false, **attributes, &block)
13
+ super("div", &block)
14
+
15
+ add_attribute(class: "alert")
16
+ add_attribute(class: "alert-#{context}")
17
+ add_attribute(class: "alert-dismissible") if dismissible
18
+ add_attribute(role: "alert")
19
+
20
+ add_attribute(attributes) unless attributes.empty?
21
+ add_content(AlertCloseButton.new) if dismissible
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "components/alert"
4
+
5
+ module ElementComponent
6
+ module Components
7
+ end
8
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElementComponent
4
+ class Element
5
+ attr_reader :element, :attributes, :contents, :html
6
+
7
+ def initialize(element, closing_tag: true, **attribute, &block)
8
+ @element = element
9
+ @closing_tag = closing_tag
10
+ @html = String.new
11
+
12
+ add_attribute!(attribute)
13
+ reset_contents!
14
+ instance_eval(&block) if block
15
+ end
16
+
17
+ def add_content!(content)
18
+ reset_contents!
19
+
20
+ add_content(content)
21
+ end
22
+
23
+ def add_content(content = nil, &block)
24
+ if block_given?
25
+ @contents.push(block)
26
+ else
27
+ @contents.push(content)
28
+ end
29
+
30
+ self
31
+ end
32
+
33
+ def add_attribute!(hash_attr)
34
+ reset_attributes!
35
+
36
+ add_attribute(hash_attr)
37
+ end
38
+
39
+ def add_attribute(hash_attr)
40
+ hash_attr.each_key do |attr|
41
+ @attributes[attr] = [] unless @attributes.key?(attr)
42
+
43
+ hash_attr[attr]
44
+ .to_s
45
+ .split
46
+ .each do |value|
47
+ @attributes[attr].push(value)
48
+ end
49
+ end
50
+
51
+ self
52
+ end
53
+
54
+ def remove_attribute(attribute)
55
+ @attributes = @attributes.except(attribute)
56
+ end
57
+
58
+ def remove_attribute_value(attribute, value)
59
+ attributes[attribute].delete(value)
60
+ end
61
+
62
+ def reset_contents!
63
+ @contents = []
64
+ end
65
+
66
+ def reset_attributes!
67
+ @attributes = {}
68
+ end
69
+
70
+ def new_element(...) = Element.new(...)
71
+
72
+ def render
73
+ @html = String.new
74
+
75
+ before_render if respond_to? "before_render"
76
+
77
+ if respond_to? "around_render"
78
+ around_render { build }
79
+ else
80
+ build
81
+ end
82
+
83
+ after_render(@html) if respond_to? "after_render"
84
+
85
+ @html
86
+ end
87
+
88
+ private
89
+
90
+ def build
91
+ @html << "<#{@element}"
92
+ @html << (mount_attributes.empty? ? ">" : " #{mount_attributes}>")
93
+ @html << mount_content
94
+ @html << "</#{@element}>" if @closing_tag
95
+ @html
96
+ end
97
+
98
+ def mount_attributes
99
+ @attributes.map { |attr| "#{attr[0].to_sym}=\"#{attr[1].join(" ")}\"" }.join(" ")
100
+ end
101
+
102
+ def mount_content
103
+ @contents.dup.map do |content|
104
+ case content
105
+ in Element
106
+ content.render
107
+ in Proc
108
+ result = instance_eval(&content)
109
+ if result.equal?(self)
110
+ ""
111
+ elsif result.respond_to?(:render)
112
+ result.render
113
+ else
114
+ result.to_s
115
+ end
116
+ else
117
+ content.to_s
118
+ end
119
+ end.join
120
+ end
121
+ end
122
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ElementComponent
4
- VERSION = "0.4.0"
4
+ VERSION = "0.6.0"
5
5
  end
@@ -1,10 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "element_component/version"
4
- require_relative "element_component/core/element"
5
- require_relative "element_component/core/maker"
4
+ require_relative "element_component/element"
5
+ require_relative "element_component/components"
6
6
 
7
7
  module ElementComponent
8
8
  class Error < StandardError; end
9
- # Your code goes here...
10
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: element_component
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - João Paulo Correia
@@ -11,21 +11,26 @@ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: HTML Builder
13
13
  email:
14
- - ijoohn@hotmail.com
14
+ - joaopaulocorreia1010@gmail.com
15
15
  executables: []
16
16
  extensions: []
17
17
  extra_rdoc_files: []
18
18
  files:
19
19
  - ".rspec"
20
20
  - ".rubocop.yml"
21
+ - AGENTS.md
21
22
  - LICENSE.txt
22
23
  - README.md
23
24
  - Rakefile
25
+ - examples/alert_example.rb
24
26
  - lib/element_component.rb
25
- - lib/element_component/core/element.rb
26
- - lib/element_component/core/maker.rb
27
+ - lib/element_component/components.rb
28
+ - lib/element_component/components/alert.rb
29
+ - lib/element_component/components/alert/close_button.rb
30
+ - lib/element_component/components/alert/heading.rb
31
+ - lib/element_component/components/alert/link.rb
32
+ - lib/element_component/element.rb
27
33
  - lib/element_component/version.rb
28
- - sig/element_component.rbs
29
34
  homepage: https://github.com/joaopaulocorreia/element_component
30
35
  licenses:
31
36
  - MIT
@@ -46,7 +51,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
46
51
  - !ruby/object:Gem::Version
47
52
  version: '0'
48
53
  requirements: []
49
- rubygems_version: 3.6.9
54
+ rubygems_version: 4.0.6
50
55
  specification_version: 4
51
56
  summary: HTML Builder
52
57
  test_files: []
@@ -1,80 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ElementComponent
4
- module Core
5
- class Element
6
- attr_reader :element, :attributes, :contents
7
-
8
- def initialize(element, content: [], attribute: {}, closing_tag: true)
9
- @element = element
10
- @closing_tag = closing_tag
11
- @objects = []
12
-
13
- reset_attributes!
14
- reset_contents!
15
-
16
- if content.is_a? Array
17
- content.each { |item| add_content item }
18
- else
19
- add_content content
20
- end
21
-
22
- attribute.each_key { |key| add_attribute key, attribute[key] }
23
- end
24
-
25
- def add_content(content, reset: false)
26
- reset_contents! if reset
27
- @contents.push(content)
28
-
29
- content
30
- end
31
-
32
- def add_attribute(attribute, value, reset: false)
33
- attribute = attribute.to_sym
34
-
35
- @attributes.delete attribute if reset
36
-
37
- return @attributes[attribute].push(value) if @attributes.key?(attribute)
38
-
39
- @attributes[attribute] = [value]
40
- end
41
-
42
- def remove_attribute!(attribute)
43
- attribute = attribute.to_sym
44
- @attributes = @attributes.except(attribute)
45
- end
46
-
47
- def remove_attribute_value(attribute, value)
48
- attribute = attribute.to_sym
49
- attributes[attribute].delete(value)
50
- end
51
-
52
- def reset_contents!
53
- @contents = []
54
- end
55
-
56
- def reset_attributes!
57
- @attributes = {}
58
- end
59
-
60
- def build
61
- html = "<#{@element}"
62
- html << (mount_attributes.empty? ? ">" : " #{mount_attributes}>")
63
-
64
- html << mount_content
65
-
66
- html << "</#{@element}>" if @closing_tag
67
- end
68
-
69
- private
70
-
71
- def mount_attributes
72
- @attributes.map { |attr| "#{attr[0].to_sym}=\"#{attr[1].join(" ")}\"" }.join(" ")
73
- end
74
-
75
- def mount_content
76
- @contents.map { |content| content.is_a?(Element) ? content.build : content.to_s }.join
77
- end
78
- end
79
- end
80
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ElementComponent
4
- module Core
5
- class Maker
6
- def method_missing(method, **args, &block)
7
- new_element = ElementComponent::Core::Element.new(method.to_s.gsub("_", "-"), **args)
8
- block.call new_element if block_given?
9
- new_element
10
- end
11
- end
12
- end
13
- end
@@ -1,4 +0,0 @@
1
- module ElementComponent
2
- VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
- end