phlex-slotable 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.standard.yml +3 -1
- data/CHANGELOG.md +14 -0
- data/README.md +213 -222
- data/benchmark/main.rb +2 -1
- data/lib/phlex/slotable/version.rb +1 -1
- data/lib/phlex/slotable.rb +23 -16
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 47c3384e49460492a4ff914af1ecd7c6a16fa2841f90d49a4b83b84c305cf892
|
4
|
+
data.tar.gz: aed5afef686e5f630b98842de12b18cc9f5634dd9f099b07273a7aa281f2915a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 02c8d0105b65526025feea5fe1aadd7851a96e8406ccb574c0b7bb45d732e54dc85c98f6fbb182af5f57ae8f404cbe17a879bafad42ef6ce9ecd0c168c286d75
|
7
|
+
data.tar.gz: 76a8bfd794fd7a7040bf1699db0dd3001f9d7de0cac0f641515494307a3cb370ab173588e2ba3610553c601f907d1da3990d5580702c1308c008e6d2ce11a327
|
data/.standard.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.4.0] - 2024-02-14
|
4
|
+
- [BREAKING CHANGE] Rename `many` option to `collection`.
|
5
|
+
|
6
|
+
*stephannv*
|
7
|
+
|
8
|
+
- Improve generic slot performance
|
9
|
+
|
10
|
+
*stephannv*
|
11
|
+
|
12
|
+
## [0.3.1] - 2024-02-14
|
13
|
+
- Support Ruby 2.7
|
14
|
+
|
15
|
+
*stephannv*
|
16
|
+
|
3
17
|
## [0.3.0] - 2024-02-14
|
4
18
|
|
5
19
|
- Match Slotable peformance with DeferredRender
|
data/README.md
CHANGED
@@ -2,10 +2,25 @@
|
|
2
2
|
> Please note that Phlex::Slotable is currently under development and may undergo changes to its API before reaching the stable release (1.0.0). As a result, there may be breaking changes that affect its usage.
|
3
3
|
|
4
4
|
# Phlex::Slotable
|
5
|
+
[![CI](https://github.com/stephannv/phlex-slotable/actions/workflows/main.yml/badge.svg)](https://github.com/stephannv/phlex-slotable/actions/workflows/main.yml)
|
5
6
|
|
6
7
|
Phlex::Slotable enables slots feature to [Phlex](https://www.phlex.fun/) views. Inspired by ViewComponent.
|
7
8
|
|
8
|
-
|
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
|
+
- [Development](#development)
|
17
|
+
- [Contributing](#contributing)
|
18
|
+
|
19
|
+
## What is a slot?
|
20
|
+
|
21
|
+
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.
|
22
|
+
|
23
|
+
## Getting started
|
9
24
|
|
10
25
|
Install the gem and add to the application's Gemfile by executing:
|
11
26
|
|
@@ -15,268 +30,272 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
15
30
|
|
16
31
|
$ gem install phlex-slotable
|
17
32
|
|
18
|
-
|
33
|
+
> [!TIP]
|
34
|
+
> 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.
|
35
|
+
|
36
|
+
Afterward, simply include `Phlex::Slotable` into your Phlex component and utilize `slot` macro to define the component's slots. For example:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
class MyComponent < Phlex::HTML
|
40
|
+
include Phlex::Slotable
|
41
|
+
|
42
|
+
slot :my_slot
|
43
|
+
end
|
44
|
+
```
|
19
45
|
|
20
|
-
|
46
|
+
Below, you will find a more detailed explanation of how to use the `slot` API.
|
21
47
|
|
22
|
-
|
48
|
+
## Generic slot
|
23
49
|
|
24
|
-
|
25
|
-
- `slot :slot_name, many: true` denotes a slot capable of being rendered multiple times within a view
|
50
|
+
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:
|
26
51
|
|
27
52
|
```ruby
|
28
|
-
class
|
53
|
+
class PageComponent < Phlex::HTML
|
29
54
|
include Phlex::Slotable
|
30
55
|
|
31
|
-
slot :
|
32
|
-
slot :post, many: true
|
33
|
-
|
34
|
-
# ...
|
56
|
+
slot :title
|
35
57
|
end
|
36
58
|
```
|
37
59
|
|
38
|
-
To render a
|
39
|
-
|
40
|
-
For multi-slot rendering, iterate over the `{slot_name}_slots` collection and and render each slot individually, eg. `post_slots.each { |s| render s }`.
|
60
|
+
To render a slot, render the `{slot_name}_slot`:
|
41
61
|
|
42
62
|
```ruby
|
43
|
-
class
|
63
|
+
class PageComponent < Phlex::HTML
|
44
64
|
include Phlex::Slotable
|
45
65
|
|
46
|
-
slot :
|
47
|
-
slot :post, many: true
|
66
|
+
slot :title
|
48
67
|
|
49
68
|
def template
|
50
|
-
|
51
|
-
|
52
|
-
|
69
|
+
header { render title_slot }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
53
73
|
|
54
|
-
|
55
|
-
post_slots.each do |slot|
|
56
|
-
p { render slot }
|
57
|
-
end
|
74
|
+
To pass content to the component's slot, you should use `with_{slot_name}`:
|
58
75
|
|
59
|
-
|
60
|
-
|
76
|
+
```ruby
|
77
|
+
PageComponent.new.call do |page|
|
78
|
+
page.with_title do
|
79
|
+
h1 { "Hello World!" }
|
61
80
|
end
|
62
81
|
end
|
63
82
|
```
|
64
83
|
|
65
|
-
|
84
|
+
Returning:
|
85
|
+
|
86
|
+
```html
|
87
|
+
<header>
|
88
|
+
<h1>Hello World!</h1>
|
89
|
+
</header>
|
90
|
+
```
|
91
|
+
|
92
|
+
You can test if a slot has been passed to the component with `{slot_name}_slot?` method. For example:
|
66
93
|
|
67
94
|
```ruby
|
68
|
-
class
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
h1 { "Hello World!" }
|
73
|
-
end
|
95
|
+
class PageComponent < Phlex::HTML
|
96
|
+
include Phlex::Slotable
|
97
|
+
|
98
|
+
slot :title
|
74
99
|
|
75
|
-
|
76
|
-
|
77
|
-
|
100
|
+
def template
|
101
|
+
if header_slot?
|
102
|
+
header { render title_slot }
|
103
|
+
else
|
104
|
+
plain "No title"
|
78
105
|
end
|
79
106
|
end
|
80
107
|
end
|
81
|
-
|
82
|
-
MyPage.new.call
|
83
108
|
```
|
84
109
|
|
85
|
-
|
110
|
+
## Slot collection
|
86
111
|
|
87
|
-
|
88
|
-
<div id="header">
|
89
|
-
<h1>Hello World</h1>
|
90
|
-
</div>
|
91
|
-
<div id="main">
|
92
|
-
<p>Post A</p>
|
93
|
-
<p>Post B</p>
|
94
|
-
<p>Post C</p>
|
112
|
+
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:
|
95
113
|
|
96
|
-
|
97
|
-
|
98
|
-
|
114
|
+
```ruby
|
115
|
+
class ListComponent < Phlex::HTML
|
116
|
+
include Phlex::Slotable
|
99
117
|
|
100
|
-
|
118
|
+
slot :item, collection: true
|
119
|
+
end
|
120
|
+
```
|
101
121
|
|
102
|
-
|
122
|
+
To render a collection of slots, iterate over the `{slot_name}_slots` collection and render each slot individually:
|
103
123
|
|
104
124
|
```ruby
|
105
|
-
class
|
106
|
-
|
125
|
+
class ListComponent < Phlex::HTML
|
126
|
+
include Phlex::Slotable
|
107
127
|
|
108
|
-
slot :
|
109
|
-
slot :post, many: true
|
128
|
+
slot :item, collection: true
|
110
129
|
|
111
130
|
def template
|
112
|
-
if
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
end
|
117
|
-
|
118
|
-
div id: "main" do
|
119
|
-
if post_slots?
|
120
|
-
post_slots.each do |slot|
|
121
|
-
p { render slot }
|
131
|
+
if item_slots?
|
132
|
+
ul do
|
133
|
+
item_slots.each do |item_slot|
|
134
|
+
li { render item_slot }
|
122
135
|
end
|
123
|
-
|
124
|
-
span { "Count: #{post_slots.count}" }
|
125
|
-
else
|
126
|
-
span { "No post yet" }
|
127
136
|
end
|
128
137
|
end
|
138
|
+
|
139
|
+
span { "Total: #{item_slots.size}" }
|
129
140
|
end
|
130
141
|
end
|
131
142
|
```
|
132
143
|
|
133
|
-
|
134
|
-
|
135
|
-
Slots have the capability to render other views, Simply pass the view class name to the `slot` method.
|
144
|
+
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:
|
136
145
|
|
137
146
|
```ruby
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
def template(&content)
|
144
|
-
h1(class: "text-#{@size}", &content)
|
145
|
-
end
|
147
|
+
ListComponent.new.call do |list|
|
148
|
+
list.with_item { "Item A" }
|
149
|
+
list.with_item { "Item B" }
|
150
|
+
list.with_item { "Item C" }
|
146
151
|
end
|
152
|
+
```
|
147
153
|
|
148
|
-
|
149
|
-
def initialize(featured:)
|
150
|
-
@featured = featured
|
151
|
-
end
|
154
|
+
Returning:
|
152
155
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
+
```html
|
157
|
+
<ul>
|
158
|
+
<li>Item A</li>
|
159
|
+
<li>Item B</li>
|
160
|
+
<li>Item C</li>
|
161
|
+
</ul>
|
162
|
+
|
163
|
+
<span>Total: 3</span>
|
164
|
+
```
|
165
|
+
|
166
|
+
## Component slot
|
167
|
+
|
168
|
+
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
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
class ListHeaderComponent < Phlex::HTML
|
172
|
+
# omitted code
|
156
173
|
end
|
157
174
|
|
158
|
-
class
|
175
|
+
class ListItemComponent < Phlex::HTML
|
176
|
+
# omitted code
|
177
|
+
end
|
178
|
+
|
179
|
+
class ListComponent < Phlex::HTML
|
159
180
|
include Phlex::Slotable
|
160
181
|
|
161
|
-
slot :header,
|
162
|
-
slot :
|
182
|
+
slot :header, ListHeaderComponent
|
183
|
+
slot :item, ListItemComponent, collection: true
|
163
184
|
|
164
185
|
def template
|
165
|
-
|
166
|
-
|
167
|
-
render header_slot
|
168
|
-
end
|
186
|
+
div id: "header" do
|
187
|
+
render header_slot if header_slot?
|
169
188
|
end
|
170
189
|
|
171
|
-
|
172
|
-
|
173
|
-
post_slots.each { render slot }
|
174
|
-
|
175
|
-
span { "Count: #{post_slots.count}" }
|
176
|
-
else
|
177
|
-
span { "No post yet" }
|
178
|
-
end
|
190
|
+
ul do
|
191
|
+
item_slots.each { |slot| render slot }
|
179
192
|
end
|
180
193
|
end
|
181
194
|
end
|
182
195
|
|
183
|
-
|
184
|
-
|
185
|
-
render BlogComponent.new do |blog|
|
186
|
-
blog.with_header(size: :lg) { "Hello World!" }
|
196
|
+
ListComponent.new.call do |list|
|
197
|
+
list.with_header(size: "lg") { "Hello World!" }
|
187
198
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
end
|
192
|
-
end
|
199
|
+
list.with_item(active: true) { "Item A" }
|
200
|
+
list.with_item { "Item B" }
|
201
|
+
list.with_item { "Item C" }
|
193
202
|
end
|
194
|
-
|
195
|
-
MyPage.new.call
|
196
203
|
```
|
197
204
|
|
198
|
-
|
205
|
+
Returning:
|
199
206
|
|
200
207
|
```html
|
201
208
|
<div id="header">
|
202
|
-
<h1 class="text-lg">Hello World
|
203
|
-
</div>
|
204
|
-
<div id="main">
|
205
|
-
<p class="featured">Post A</p>
|
206
|
-
<p>Post B</p>
|
207
|
-
<p>Post C</p>
|
209
|
+
<h1 class="text-lg">Hello World!</h1>
|
208
210
|
|
209
|
-
<
|
211
|
+
<ul>
|
212
|
+
<li class="active">Item A</li>
|
213
|
+
<li>Item B</li>
|
214
|
+
<li>Item C</li>
|
215
|
+
</ul>
|
210
216
|
</div>
|
211
217
|
```
|
212
218
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
219
|
+
> [!TIP]
|
220
|
+
> You can also pass the component class as a string if your component class hasn't been defined yet. For example:
|
221
|
+
>
|
222
|
+
> ```ruby
|
223
|
+
> slot :header, "HeaderComponent"
|
224
|
+
> slot :item, "ItemComponent", collection: true
|
225
|
+
>```
|
217
226
|
|
218
|
-
# This will not work
|
219
|
-
slot :header, HeaderComponent # uninitialized constant BlogComponent::HeaderComponent
|
220
|
-
# You should do this
|
221
|
-
slot :header, "HeaderComponent"
|
222
227
|
|
223
|
-
|
228
|
+
## Lambda slot
|
224
229
|
|
225
|
-
|
226
|
-
# ...
|
227
|
-
end
|
228
|
-
end
|
229
|
-
```
|
230
|
+
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.
|
230
231
|
|
231
|
-
#### Lambda slots
|
232
|
-
Lambda slots are valuable when you prefer not to create another component for straightforward structures or when you need to render another view with specific parameters
|
233
232
|
```ruby
|
234
|
-
|
235
|
-
class BlogComponent < Phlex::HTML
|
233
|
+
class ListComponent < Phlex::HTML
|
236
234
|
include Phlex::Slotable
|
237
235
|
|
238
|
-
slot :header, ->(size:, &content)
|
239
|
-
|
240
|
-
end
|
236
|
+
slot :header, ->(size:, &content) do
|
237
|
+
render HeaderComponent.new(size: size, color: "primary")
|
238
|
+
end
|
239
|
+
slot :item, ->(href:, &content) { li { a(href: href, &content) } }, collection: true
|
241
240
|
|
242
|
-
class MyPage < Phlex::HTML
|
243
241
|
def template
|
244
|
-
|
245
|
-
|
242
|
+
div id: "header" do
|
243
|
+
render header_slot if header_slot?
|
244
|
+
end
|
246
245
|
|
247
|
-
|
248
|
-
|
249
|
-
blog.with_post { "Post C" }
|
246
|
+
ul do
|
247
|
+
item_slots.each { |slot| render slot }
|
250
248
|
end
|
251
249
|
end
|
252
250
|
end
|
253
|
-
```
|
254
|
-
|
255
|
-
You can access the internal view state within lambda slots. For example:
|
256
|
-
```ruby
|
257
|
-
class BlogComponent < Phlex::HTML
|
258
|
-
include Phlex::Slotable
|
259
251
|
|
260
|
-
|
252
|
+
ListComponent.new.call do |list|
|
253
|
+
list.with_header(size: "lg") { "Hello World!" }
|
261
254
|
|
262
|
-
|
263
|
-
|
264
|
-
|
255
|
+
list.with_item(href: "/a") { "Item A" }
|
256
|
+
list.with_item(href: "/b") { "Item B" }
|
257
|
+
list.with_item(href: "/c") { "Item C" }
|
265
258
|
end
|
259
|
+
```
|
266
260
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
261
|
+
Returning:
|
262
|
+
|
263
|
+
```html
|
264
|
+
<div id="header">
|
265
|
+
<h1 class="text-lg text-primary">Hello World!</h1>
|
266
|
+
|
267
|
+
<ul>
|
268
|
+
<li><a href="/a">Item A</a></li>
|
269
|
+
<li><a href="/b">Item B</a></li>
|
270
|
+
<li><a href="/c">Item C</a></li>
|
271
|
+
</ul>
|
272
|
+
</div>
|
274
273
|
```
|
275
274
|
|
276
|
-
|
277
|
-
|
275
|
+
> [!TIP]
|
276
|
+
> You can access the internal component state within lambda slots. For example
|
277
|
+
>
|
278
|
+
> ```ruby
|
279
|
+
> slot :header, ->(&content) { render HeaderComponent.new(featured: @featured), &content }
|
280
|
+
>
|
281
|
+
> def initialize(featured:)
|
282
|
+
> @featured = feature
|
283
|
+
> end
|
284
|
+
> ```
|
285
|
+
|
286
|
+
## Polymorphic slot
|
287
|
+
|
288
|
+
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.
|
278
289
|
|
279
290
|
```ruby
|
291
|
+
class IconComponent < Phlex::HTML
|
292
|
+
# omitted code
|
293
|
+
end
|
294
|
+
|
295
|
+
class ImageComponent < Phlex::HTML
|
296
|
+
# omitted code
|
297
|
+
end
|
298
|
+
|
280
299
|
class CardComponent < Phlex::HTML
|
281
300
|
include Phlex::Slotable
|
282
301
|
|
@@ -284,73 +303,45 @@ class CardComponent < Phlex::HTML
|
|
284
303
|
|
285
304
|
def template
|
286
305
|
if avatar_slot?
|
287
|
-
|
288
|
-
|
306
|
+
div id: "avatar" do
|
307
|
+
render avatar_slot
|
289
308
|
end
|
290
309
|
end
|
291
310
|
end
|
292
311
|
end
|
293
|
-
```
|
294
312
|
|
295
|
-
|
296
|
-
|
297
|
-
class UserCardComponent < Phlex::HTML
|
298
|
-
def initialize(user:)
|
299
|
-
@user = user
|
300
|
-
end
|
313
|
+
User = Data.define(:image_url)
|
314
|
+
user = User.new(image_url: "user.png")
|
301
315
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
card.with_icon_avatar(name: :user)
|
308
|
-
end
|
309
|
-
end
|
316
|
+
CardComponent.new.call do |card|
|
317
|
+
if user.image_url
|
318
|
+
card.with_image_avatar(src: user.image_url)
|
319
|
+
else
|
320
|
+
card.with_icon_avatar(name: :user)
|
310
321
|
end
|
311
322
|
end
|
312
323
|
```
|
313
324
|
|
314
|
-
|
315
|
-
```ruby
|
316
|
-
class CardComponent < Phlex::HTML
|
317
|
-
include Phlex::Slotable
|
318
|
-
|
319
|
-
slot :avatar, types: {
|
320
|
-
icon: IconComponent,
|
321
|
-
image: "ImageComponent",
|
322
|
-
text: ->(size:, &content) { span(class: "text-#{size}", &content) }
|
323
|
-
}, many: true
|
324
|
-
|
325
|
-
def template
|
326
|
-
if avatar_slots?
|
327
|
-
avatar_slots.each do |slot|
|
328
|
-
render slot
|
329
|
-
end
|
330
|
-
end
|
331
|
-
|
332
|
-
span { "Count: #{avatar_slots.size}" }
|
333
|
-
end
|
325
|
+
Returning:
|
334
326
|
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
def template
|
340
|
-
render CardComponent.new do |card|
|
341
|
-
card.with_image_avatar(src: @user.image)
|
342
|
-
card.with_icon_avatar(name: :user)
|
343
|
-
card.with_text_avatar(size: :lg) { "SV" }
|
344
|
-
end
|
345
|
-
end
|
346
|
-
end
|
327
|
+
```html
|
328
|
+
<div id="avatar">
|
329
|
+
<img src="user.png"/>
|
330
|
+
</div>
|
347
331
|
```
|
348
332
|
|
333
|
+
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`.
|
349
334
|
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
335
|
+
> [!TIP]
|
336
|
+
> You can take advantage of all the previously introduced features, such as lambda slot and slot collection:
|
337
|
+
>
|
338
|
+
> ```ruby
|
339
|
+
> slot :avatar, collection: true, types: {
|
340
|
+
> icon: IconComponent,
|
341
|
+
> image: "ImageComponent",
|
342
|
+
> text: ->(&content) { span(class: "avatar", &content) }
|
343
|
+
> }
|
344
|
+
> ```
|
354
345
|
|
355
346
|
## Development
|
356
347
|
|
@@ -360,7 +351,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
360
351
|
|
361
352
|
## Contributing
|
362
353
|
|
363
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/stephannv/phlex-
|
354
|
+
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).
|
364
355
|
|
365
356
|
## License
|
366
357
|
|
data/benchmark/main.rb
CHANGED
@@ -34,7 +34,7 @@ class SlotableList < Phlex::HTML
|
|
34
34
|
include Phlex::Slotable
|
35
35
|
|
36
36
|
slot :header
|
37
|
-
slot :item,
|
37
|
+
slot :item, collection: true
|
38
38
|
|
39
39
|
def template
|
40
40
|
if header_slot
|
@@ -102,4 +102,5 @@ puts
|
|
102
102
|
Benchmark.ips do |x|
|
103
103
|
x.report("Deferred") { DeferredListExample.new.call }
|
104
104
|
x.report("Slotable") { SlotableListExample.new.call }
|
105
|
+
x.compare!
|
105
106
|
end
|
data/lib/phlex/slotable.rb
CHANGED
@@ -9,35 +9,36 @@ module Phlex
|
|
9
9
|
end
|
10
10
|
|
11
11
|
module ClassMethods
|
12
|
-
def slot(slot_name, callable = nil, types: nil,
|
12
|
+
def slot(slot_name, callable = nil, types: nil, collection: false)
|
13
13
|
include Phlex::DeferredRender
|
14
14
|
|
15
15
|
if types
|
16
16
|
types.each do |type, callable|
|
17
|
-
define_setter_method(slot_name, callable,
|
17
|
+
define_setter_method(slot_name, callable, collection: collection, type: type)
|
18
18
|
end
|
19
19
|
else
|
20
|
-
define_setter_method(slot_name, callable,
|
20
|
+
define_setter_method(slot_name, callable, collection: collection)
|
21
21
|
end
|
22
|
-
define_predicate_method(slot_name,
|
23
|
-
define_getter_method(slot_name,
|
22
|
+
define_predicate_method(slot_name, collection: collection)
|
23
|
+
define_getter_method(slot_name, collection: collection)
|
24
24
|
end
|
25
25
|
|
26
26
|
private
|
27
27
|
|
28
|
-
def define_setter_method(slot_name, callable,
|
28
|
+
def define_setter_method(slot_name, callable, collection:, type: nil)
|
29
29
|
slot_name_with_type = type ? "#{type}_#{slot_name}" : slot_name
|
30
|
+
signature = callable.nil? ? "(&block)" : "(*args, **kwargs, &block)"
|
30
31
|
|
31
|
-
setter_method = if
|
32
|
+
setter_method = if collection
|
32
33
|
<<-RUBY
|
33
|
-
def with_#{slot_name_with_type}
|
34
|
+
def with_#{slot_name_with_type}#{signature}
|
34
35
|
@#{slot_name}_slots ||= []
|
35
36
|
@#{slot_name}_slots << #{callable_value(slot_name_with_type, callable)}
|
36
37
|
end
|
37
38
|
RUBY
|
38
39
|
else
|
39
40
|
<<-RUBY
|
40
|
-
def with_#{slot_name_with_type}
|
41
|
+
def with_#{slot_name_with_type}#{signature}
|
41
42
|
@#{slot_name}_slot = #{callable_value(slot_name_with_type, callable)}
|
42
43
|
end
|
43
44
|
RUBY
|
@@ -52,8 +53,8 @@ module Phlex
|
|
52
53
|
private :"__call_#{slot_name}__"
|
53
54
|
end
|
54
55
|
|
55
|
-
def define_getter_method(slot_name,
|
56
|
-
getter_method = if
|
56
|
+
def define_getter_method(slot_name, collection:)
|
57
|
+
getter_method = if collection
|
57
58
|
<<-RUBY
|
58
59
|
def #{slot_name}_slots
|
59
60
|
@#{slot_name}_slots ||= []
|
@@ -62,7 +63,9 @@ module Phlex
|
|
62
63
|
RUBY
|
63
64
|
else
|
64
65
|
<<-RUBY
|
65
|
-
def #{slot_name}_slot
|
66
|
+
def #{slot_name}_slot
|
67
|
+
@#{slot_name}_slot
|
68
|
+
end
|
66
69
|
private :#{slot_name}_slot
|
67
70
|
RUBY
|
68
71
|
end
|
@@ -70,15 +73,19 @@ module Phlex
|
|
70
73
|
class_eval(getter_method, __FILE__, __LINE__ + 1)
|
71
74
|
end
|
72
75
|
|
73
|
-
def define_predicate_method(slot_name,
|
74
|
-
predicate_method = if
|
76
|
+
def define_predicate_method(slot_name, collection:)
|
77
|
+
predicate_method = if collection
|
75
78
|
<<-RUBY
|
76
|
-
def #{slot_name}_slots?
|
79
|
+
def #{slot_name}_slots?
|
80
|
+
#{slot_name}_slots.any?
|
81
|
+
end
|
77
82
|
private :#{slot_name}_slots?
|
78
83
|
RUBY
|
79
84
|
else
|
80
85
|
<<-RUBY
|
81
|
-
def #{slot_name}_slot?
|
86
|
+
def #{slot_name}_slot?
|
87
|
+
!#{slot_name}_slot.nil?
|
88
|
+
end
|
82
89
|
private :#{slot_name}_slot?
|
83
90
|
RUBY
|
84
91
|
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.4.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-02-
|
11
|
+
date: 2024-02-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: phlex
|