evc_rails 0.2.2 → 0.3.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 +4 -4
- data/CHANGELOG.md +113 -0
- data/README.md +113 -2
- data/lib/evc_rails/template_handler.rb +41 -11
- data/lib/evc_rails/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a155f85c540ca32801ef3033f92abdc7912a3ff5c533faa19f92b00c774968b
|
4
|
+
data.tar.gz: 79de2d1b2b3557009c0d9e98f5ade01418ad7e49b61567c2056898ddfe537ea9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1039a2e12cc8f11c8855dd0bafd2abd9de98e61a87f9e2c49fe27f7d40a497d30d6f1b05d4da7bfb8b7eede13b75c33ab67df4cb48fe8ab8de4d825284d52388
|
7
|
+
data.tar.gz: c73e72e59cd863f513bf07e60675081c3db3255b77090da206ae8652a8e6c15f1b6079641fd1e27b5dbe0b95e71ae45d80a7975b0af2479b5d4e802b9b102a0d
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [0.3.0] - 2024-12-19
|
9
|
+
|
10
|
+
### Added
|
11
|
+
|
12
|
+
- **Enhanced slot nesting support**: Improved handling of deeply nested slots within components
|
13
|
+
- **Self-closing slot optimization**: Self-closing slots now render as method calls without blocks for better performance
|
14
|
+
- **Stack-based nesting logic**: More robust handling of complex nested component and slot structures
|
15
|
+
|
16
|
+
### Changed
|
17
|
+
|
18
|
+
- **Slot variable naming**: Improved slot variable naming by stripping `with_` prefix for cleaner block variables
|
19
|
+
- **Nested slot context**: Enhanced logic to ensure slots use the nearest component variable unless directly nested inside another slot
|
20
|
+
|
21
|
+
### Fixed
|
22
|
+
|
23
|
+
- **Block variable syntax**: Fixed Ruby block variable syntax to use `do |var|` instead of `|var| do`
|
24
|
+
- **Block variable yielding**: Corrected block variable yielding for complex nested structures
|
25
|
+
- **Slot method compatibility**: Ensured proper compatibility with ViewComponent's generated slot methods
|
26
|
+
- **Test suite improvements**: Updated all tests to reflect the new behavior and added comprehensive test coverage
|
27
|
+
|
28
|
+
## [0.2.3] - 2024-12-19
|
29
|
+
|
30
|
+
### Changed
|
31
|
+
|
32
|
+
- **Block variable yielding with `as`**: The block variable is now always yielded if the `as` attribute is present on a component tag, even if no `<With...>` slot tags are used inside the block. This makes advanced usage (such as calling plural slot methods directly) more ergonomic and predictable.
|
33
|
+
- **Documentation improvements**: Clarified the need for the `as` attribute when using plural slot methods with block variables, and improved wording for advanced usage in the README.
|
34
|
+
- **Test suite**: Added and updated tests to verify block variable yielding with `as` and to match the new behavior.
|
35
|
+
|
36
|
+
## [0.2.2] - 2024-12-19
|
37
|
+
|
38
|
+
### Added
|
39
|
+
|
40
|
+
- **Boolean attribute shorthand**: Support for HTML-style boolean attributes without values
|
41
|
+
- `<Button disabled required />` now converts to `disabled: true, required: true`
|
42
|
+
- Makes templates more concise and readable for boolean parameters
|
43
|
+
- Works with any boolean parameter your component defines
|
44
|
+
|
45
|
+
### Changed
|
46
|
+
|
47
|
+
- Updated test suite to use new snake_case variable naming convention
|
48
|
+
- Improved block variable syntax consistency across all examples
|
49
|
+
|
50
|
+
## [0.2.1] - 2024-12-19
|
51
|
+
|
52
|
+
### Added
|
53
|
+
|
54
|
+
- **Custom block variable naming**: Support for `as` attribute to customize yielded variable names
|
55
|
+
- `<Card as="my_card">` yields `|my_card|` instead of `|card|`
|
56
|
+
- Helps avoid variable name collisions in nested components
|
57
|
+
- Useful for accessing parent component context from nested components
|
58
|
+
|
59
|
+
### Changed
|
60
|
+
|
61
|
+
- **Block variable naming**: Components now yield snake_case variable names by default
|
62
|
+
- `<Card>` now yields `|card|` instead of `|c|`
|
63
|
+
- More descriptive and consistent with Ruby naming conventions
|
64
|
+
- Only yields variables when slots are present in the component
|
65
|
+
|
66
|
+
### Fixed
|
67
|
+
|
68
|
+
- Improved slot method naming to match ViewComponent conventions
|
69
|
+
- `<WithHeader>` maps to `with_header` method (not `header`)
|
70
|
+
- `<WithItem>` maps to `with_item` method for both `renders_one` and `renders_many`
|
71
|
+
- Ensures compatibility with ViewComponent's generated method names
|
72
|
+
|
73
|
+
## [0.2.0] - 2024-12-19
|
74
|
+
|
75
|
+
### Added
|
76
|
+
|
77
|
+
- **Slot support**: Full support for ViewComponent slots with `<With...>` syntax
|
78
|
+
- `<WithHeader>` for `renders_one :header`
|
79
|
+
- `<WithItem>` for `renders_many :items` (uses singular method name)
|
80
|
+
- Support for slot attributes and Ruby expressions
|
81
|
+
- Automatic block variable yielding when slots are present
|
82
|
+
- **Namespaced component support**: Components in subdirectories
|
83
|
+
- `<UI::Button />` maps to `app/components/ui/button_component.rb`
|
84
|
+
- `<Forms::Fields::TextField />` for deeply nested components
|
85
|
+
- **Enhanced error messages**: Line numbers and column positions for better debugging
|
86
|
+
- **Production caching**: Automatic template caching using Rails.cache
|
87
|
+
- **Cache management**: Methods to clear and inspect template cache
|
88
|
+
|
89
|
+
### Changed
|
90
|
+
|
91
|
+
- **Template handler architecture**: Improved performance and reliability
|
92
|
+
- **Error handling**: More descriptive error messages with context
|
93
|
+
|
94
|
+
## [0.1.0] - 2024-12-19
|
95
|
+
|
96
|
+
### Added
|
97
|
+
|
98
|
+
- **Initial release**: Basic JSX-like syntax for ViewComponent
|
99
|
+
- **Self-closing components**: `<Button />` syntax
|
100
|
+
- **Block components**: `<Card>content</Card>` syntax
|
101
|
+
- **Attribute support**: String, Ruby expressions, and multiple attributes
|
102
|
+
- **ERB integration**: Full support for ERB tags and Ruby expressions
|
103
|
+
- **Drop-in replacement**: Works as a replacement for `.erb` files
|
104
|
+
- **Basic caching**: Template compilation caching for performance
|
105
|
+
|
106
|
+
## Version History Notes
|
107
|
+
|
108
|
+
- **0.1.0**: Initial release with core functionality
|
109
|
+
- **0.2.0**: Major feature addition with slots and namespaced components
|
110
|
+
- **0.2.1**: Improved variable naming and slot method compatibility
|
111
|
+
- **0.2.2**: Added boolean attribute shorthand for cleaner templates
|
112
|
+
- **0.2.3**: Added block variable yielding with `as` attribute for more ergonomic advanced usage
|
113
|
+
- **0.3.0**: Enhanced slot nesting support and improved block variable handling for complex component structures
|
data/README.md
CHANGED
@@ -42,10 +42,11 @@ No component modifications required - just install and enjoy easier syntax!
|
|
42
42
|
- **Block components**: `<Container>content</Container>`
|
43
43
|
- **Attributes**: String, Ruby expressions, and multiple attributes
|
44
44
|
- **Namespaced components**: `<UI::Button />`, `<Forms::Fields::TextField />`
|
45
|
-
- **
|
46
|
-
- **Deep nesting**: Complex component hierarchies
|
45
|
+
- **Advanced slot support**: `<WithHeader>...</WithHeader>` with `renders_one` and `renders_many`, including complex nesting
|
46
|
+
- **Deep nesting**: Complex component hierarchies with proper block variable handling
|
47
47
|
- **Production-ready caching** with Rails.cache integration
|
48
48
|
- **Better error messages** with line numbers and column positions
|
49
|
+
- **Boolean attribute shorthand** for cleaner templates
|
49
50
|
|
50
51
|
## Installation
|
51
52
|
|
@@ -230,6 +231,52 @@ Becomes:
|
|
230
231
|
<% end %>
|
231
232
|
```
|
232
233
|
|
234
|
+
#### Self-Closing Slots
|
235
|
+
|
236
|
+
You can also use self-closing slot tags when you don't need to pass content:
|
237
|
+
|
238
|
+
```erb
|
239
|
+
<Card>
|
240
|
+
<WithHeader />
|
241
|
+
<WithBody>
|
242
|
+
<p>This is the body content.</p>
|
243
|
+
</WithBody>
|
244
|
+
</Card>
|
245
|
+
```
|
246
|
+
|
247
|
+
Becomes:
|
248
|
+
|
249
|
+
```erb
|
250
|
+
<%= render CardComponent.new do |card| %>
|
251
|
+
<% card.with_header %>
|
252
|
+
<% card.with_body do %>
|
253
|
+
<p>This is the body content.</p>
|
254
|
+
<% end %>
|
255
|
+
<% end %>
|
256
|
+
```
|
257
|
+
|
258
|
+
#### Slot Attributes with Ruby Expressions
|
259
|
+
|
260
|
+
Slots can accept attributes and Ruby expressions:
|
261
|
+
|
262
|
+
```erb
|
263
|
+
<Card>
|
264
|
+
<WithHeader user={@current_user} class="welcome-header">
|
265
|
+
Welcome, <%= @current_user.name %>
|
266
|
+
</WithHeader>
|
267
|
+
</Card>
|
268
|
+
```
|
269
|
+
|
270
|
+
Becomes:
|
271
|
+
|
272
|
+
```erb
|
273
|
+
<%= render CardComponent.new do |card| %>
|
274
|
+
<% card.with_header(user: @current_user, class: "welcome-header") do %>
|
275
|
+
Welcome, <%= @current_user.name %>
|
276
|
+
<% end %>
|
277
|
+
<% end %>
|
278
|
+
```
|
279
|
+
|
233
280
|
#### Multiple Slots (`renders_many`)
|
234
281
|
|
235
282
|
```ruby
|
@@ -298,6 +345,70 @@ end
|
|
298
345
|
|
299
346
|
This generates distinct variables, `outer_card` and `inner_card`, allowing you to access the context of each component without collision.
|
300
347
|
|
348
|
+
#### Passing a Collection to a Plural Slot (Array Notation)
|
349
|
+
|
350
|
+
You can also pass an array directly to a plural slot method using embedded Ruby inside your EVC template. For this advanced use case, block variables are not inferred automatically and it is necessary to define block variables with the `as` attribute.
|
351
|
+
|
352
|
+
```erb
|
353
|
+
<Navigation as="navigation">
|
354
|
+
<% navigation.with_links([
|
355
|
+
{ name: "Home", href: "/" },
|
356
|
+
{ name: "Pricing", href: "/pricing" },
|
357
|
+
{ name: "Sign Up", href: "/sign-up" }
|
358
|
+
]) %>
|
359
|
+
</Navigation>
|
360
|
+
```
|
361
|
+
|
362
|
+
This is equivalent to the ERB version:
|
363
|
+
|
364
|
+
```erb
|
365
|
+
<%= render NavigationComponent.new do |navigation| %>
|
366
|
+
<% navigation.with_links([
|
367
|
+
{ name: "Home", href: "/" },
|
368
|
+
{ name: "Pricing", href: "/pricing" },
|
369
|
+
{ name: "Sign Up", href: "/sign-up" }
|
370
|
+
]) %>
|
371
|
+
<% end %>
|
372
|
+
```
|
373
|
+
|
374
|
+
You can use this approach for any plural slot method generated by `renders_many`. The block variable (e.g., `navigation`) is always available inside the component block when you use the `as` attribute, even if there are no `<With...>` slot tags present.
|
375
|
+
|
376
|
+
#### Complex Nested Slot Structures
|
377
|
+
|
378
|
+
EVC handles complex nested slot structures with proper block variable scoping:
|
379
|
+
|
380
|
+
```erb
|
381
|
+
<Navigation>
|
382
|
+
<WithLink href={learning_path} text="Learning Path" />
|
383
|
+
<WithLink href={courses_path} text="All Courses" />
|
384
|
+
<WithLink text="Reports">
|
385
|
+
<WithSublink href={reports_users_path} text="Users" />
|
386
|
+
<WithSublink href={reports_activity_path} text="Activity" />
|
387
|
+
</WithLink>
|
388
|
+
<WithFooter>
|
389
|
+
<div>Footer content</div>
|
390
|
+
</WithFooter>
|
391
|
+
</Navigation>
|
392
|
+
```
|
393
|
+
|
394
|
+
Becomes:
|
395
|
+
|
396
|
+
```erb
|
397
|
+
<%= render NavigationComponent.new do |navigation| %>
|
398
|
+
<% navigation.with_link(href: learning_path, text: "Learning Path") %>
|
399
|
+
<% navigation.with_link(href: courses_path, text: "All Courses") %>
|
400
|
+
<% navigation.with_link(text: "Reports") do |link| %>
|
401
|
+
<% link.with_sublink(href: reports_users_path, text: "Users") %>
|
402
|
+
<% link.with_sublink(href: reports_activity_path, text: "Activity") %>
|
403
|
+
<% end %>
|
404
|
+
<% navigation.with_footer do %>
|
405
|
+
<div>Footer content</div>
|
406
|
+
<% end %>
|
407
|
+
<% end %>
|
408
|
+
```
|
409
|
+
|
410
|
+
This demonstrates how EVC properly handles nested slots with correct block variable scoping - the inner `WithSublink` slots use the `link` variable from their parent `WithLink` slot.
|
411
|
+
|
301
412
|
### Mixed Content
|
302
413
|
|
303
414
|
You can mix regular HTML, ERB, and component tags:
|
@@ -113,6 +113,7 @@ module EvcRails
|
|
113
113
|
|
114
114
|
# Determine if this is a slot (e.g., WithHeader, WithPost)
|
115
115
|
parent_component = stack.reverse.find { |item| item[4] == :component }
|
116
|
+
parent_slot_index = stack.rindex { |item| item[4] == :slot }
|
116
117
|
is_slot = false
|
117
118
|
slot_name = nil
|
118
119
|
|
@@ -121,15 +122,22 @@ module EvcRails
|
|
121
122
|
# Convert CamelCase slot name to snake_case
|
122
123
|
slot_name = tag_name[4..].gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
123
124
|
parent_component[6] = true # Mark parent as having a slot
|
125
|
+
# If this slot is nested inside another slot, mark the parent slot as having nested slots
|
126
|
+
stack[parent_slot_index][6] = true if parent_slot_index
|
124
127
|
end
|
125
128
|
|
126
129
|
if is_self_closing
|
127
130
|
if is_slot
|
128
|
-
|
131
|
+
# For self-closing slots, use the most recent slot variable if present, otherwise the component variable
|
132
|
+
parent_variable = if stack.last && stack.last[4] == :slot
|
133
|
+
stack.last[8]
|
134
|
+
else
|
135
|
+
parent_component[8]
|
136
|
+
end
|
129
137
|
result << if param_str.empty?
|
130
|
-
"<% #{parent_variable}.with_#{slot_name}
|
138
|
+
"<% #{parent_variable}.with_#{slot_name} %>"
|
131
139
|
else
|
132
|
-
"<% #{parent_variable}.with_#{slot_name}(#{param_str})
|
140
|
+
"<% #{parent_variable}.with_#{slot_name}(#{param_str}) %>"
|
133
141
|
end
|
134
142
|
else
|
135
143
|
component_class = "#{tag_name}Component"
|
@@ -140,8 +148,20 @@ module EvcRails
|
|
140
148
|
end
|
141
149
|
end
|
142
150
|
elsif is_slot
|
143
|
-
|
144
|
-
stack
|
151
|
+
# For block slots, determine the parent variable
|
152
|
+
if stack.last && stack.last[4] == :slot
|
153
|
+
# The most recent stack entry is a slot; use its variable
|
154
|
+
parent_variable = stack.last[8]
|
155
|
+
# Mark the parent slot as having nested slots
|
156
|
+
stack.last[6] = true
|
157
|
+
else
|
158
|
+
# Otherwise, use the nearest component variable
|
159
|
+
parent_variable = parent_component[8]
|
160
|
+
end
|
161
|
+
# Generate a variable name for this slot (strip 'with_' prefix)
|
162
|
+
slot_variable_name = component_variable_name(tag_name.sub(/^With/, ""))
|
163
|
+
stack << [tag_name, nil, param_str, result.length, :slot, slot_name, false, match.begin(0),
|
164
|
+
slot_variable_name]
|
145
165
|
result << if param_str.empty?
|
146
166
|
"<% #{parent_variable}.with_#{slot_name} do %>"
|
147
167
|
else
|
@@ -150,8 +170,9 @@ module EvcRails
|
|
150
170
|
else
|
151
171
|
component_class = "#{tag_name}Component"
|
152
172
|
variable_name = as_variable || component_variable_name(tag_name)
|
173
|
+
force_block_variable = !as_variable.nil?
|
153
174
|
stack << [tag_name, component_class, param_str, result.length, :component, nil, false, match.begin(0),
|
154
|
-
variable_name]
|
175
|
+
variable_name, force_block_variable]
|
155
176
|
result << if param_str.empty?
|
156
177
|
"<%= render #{component_class}.new do %>"
|
157
178
|
else
|
@@ -188,23 +209,32 @@ module EvcRails
|
|
188
209
|
tag_type = open_tag_data[4]
|
189
210
|
|
190
211
|
if tag_type == :component
|
191
|
-
_tag_name, component_class, param_str, start_pos, _type, _slot_name, slot_used, _open_pos, variable_name = open_tag_data
|
212
|
+
_tag_name, component_class, param_str, start_pos, _type, _slot_name, slot_used, _open_pos, variable_name, force_block_variable = open_tag_data
|
192
213
|
|
193
|
-
# Patch in |variable_name| for component if a slot was used
|
194
|
-
if slot_used
|
214
|
+
# Patch in |variable_name| for component if a slot was used or as_variable was present
|
215
|
+
if slot_used || force_block_variable
|
195
216
|
# More robustly find the end of the `do` block to insert the variable.
|
196
217
|
# This avoids faulty regex matching on complex parameters.
|
197
218
|
relevant_part = result[start_pos..-1]
|
198
219
|
match_for_insertion = /( do)( %>)/.match(relevant_part)
|
199
220
|
if match_for_insertion
|
200
|
-
# Insert the variable name just
|
201
|
-
insertion_point = start_pos + match_for_insertion.begin(1)
|
221
|
+
# Insert the variable name just after the ` do`
|
222
|
+
insertion_point = start_pos + match_for_insertion.begin(1) + match_for_insertion[1].length
|
202
223
|
result.insert(insertion_point, " |#{variable_name}|")
|
203
224
|
end
|
204
225
|
end
|
205
226
|
|
206
227
|
result << "<% end %>"
|
207
228
|
else # It's a slot
|
229
|
+
_tag_name, _component_class, param_str, start_pos, _type, _slot_name, has_nested_slots, _open_pos, slot_variable_name = open_tag_data
|
230
|
+
if has_nested_slots
|
231
|
+
relevant_part = result[start_pos..-1]
|
232
|
+
match_for_insertion = /( do)( %>)/.match(relevant_part)
|
233
|
+
if match_for_insertion
|
234
|
+
insertion_point = start_pos + match_for_insertion.begin(1) + match_for_insertion[1].length
|
235
|
+
result.insert(insertion_point, " |#{slot_variable_name}|")
|
236
|
+
end
|
237
|
+
end
|
208
238
|
result << "<% end %>"
|
209
239
|
end
|
210
240
|
|
data/lib/evc_rails/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: evc_rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- scttymn
|
@@ -77,6 +77,7 @@ extensions: []
|
|
77
77
|
extra_rdoc_files: []
|
78
78
|
files:
|
79
79
|
- ".rubocop.yml"
|
80
|
+
- CHANGELOG.md
|
80
81
|
- LICENSE.txt
|
81
82
|
- README.md
|
82
83
|
- Rakefile
|