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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb8d4d59a3526f01f08e3c152ff5abe083e1a2bf1e66ab929db733356b56c822
4
- data.tar.gz: 07c8c5688a9ad4ddb78f34ca174f478e9103b656c09223c86aed522746b0b299
3
+ metadata.gz: 1a155f85c540ca32801ef3033f92abdc7912a3ff5c533faa19f92b00c774968b
4
+ data.tar.gz: 79de2d1b2b3557009c0d9e98f5ade01418ad7e49b61567c2056898ddfe537ea9
5
5
  SHA512:
6
- metadata.gz: 6e429ae91cf3b0e3ac7b47001e164e65f6c4c6bd45f6c11f4258bb0a3c7cf77fa04594a4f493b887f434809ff11174996b09099273ac1bfac50897b002489158
7
- data.tar.gz: 57c5fb478a987d55dd24e22afda9198f9af0b11056ac3ab2bbf66a52ec420251cae33cfa596caa5074015e241cd5d6754b2f95a04c46ba1951c0190694f92e64
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
- - **Slot support**: `<Card::Header>...</Card::Header>` with `renders_one` and `renders_many`
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
- parent_variable = parent_component[8]
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} do %><% end %>"
138
+ "<% #{parent_variable}.with_#{slot_name} %>"
131
139
  else
132
- "<% #{parent_variable}.with_#{slot_name}(#{param_str}) do %><% end %>"
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
- parent_variable = parent_component[8]
144
- stack << [tag_name, nil, param_str, result.length, :slot, slot_name, false, match.begin(0), nil]
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 before the ` do`
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EvcRails
4
- VERSION = "0.2.2"
4
+ VERSION = "0.3.0"
5
5
  end
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.2.2
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