phlex-slotable 0.3.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +231 -217
- data/benchmark/main.rb +10 -5
- data/lib/phlex/slotable/version.rb +1 -1
- data/lib/phlex/slotable.rb +27 -29
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 663b35110ae8bfad98b0a05e7a5a30afc296f16fc088b4271499ef02b03b9ba2
|
4
|
+
data.tar.gz: eb7153fef5861841d353e5404498dbbaadb8ad1bc7e7f83d6ae82f9bebc0551e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e7fcf450ddbd8bbab8b6eeb22fa54f0de68d9e5611da5c8c1f71e87bd27d2b7497eecdff21b9d61d40006ea7d1f0f782dfa1b4213284a46e8ae8988b8357394
|
7
|
+
data.tar.gz: ad39d88df8841cd7e7599b50b64d48e9b7faefc9abb44b3856749e728bad37c672dbd09fff420324d6b59257f1aaf65d5aef00af614a9a2f509b71e32fb923b3
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.5.0] - 2025-09-08
|
4
|
+
- Prepare for Phlex 2.0 💪
|
5
|
+
- Drop Ruby 2.7, 3.0 and 3.1 support
|
6
|
+
- Add Phlex::Kit compatibility tests
|
7
|
+
- Make `Phlex::Slotable::VERSION` available by default
|
8
|
+
|
9
|
+
## [0.4.0] - 2024-02-14
|
10
|
+
- [BREAKING CHANGE] Rename `many` option to `collection`.
|
11
|
+
|
12
|
+
*stephannv*
|
13
|
+
|
14
|
+
- Improve generic slot performance
|
15
|
+
|
16
|
+
*stephannv*
|
17
|
+
|
3
18
|
## [0.3.1] - 2024-02-14
|
4
19
|
- Support Ruby 2.7
|
5
20
|
|
data/README.md
CHANGED
@@ -6,7 +6,22 @@
|
|
6
6
|
|
7
7
|
Phlex::Slotable enables slots feature to [Phlex](https://www.phlex.fun/) views. Inspired by ViewComponent.
|
8
8
|
|
9
|
-
|
9
|
+
- [What is a slot?](#what-is-a-slot)
|
10
|
+
- [Getting started](#getting-started)
|
11
|
+
- [Generic slot](#generic-slot)
|
12
|
+
- [Slot collection](#slot-collection)
|
13
|
+
- [Component slot](#component-slot)
|
14
|
+
- [Lambda slot](#lambda-slot)
|
15
|
+
- [Polymorphic slot](#polymorphic-slot)
|
16
|
+
- [Performance](#performance)
|
17
|
+
- [Development](#development)
|
18
|
+
- [Contributing](#contributing)
|
19
|
+
|
20
|
+
## What is a slot?
|
21
|
+
|
22
|
+
In the context of view components, a **slot** serves as a placeholder inside a component that can be filled with custom content. Essentially, slots enable a component to accept external content and autonomously organize it within its structure. This abstraction allows developers to work with components without needing to understand their internals, thereby ensuring visual consistency and improving developer experience.
|
23
|
+
|
24
|
+
## Getting started
|
10
25
|
|
11
26
|
Install the gem and add to the application's Gemfile by executing:
|
12
27
|
|
@@ -16,268 +31,272 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
16
31
|
|
17
32
|
$ gem install phlex-slotable
|
18
33
|
|
19
|
-
|
20
|
-
|
21
|
-
#### Basic
|
34
|
+
> [!TIP]
|
35
|
+
> If you prefer not to add another dependency to your project, you can simply copy the [Phlex::Slotable](https://github.com/stephannv/phlex-slotable/blob/main/lib/phlex/slotable.rb) file into your project.
|
22
36
|
|
23
|
-
|
24
|
-
|
25
|
-
- `slot :slot_name` declaration establishes a single slot intended for rendering once within a view
|
26
|
-
- `slot :slot_name, many: true` denotes a slot capable of being rendered multiple times within a view
|
37
|
+
Afterward, simply include `Phlex::Slotable` into your Phlex component and utilize `slot` macro to define the component's slots. For example:
|
27
38
|
|
28
39
|
```ruby
|
29
|
-
class
|
40
|
+
class MyComponent < Phlex::HTML
|
30
41
|
include Phlex::Slotable
|
31
42
|
|
32
|
-
slot :
|
33
|
-
slot :post, many: true
|
34
|
-
|
35
|
-
# ...
|
43
|
+
slot :my_slot
|
36
44
|
end
|
37
45
|
```
|
38
46
|
|
39
|
-
|
47
|
+
Below, you will find a more detailed explanation of how to use the `slot` API.
|
40
48
|
|
41
|
-
|
49
|
+
## Generic slot
|
50
|
+
|
51
|
+
Any content can be passed to components through generic slots, also known as passthrough slots. To define a generic slot, use `slot :{slot_name}`. For example:
|
42
52
|
|
43
53
|
```ruby
|
44
|
-
class
|
54
|
+
class PageComponent < Phlex::HTML
|
45
55
|
include Phlex::Slotable
|
46
56
|
|
47
|
-
slot :
|
48
|
-
|
57
|
+
slot :title
|
58
|
+
end
|
59
|
+
```
|
49
60
|
|
50
|
-
|
51
|
-
div id: "header" do
|
52
|
-
render header_slot
|
53
|
-
end
|
61
|
+
To render a slot, render the `{slot_name}_slot`:
|
54
62
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
end
|
63
|
+
```ruby
|
64
|
+
class PageComponent < Phlex::HTML
|
65
|
+
include Phlex::Slotable
|
59
66
|
|
60
|
-
|
61
|
-
|
67
|
+
slot :title
|
68
|
+
|
69
|
+
def template
|
70
|
+
header { render title_slot }
|
62
71
|
end
|
63
72
|
end
|
64
73
|
```
|
65
74
|
|
66
|
-
|
75
|
+
To pass content to the component's slot, you should use `with_{slot_name}`:
|
67
76
|
|
68
77
|
```ruby
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
blog.with_header do
|
73
|
-
h1 { "Hello World!" }
|
74
|
-
end
|
75
|
-
|
76
|
-
blog.with_post { "Post A" }
|
77
|
-
blog.with_post { "Post B" }
|
78
|
-
blog.with_post { "Post C" }
|
79
|
-
end
|
78
|
+
PageComponent.new.call do |page|
|
79
|
+
page.with_title do
|
80
|
+
h1 { "Hello World!" }
|
80
81
|
end
|
81
82
|
end
|
82
|
-
|
83
|
-
MyPage.new.call
|
84
83
|
```
|
85
84
|
|
86
|
-
|
85
|
+
Returning:
|
87
86
|
|
88
87
|
```html
|
89
|
-
<
|
90
|
-
<h1>Hello World
|
91
|
-
</
|
92
|
-
<div id="main">
|
93
|
-
<p>Post A</p>
|
94
|
-
<p>Post B</p>
|
95
|
-
<p>Post C</p>
|
96
|
-
|
97
|
-
<span>Count: 3</span>
|
98
|
-
</div>
|
88
|
+
<header>
|
89
|
+
<h1>Hello World!</h1>
|
90
|
+
</header>
|
99
91
|
```
|
100
92
|
|
101
|
-
|
102
|
-
|
103
|
-
You can verify whether a slot has been provided to the view using `{slot_name}_slot?` for single slots or `{slot_name}_slots?` when for multi-slots.
|
93
|
+
You can test if a slot has been passed to the component with `{slot_name}_slot?` method. For example:
|
104
94
|
|
105
95
|
```ruby
|
106
|
-
class
|
96
|
+
class PageComponent < Phlex::HTML
|
107
97
|
include Phlex::Slotable
|
108
98
|
|
109
|
-
slot :
|
110
|
-
slot :post, many: true
|
99
|
+
slot :title
|
111
100
|
|
112
101
|
def template
|
113
102
|
if header_slot?
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
end
|
118
|
-
|
119
|
-
div id: "main" do
|
120
|
-
if post_slots?
|
121
|
-
post_slots.each do |slot|
|
122
|
-
p { render slot }
|
123
|
-
end
|
124
|
-
|
125
|
-
span { "Count: #{post_slots.count}" }
|
126
|
-
else
|
127
|
-
span { "No post yet" }
|
128
|
-
end
|
103
|
+
header { render title_slot }
|
104
|
+
else
|
105
|
+
plain "No title"
|
129
106
|
end
|
130
107
|
end
|
131
108
|
end
|
132
109
|
```
|
133
110
|
|
134
|
-
|
111
|
+
## Slot collection
|
135
112
|
|
136
|
-
|
113
|
+
A slot collection denotes a slot capable of being rendered multiple times within a component. It has some minor differences compared to a single slot seen previously. First, you should pass `collection: true` when defining the slot:
|
137
114
|
|
138
115
|
```ruby
|
139
|
-
class
|
140
|
-
|
141
|
-
@size = size
|
142
|
-
end
|
116
|
+
class ListComponent < Phlex::HTML
|
117
|
+
include Phlex::Slotable
|
143
118
|
|
144
|
-
|
145
|
-
h1(class: "text-#{@size}", &content)
|
146
|
-
end
|
119
|
+
slot :item, collection: true
|
147
120
|
end
|
121
|
+
```
|
148
122
|
|
149
|
-
|
150
|
-
def initialize(featured:)
|
151
|
-
@featured = featured
|
152
|
-
end
|
153
|
-
|
154
|
-
def template(&content)
|
155
|
-
p(class: @featured ? "featured" : nil, &content)
|
156
|
-
end
|
157
|
-
end
|
123
|
+
To render a collection of slots, iterate over the `{slot_name}_slots` collection and render each slot individually:
|
158
124
|
|
159
|
-
|
125
|
+
```ruby
|
126
|
+
class ListComponent < Phlex::HTML
|
160
127
|
include Phlex::Slotable
|
161
128
|
|
162
|
-
slot :
|
163
|
-
slot :post, PostComponent, many: true
|
129
|
+
slot :item, collection: true
|
164
130
|
|
165
131
|
def template
|
166
|
-
if
|
167
|
-
|
168
|
-
|
132
|
+
if item_slots?
|
133
|
+
ul do
|
134
|
+
item_slots.each do |item_slot|
|
135
|
+
li { render item_slot }
|
136
|
+
end
|
169
137
|
end
|
170
138
|
end
|
171
139
|
|
172
|
-
|
173
|
-
if post_slots?
|
174
|
-
post_slots.each { render slot }
|
175
|
-
|
176
|
-
span { "Count: #{post_slots.count}" }
|
177
|
-
else
|
178
|
-
span { "No post yet" }
|
179
|
-
end
|
180
|
-
end
|
140
|
+
span { "Total: #{item_slots.size}" }
|
181
141
|
end
|
182
142
|
end
|
143
|
+
```
|
183
144
|
|
184
|
-
|
185
|
-
def template
|
186
|
-
render BlogComponent.new do |blog|
|
187
|
-
blog.with_header(size: :lg) { "Hello World!" }
|
145
|
+
To set slot content, use the `with_{slot_name}` method when rendering the component. Unlike the single slot, `with_{slot_name}` can be called multiple times:
|
188
146
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
147
|
+
```ruby
|
148
|
+
ListComponent.new.call do |list|
|
149
|
+
list.with_item { "Item A" }
|
150
|
+
list.with_item { "Item B" }
|
151
|
+
list.with_item { "Item C" }
|
194
152
|
end
|
195
|
-
|
196
|
-
MyPage.new.call
|
197
153
|
```
|
198
154
|
|
199
|
-
|
155
|
+
Returning:
|
200
156
|
|
201
157
|
```html
|
202
|
-
<
|
203
|
-
<
|
204
|
-
</
|
205
|
-
<
|
206
|
-
|
207
|
-
<p>Post B</p>
|
208
|
-
<p>Post C</p>
|
158
|
+
<ul>
|
159
|
+
<li>Item A</li>
|
160
|
+
<li>Item B</li>
|
161
|
+
<li>Item C</li>
|
162
|
+
</ul>
|
209
163
|
|
210
|
-
|
211
|
-
</div>
|
164
|
+
<span>Total: 3</span>
|
212
165
|
```
|
213
166
|
|
214
|
-
|
215
|
-
```ruby
|
216
|
-
class BlogComponent < Phlex::HTML
|
217
|
-
include Phlex::Slotable
|
218
|
-
|
219
|
-
# This will not work
|
220
|
-
slot :header, HeaderComponent # uninitialized constant BlogComponent::HeaderComponent
|
221
|
-
# You should do this
|
222
|
-
slot :header, "HeaderComponent"
|
167
|
+
## Component slot
|
223
168
|
|
224
|
-
|
169
|
+
Slots have the capability to render other components. When defining a slot, provide the name of a component class as the second argument to define a component slot
|
225
170
|
|
226
|
-
|
227
|
-
|
228
|
-
|
171
|
+
```ruby
|
172
|
+
class ListHeaderComponent < Phlex::HTML
|
173
|
+
# omitted code
|
229
174
|
end
|
230
|
-
```
|
231
175
|
|
232
|
-
|
233
|
-
|
234
|
-
|
176
|
+
class ListItemComponent < Phlex::HTML
|
177
|
+
# omitted code
|
178
|
+
end
|
235
179
|
|
236
|
-
class
|
180
|
+
class ListComponent < Phlex::HTML
|
237
181
|
include Phlex::Slotable
|
238
182
|
|
239
|
-
slot :header,
|
240
|
-
slot :
|
241
|
-
end
|
183
|
+
slot :header, ListHeaderComponent
|
184
|
+
slot :item, ListItemComponent, collection: true
|
242
185
|
|
243
|
-
class MyPage < Phlex::HTML
|
244
186
|
def template
|
245
|
-
|
246
|
-
|
187
|
+
div id: "header" do
|
188
|
+
render header_slot if header_slot?
|
189
|
+
end
|
247
190
|
|
248
|
-
|
249
|
-
|
250
|
-
blog.with_post { "Post C" }
|
191
|
+
ul do
|
192
|
+
item_slots.each { |slot| render slot }
|
251
193
|
end
|
252
194
|
end
|
253
195
|
end
|
196
|
+
|
197
|
+
ListComponent.new.call do |list|
|
198
|
+
list.with_header(size: "lg") { "Hello World!" }
|
199
|
+
|
200
|
+
list.with_item(active: true) { "Item A" }
|
201
|
+
list.with_item { "Item B" }
|
202
|
+
list.with_item { "Item C" }
|
203
|
+
end
|
254
204
|
```
|
255
205
|
|
256
|
-
|
206
|
+
Returning:
|
207
|
+
|
208
|
+
```html
|
209
|
+
<div id="header">
|
210
|
+
<h1 class="text-lg">Hello World!</h1>
|
211
|
+
|
212
|
+
<ul>
|
213
|
+
<li class="active">Item A</li>
|
214
|
+
<li>Item B</li>
|
215
|
+
<li>Item C</li>
|
216
|
+
</ul>
|
217
|
+
</div>
|
218
|
+
```
|
219
|
+
|
220
|
+
> [!TIP]
|
221
|
+
> You can also pass the component class as a string if your component class hasn't been defined yet. For example:
|
222
|
+
>
|
223
|
+
> ```ruby
|
224
|
+
> slot :header, "HeaderComponent"
|
225
|
+
> slot :item, "ItemComponent", collection: true
|
226
|
+
>```
|
227
|
+
|
228
|
+
|
229
|
+
## Lambda slot
|
230
|
+
|
231
|
+
Lambda slots are valuable when you prefer not to create another component for straightforward structures or when you need to render another component with specific parameters.
|
232
|
+
|
257
233
|
```ruby
|
258
|
-
class
|
234
|
+
class ListComponent < Phlex::HTML
|
259
235
|
include Phlex::Slotable
|
260
236
|
|
261
|
-
slot :header, ->(size:, &content)
|
262
|
-
|
263
|
-
def initialize(header_color:)
|
264
|
-
@header_color = header_color
|
237
|
+
slot :header, ->(size:, &content) do
|
238
|
+
render HeaderComponent.new(size: size, color: "primary")
|
265
239
|
end
|
266
|
-
|
240
|
+
slot :item, ->(href:, &content) { li { a(href: href, &content) } }, collection: true
|
267
241
|
|
268
|
-
class MyPage < Phlex::HTML
|
269
242
|
def template
|
270
|
-
|
271
|
-
|
243
|
+
div id: "header" do
|
244
|
+
render header_slot if header_slot?
|
245
|
+
end
|
246
|
+
|
247
|
+
ul do
|
248
|
+
item_slots.each { |slot| render slot }
|
272
249
|
end
|
273
250
|
end
|
274
251
|
end
|
252
|
+
|
253
|
+
ListComponent.new.call do |list|
|
254
|
+
list.with_header(size: "lg") { "Hello World!" }
|
255
|
+
|
256
|
+
list.with_item(href: "/a") { "Item A" }
|
257
|
+
list.with_item(href: "/b") { "Item B" }
|
258
|
+
list.with_item(href: "/c") { "Item C" }
|
259
|
+
end
|
275
260
|
```
|
276
261
|
|
277
|
-
|
278
|
-
|
262
|
+
Returning:
|
263
|
+
|
264
|
+
```html
|
265
|
+
<div id="header">
|
266
|
+
<h1 class="text-lg text-primary">Hello World!</h1>
|
267
|
+
|
268
|
+
<ul>
|
269
|
+
<li><a href="/a">Item A</a></li>
|
270
|
+
<li><a href="/b">Item B</a></li>
|
271
|
+
<li><a href="/c">Item C</a></li>
|
272
|
+
</ul>
|
273
|
+
</div>
|
274
|
+
```
|
275
|
+
|
276
|
+
> [!TIP]
|
277
|
+
> You can access the internal component state within lambda slots. For example
|
278
|
+
>
|
279
|
+
> ```ruby
|
280
|
+
> slot :header, ->(&content) { render HeaderComponent.new(featured: @featured), &content }
|
281
|
+
>
|
282
|
+
> def initialize(featured:)
|
283
|
+
> @featured = feature
|
284
|
+
> end
|
285
|
+
> ```
|
286
|
+
|
287
|
+
## Polymorphic slot
|
288
|
+
|
289
|
+
Polymorphic slots can render one of several possible slots, allowing for flexibility in component content. This feature is particularly useful when you require a fixed structure but need to accommodate different types of content. To implement this, simply pass a types hash containing the types along with corresponding slot definitions.
|
279
290
|
|
280
291
|
```ruby
|
292
|
+
class IconComponent < Phlex::HTML
|
293
|
+
# omitted code
|
294
|
+
end
|
295
|
+
|
296
|
+
class ImageComponent < Phlex::HTML
|
297
|
+
# omitted code
|
298
|
+
end
|
299
|
+
|
281
300
|
class CardComponent < Phlex::HTML
|
282
301
|
include Phlex::Slotable
|
283
302
|
|
@@ -285,74 +304,69 @@ class CardComponent < Phlex::HTML
|
|
285
304
|
|
286
305
|
def template
|
287
306
|
if avatar_slot?
|
288
|
-
|
307
|
+
div id: "avatar" do
|
289
308
|
render avatar_slot
|
290
309
|
end
|
291
310
|
end
|
292
311
|
end
|
293
312
|
end
|
294
|
-
```
|
295
313
|
|
296
|
-
|
297
|
-
|
298
|
-
class UserCardComponent < Phlex::HTML
|
299
|
-
def initialize(user:)
|
300
|
-
@user = user
|
301
|
-
end
|
314
|
+
User = Data.define(:image_url)
|
315
|
+
user = User.new(image_url: "user.png")
|
302
316
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
card.with_icon_avatar(name: :user)
|
309
|
-
end
|
310
|
-
end
|
317
|
+
CardComponent.new.call do |card|
|
318
|
+
if user.image_url
|
319
|
+
card.with_image_avatar(src: user.image_url)
|
320
|
+
else
|
321
|
+
card.with_icon_avatar(name: :user)
|
311
322
|
end
|
312
323
|
end
|
313
324
|
```
|
314
325
|
|
315
|
-
|
316
|
-
```ruby
|
317
|
-
class CardComponent < Phlex::HTML
|
318
|
-
include Phlex::Slotable
|
326
|
+
Returning:
|
319
327
|
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
328
|
+
```html
|
329
|
+
<div id="avatar">
|
330
|
+
<img src="user.png"/>
|
331
|
+
</div>
|
332
|
+
```
|
325
333
|
|
326
|
-
|
327
|
-
if avatar_slots?
|
328
|
-
avatar_slots.each do |slot|
|
329
|
-
render slot
|
330
|
-
end
|
331
|
-
end
|
334
|
+
Note that you need to use `with_{type}_{slot_name}` to set slot content. In the example above, it was used `with_image_avatar` and `with_icon_avatar`.
|
332
335
|
|
333
|
-
|
334
|
-
|
336
|
+
> [!TIP]
|
337
|
+
> You can take advantage of all the previously introduced features, such as lambda slot and slot collection:
|
338
|
+
>
|
339
|
+
> ```ruby
|
340
|
+
> slot :avatar, collection: true, types: {
|
341
|
+
> icon: IconComponent,
|
342
|
+
> image: "ImageComponent",
|
343
|
+
> text: ->(&content) { span(class: "avatar", &content) }
|
344
|
+
> }
|
345
|
+
> ```
|
335
346
|
|
336
|
-
|
337
|
-
|
347
|
+
## Performance
|
348
|
+
Using Phlex::Slotable you don't suffer a performance penalty compared to using Phlex::DeferredRender, sometimes it can even be a little faster.
|
338
349
|
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
350
|
+
```
|
351
|
+
Generated using `ruby benchmark/main.rb`
|
352
|
+
|
353
|
+
Phlex 1.11.0
|
354
|
+
Phlex::Slotable 0.5.0
|
355
|
+
|
356
|
+
ruby 3.3.5 (2024-09-03 revision ef084cc8f4) [arm64-darwin23]
|
357
|
+
Warming up --------------------------------------
|
358
|
+
Deferred 22.176k i/100ms
|
359
|
+
Slotable 23.516k i/100ms
|
360
|
+
Calculating -------------------------------------
|
361
|
+
Deferred 222.727k (± 0.8%) i/s (4.49 μs/i) - 1.131M in 5.078157s
|
362
|
+
Slotable 237.405k (± 0.6%) i/s (4.21 μs/i) - 1.199M in 5.051936s
|
363
|
+
|
364
|
+
Comparison:
|
365
|
+
Slotable: 237405.0 i/s
|
366
|
+
Deferred: 222726.8 i/s - 1.07x slower
|
348
367
|
```
|
349
368
|
|
350
369
|
|
351
|
-
## Roadmap
|
352
|
-
- ✅ ~~Accept Strings as view class name~~
|
353
|
-
- ✅ ~~Allow lambda slots~~
|
354
|
-
- ✅ ~~Allow polymorphic slots~~
|
355
|
-
|
356
370
|
## Development
|
357
371
|
|
358
372
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -361,7 +375,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
361
375
|
|
362
376
|
## Contributing
|
363
377
|
|
364
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/stephannv/phlex-
|
378
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/stephannv/phlex-slotable. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/stephannv/phlex-slotable/blob/master/CODE_OF_CONDUCT.md).
|
365
379
|
|
366
380
|
## License
|
367
381
|
|
data/benchmark/main.rb
CHANGED
@@ -2,6 +2,11 @@ require "benchmark"
|
|
2
2
|
require "benchmark/ips"
|
3
3
|
require_relative "../lib/phlex/slotable"
|
4
4
|
|
5
|
+
require "phlex/version"
|
6
|
+
|
7
|
+
puts "Phlex #{Phlex::VERSION}"
|
8
|
+
puts "Phlex::Slotable #{Phlex::Slotable::VERSION}"
|
9
|
+
|
5
10
|
class DeferredList < Phlex::HTML
|
6
11
|
include Phlex::DeferredRender
|
7
12
|
|
@@ -9,7 +14,7 @@ class DeferredList < Phlex::HTML
|
|
9
14
|
@items = []
|
10
15
|
end
|
11
16
|
|
12
|
-
def
|
17
|
+
def view_template
|
13
18
|
if @header
|
14
19
|
h1(class: "header", &@header)
|
15
20
|
end
|
@@ -34,9 +39,9 @@ class SlotableList < Phlex::HTML
|
|
34
39
|
include Phlex::Slotable
|
35
40
|
|
36
41
|
slot :header
|
37
|
-
slot :item,
|
42
|
+
slot :item, collection: true
|
38
43
|
|
39
|
-
def
|
44
|
+
def view_template
|
40
45
|
if header_slot
|
41
46
|
h1(class: "header", &header_slot)
|
42
47
|
end
|
@@ -50,7 +55,7 @@ class SlotableList < Phlex::HTML
|
|
50
55
|
end
|
51
56
|
|
52
57
|
class DeferredListExample < Phlex::HTML
|
53
|
-
def
|
58
|
+
def view_template
|
54
59
|
render DeferredList.new do |list|
|
55
60
|
list.header do
|
56
61
|
"Header"
|
@@ -68,7 +73,7 @@ class DeferredListExample < Phlex::HTML
|
|
68
73
|
end
|
69
74
|
|
70
75
|
class SlotableListExample < Phlex::HTML
|
71
|
-
def
|
76
|
+
def view_template
|
72
77
|
render SlotableList.new do |list|
|
73
78
|
list.with_header do
|
74
79
|
"Header"
|
data/lib/phlex/slotable.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "phlex"
|
4
|
+
require_relative "slotable/version"
|
4
5
|
|
5
6
|
module Phlex
|
6
7
|
module Slotable
|
@@ -9,41 +10,42 @@ module Phlex
|
|
9
10
|
end
|
10
11
|
|
11
12
|
module ClassMethods
|
12
|
-
def slot(slot_name, callable = nil, types: nil,
|
13
|
+
def slot(slot_name, callable = nil, types: nil, collection: false)
|
13
14
|
include Phlex::DeferredRender
|
14
15
|
|
15
16
|
if types
|
16
17
|
types.each do |type, callable|
|
17
|
-
define_setter_method(slot_name, callable,
|
18
|
+
define_setter_method(slot_name, callable, collection: collection, type: type)
|
18
19
|
end
|
19
20
|
else
|
20
|
-
define_setter_method(slot_name, callable,
|
21
|
+
define_setter_method(slot_name, callable, collection: collection)
|
21
22
|
end
|
22
|
-
define_predicate_method(slot_name,
|
23
|
-
define_getter_method(slot_name,
|
23
|
+
define_predicate_method(slot_name, collection: collection)
|
24
|
+
define_getter_method(slot_name, collection: collection)
|
24
25
|
end
|
25
26
|
|
26
27
|
private
|
27
28
|
|
28
|
-
def define_setter_method(slot_name, callable,
|
29
|
+
def define_setter_method(slot_name, callable, collection:, type: nil)
|
29
30
|
slot_name_with_type = type ? "#{type}_#{slot_name}" : slot_name
|
31
|
+
signature = callable.nil? ? "(&block)" : "(*args, **kwargs, &block)"
|
30
32
|
|
31
|
-
setter_method = if
|
33
|
+
setter_method = if collection
|
32
34
|
<<-RUBY
|
33
|
-
def with_#{slot_name_with_type}
|
35
|
+
def with_#{slot_name_with_type}#{signature}
|
34
36
|
@#{slot_name}_slots ||= []
|
35
37
|
@#{slot_name}_slots << #{callable_value(slot_name_with_type, callable)}
|
36
38
|
end
|
37
39
|
RUBY
|
38
40
|
else
|
39
41
|
<<-RUBY
|
40
|
-
def with_#{slot_name_with_type}
|
42
|
+
def with_#{slot_name_with_type}#{signature}
|
41
43
|
@#{slot_name}_slot = #{callable_value(slot_name_with_type, callable)}
|
42
44
|
end
|
43
45
|
RUBY
|
44
46
|
end
|
45
47
|
|
46
|
-
class_eval(setter_method, __FILE__, __LINE__
|
48
|
+
class_eval(setter_method, __FILE__, __LINE__)
|
47
49
|
define_lambda_method(slot_name_with_type, callable) if callable.is_a?(Proc)
|
48
50
|
end
|
49
51
|
|
@@ -52,44 +54,40 @@ module Phlex
|
|
52
54
|
private :"__call_#{slot_name}__"
|
53
55
|
end
|
54
56
|
|
55
|
-
def define_getter_method(slot_name,
|
56
|
-
getter_method = if
|
57
|
+
def define_getter_method(slot_name, collection:)
|
58
|
+
getter_method = if collection
|
57
59
|
<<-RUBY
|
58
|
-
def #{slot_name}_slots
|
59
|
-
|
60
|
-
end
|
60
|
+
def #{slot_name}_slots = @#{slot_name}_slots ||= []
|
61
|
+
|
61
62
|
private :#{slot_name}_slots
|
62
63
|
RUBY
|
63
64
|
else
|
64
65
|
<<-RUBY
|
65
|
-
def #{slot_name}_slot
|
66
|
-
|
67
|
-
end
|
66
|
+
def #{slot_name}_slot = @#{slot_name}_slot
|
67
|
+
|
68
68
|
private :#{slot_name}_slot
|
69
69
|
RUBY
|
70
70
|
end
|
71
71
|
|
72
|
-
class_eval(getter_method, __FILE__, __LINE__
|
72
|
+
class_eval(getter_method, __FILE__, __LINE__)
|
73
73
|
end
|
74
74
|
|
75
|
-
def define_predicate_method(slot_name,
|
76
|
-
predicate_method = if
|
75
|
+
def define_predicate_method(slot_name, collection:)
|
76
|
+
predicate_method = if collection
|
77
77
|
<<-RUBY
|
78
|
-
def #{slot_name}_slots?
|
79
|
-
|
80
|
-
end
|
78
|
+
def #{slot_name}_slots? = #{slot_name}_slots.any?
|
79
|
+
|
81
80
|
private :#{slot_name}_slots?
|
82
81
|
RUBY
|
83
82
|
else
|
84
83
|
<<-RUBY
|
85
|
-
def #{slot_name}_slot?
|
86
|
-
|
87
|
-
end
|
84
|
+
def #{slot_name}_slot? = !#{slot_name}_slot.nil?
|
85
|
+
|
88
86
|
private :#{slot_name}_slot?
|
89
87
|
RUBY
|
90
88
|
end
|
91
89
|
|
92
|
-
class_eval(predicate_method, __FILE__, __LINE__
|
90
|
+
class_eval(predicate_method, __FILE__, __LINE__)
|
93
91
|
end
|
94
92
|
|
95
93
|
def callable_value(slot_name, callable)
|
@@ -97,7 +95,7 @@ module Phlex
|
|
97
95
|
when nil
|
98
96
|
%(block)
|
99
97
|
when Proc
|
100
|
-
%(-> {
|
98
|
+
%(-> { __call_#{slot_name}__(*args, **kwargs, &block) })
|
101
99
|
else
|
102
100
|
%(#{callable}.new(*args, **kwargs, &block))
|
103
101
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: phlex-slotable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- stephann
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-09-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: phlex
|
@@ -17,6 +17,9 @@ dependencies:
|
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.9'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3'
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -24,6 +27,9 @@ dependencies:
|
|
24
27
|
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
29
|
version: '1.9'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3'
|
27
33
|
description:
|
28
34
|
email:
|
29
35
|
- 3025661+stephannv@users.noreply.github.com
|
@@ -53,14 +59,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
53
59
|
requirements:
|
54
60
|
- - ">="
|
55
61
|
- !ruby/object:Gem::Version
|
56
|
-
version: 2.
|
62
|
+
version: 3.2.0
|
57
63
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
64
|
requirements:
|
59
65
|
- - ">="
|
60
66
|
- !ruby/object:Gem::Version
|
61
67
|
version: '0'
|
62
68
|
requirements: []
|
63
|
-
rubygems_version: 3.5.
|
69
|
+
rubygems_version: 3.5.18
|
64
70
|
signing_key:
|
65
71
|
specification_version: 4
|
66
72
|
summary: Enable Slot API for Phlex views
|