tailmix 0.4.7 → 0.4.8
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 +8 -0
- data/README.md +46 -205
- data/app/javascript/tailmix/runtime/action_dispatcher.js +132 -0
- data/app/javascript/tailmix/runtime/component.js +130 -0
- data/app/javascript/tailmix/runtime/index.js +130 -0
- data/app/javascript/tailmix/runtime/plugins.js +35 -0
- data/app/javascript/tailmix/runtime/updater.js +140 -0
- data/docs/01_getting_started.md +0 -81
- data/examples/_modal_component.arb +17 -25
- data/examples/modal_component.rb +31 -155
- data/lib/tailmix/definition/builders/action_builder.rb +39 -0
- data/lib/tailmix/definition/{contexts → builders}/actions/element_builder.rb +1 -1
- data/lib/tailmix/definition/{contexts → builders}/attribute_builder.rb +1 -1
- data/lib/tailmix/definition/builders/component_builder.rb +81 -0
- data/lib/tailmix/definition/{contexts → builders}/dimension_builder.rb +2 -2
- data/lib/tailmix/definition/builders/element_builder.rb +83 -0
- data/lib/tailmix/definition/builders/reactor_builder.rb +43 -0
- data/lib/tailmix/definition/builders/rule_builder.rb +58 -0
- data/lib/tailmix/definition/builders/state_builder.rb +21 -0
- data/lib/tailmix/definition/{contexts → builders}/variant_builder.rb +17 -2
- data/lib/tailmix/definition/context_builder.rb +17 -12
- data/lib/tailmix/definition/payload_proxy.rb +16 -0
- data/lib/tailmix/definition/result.rb +17 -19
- data/lib/tailmix/dev/docs.rb +3 -9
- data/lib/tailmix/dev/tools.rb +0 -5
- data/lib/tailmix/dsl.rb +3 -5
- data/lib/tailmix/engine.rb +11 -1
- data/lib/tailmix/html/attributes.rb +22 -12
- data/lib/tailmix/middleware/registry_cleaner.rb +17 -0
- data/lib/tailmix/registry.rb +37 -0
- data/lib/tailmix/runtime/action_proxy.rb +29 -0
- data/lib/tailmix/runtime/attribute_builder.rb +102 -0
- data/lib/tailmix/runtime/attribute_cache.rb +23 -0
- data/lib/tailmix/runtime/context.rb +51 -62
- data/lib/tailmix/runtime/facade_builder.rb +8 -5
- data/lib/tailmix/runtime/state.rb +36 -0
- data/lib/tailmix/runtime/state_proxy.rb +34 -0
- data/lib/tailmix/runtime.rb +0 -1
- data/lib/tailmix/version.rb +1 -1
- data/lib/tailmix/view_helpers.rb +49 -0
- data/lib/tailmix.rb +4 -2
- metadata +26 -20
- data/app/javascript/tailmix/finder.js +0 -15
- data/app/javascript/tailmix/index.js +0 -7
- data/app/javascript/tailmix/mutator.js +0 -28
- data/app/javascript/tailmix/runner.js +0 -7
- data/app/javascript/tailmix/stimulus_adapter.js +0 -37
- data/docs/02_dsl_reference.md +0 -266
- data/docs/03_advanced_usage.md +0 -88
- data/docs/04_client_side_bridge.md +0 -119
- data/docs/05_cookbook.md +0 -249
- data/lib/tailmix/definition/contexts/action_builder.rb +0 -31
- data/lib/tailmix/definition/contexts/element_builder.rb +0 -55
- data/lib/tailmix/definition/contexts/stimulus_builder.rb +0 -101
- data/lib/tailmix/dev/stimulus_generator.rb +0 -124
- data/lib/tailmix/runtime/stimulus/compiler.rb +0 -59
data/docs/05_cookbook.md
DELETED
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
# Cookbook
|
|
2
|
-
|
|
3
|
-
This document contains practical recipes for building common UI components with Tailmix.
|
|
4
|
-
|
|
5
|
-
## Alert Component
|
|
6
|
-
|
|
7
|
-
Alerts are used to communicate a state that affects the entire system, feature, or page.
|
|
8
|
-
|
|
9
|
-
### 1. Component Definition
|
|
10
|
-
|
|
11
|
-
Here's the Ruby code for a flexible `AlertComponent`.
|
|
12
|
-
|
|
13
|
-
```ruby
|
|
14
|
-
# app/components/alert_component.rb
|
|
15
|
-
class AlertComponent
|
|
16
|
-
include Tailmix
|
|
17
|
-
attr_reader :ui, :icon_svg, :message
|
|
18
|
-
|
|
19
|
-
def initialize(intent: :info, message:)
|
|
20
|
-
@ui = tailmix(intent: intent)
|
|
21
|
-
@message = message
|
|
22
|
-
@icon_svg = fetch_icon(intent) # Logic to get the correct SVG icon
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
private
|
|
26
|
-
|
|
27
|
-
def fetch_icon(intent)
|
|
28
|
-
# ...
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
tailmix do
|
|
32
|
-
element :wrapper, "flex items-center p-4 text-sm border rounded-lg" do
|
|
33
|
-
dimension :intent, default: :info do
|
|
34
|
-
variant :info, "text-blue-800 bg-blue-50 border-blue-300"
|
|
35
|
-
variant :success, "text-green-800 bg-green-50 border-green-300"
|
|
36
|
-
variant :warning, "text-yellow-800 bg-yellow-50 border-yellow-300"
|
|
37
|
-
variant :danger, "text-red-800 bg-red-50 border-red-300"
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
element :icon, "flex-shrink-0 w-5 h-5"
|
|
42
|
-
element :message_area, "ml-3"
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
#### View Usage (ERB)
|
|
48
|
-
Instantiate the component in your controller or view and use the ui helper to render the elements.
|
|
49
|
-
|
|
50
|
-
```html
|
|
51
|
-
<%# Success Alert %>
|
|
52
|
-
<% success_alert = AlertComponent.new(intent: :success, message: "Your profile has been updated.") %>
|
|
53
|
-
|
|
54
|
-
<div <%= tag.attributes **success_alert.ui.wrapper %>>
|
|
55
|
-
<div <%= tag.attributes **success_alert.ui.icon %>>
|
|
56
|
-
<%= success_alert.icon_svg.html_safe %>
|
|
57
|
-
</div>
|
|
58
|
-
<div <%= tag.attributes **success_alert.ui.message_area %>>
|
|
59
|
-
<%= success_alert.message %>
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<%# Danger Alert %>
|
|
65
|
-
<% danger_alert = AlertComponent.new(intent: :danger, message: "Failed to delete the record.") %>
|
|
66
|
-
|
|
67
|
-
<div <%= tag.attributes **danger_alert.ui.wrapper %>>
|
|
68
|
-
<div <%= tag.attributes **danger_alert.ui.icon %>>
|
|
69
|
-
<%= danger_alert.icon_svg.html_safe %>
|
|
70
|
-
</div>
|
|
71
|
-
<div <%= tag.attributes **danger_alert.ui.message_area %>>
|
|
72
|
-
<%= danger_alert.message %>
|
|
73
|
-
</div>
|
|
74
|
-
</div>
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
#### View Usage .arb (Ruby Arbre)
|
|
78
|
-
|
|
79
|
-
```ruby
|
|
80
|
-
# Success Alert
|
|
81
|
-
success_alert = AlertComponent.new(intent: :success, message: "Your profile has been updated.")
|
|
82
|
-
ui = success_alert.ui
|
|
83
|
-
|
|
84
|
-
div ui.wrapper do
|
|
85
|
-
div ui.icon do
|
|
86
|
-
success_alert.icon_svg.html_safe
|
|
87
|
-
end
|
|
88
|
-
div ui.message_area do
|
|
89
|
-
success_alert.message
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Danger Alert
|
|
94
|
-
danger_alert = AlertComponent.new(intent: :danger, message: "Failed to delete the record.")
|
|
95
|
-
ui = danger_alert.ui
|
|
96
|
-
|
|
97
|
-
div ui.wrapper do
|
|
98
|
-
div ui.icon do
|
|
99
|
-
danger_alert.icon_svg.html_safe
|
|
100
|
-
end
|
|
101
|
-
div ui.message_area do
|
|
102
|
-
danger_alert.message
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
## Badge Component
|
|
108
|
-
|
|
109
|
-
Badges are used for labeling, categorization, or highlighting small pieces of information. This recipe shows how to combine multiple dimensions like `size` and `color`.
|
|
110
|
-
|
|
111
|
-
### 1. Component Definition
|
|
112
|
-
|
|
113
|
-
```ruby
|
|
114
|
-
# app/components/badge_component.rb
|
|
115
|
-
class BadgeComponent
|
|
116
|
-
include Tailmix
|
|
117
|
-
attr_reader :ui, :text
|
|
118
|
-
|
|
119
|
-
def initialize(text, color: :gray, size: :sm)
|
|
120
|
-
@ui = tailmix(color: color, size: size)
|
|
121
|
-
@text = text
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
tailmix do
|
|
125
|
-
element :badge, "inline-flex items-center font-medium rounded-full" do
|
|
126
|
-
dimension :color, default: :gray do
|
|
127
|
-
variant :gray, "bg-gray-100 text-gray-600"
|
|
128
|
-
variant :success, "bg-green-100 text-green-700"
|
|
129
|
-
variant :warning, "bg-yellow-100 text-yellow-700"
|
|
130
|
-
variant :danger, "bg-red-100 text-red-700"
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
dimension :size, default: :sm do
|
|
134
|
-
variant :sm, "px-2.5 py-0.5 text-xs"
|
|
135
|
-
variant :md, "px-3 py-1 text-sm"
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
#### View Usage (ERB)
|
|
143
|
-
|
|
144
|
-
```html
|
|
145
|
-
<%# A medium-sized success badge %>
|
|
146
|
-
<% success_badge = BadgeComponent.new("Active", color: :success, size: :md) %>
|
|
147
|
-
<span <%= tag.attributes **success_badge.ui.badge %>>
|
|
148
|
-
<%= success_badge.text %>
|
|
149
|
-
</span>
|
|
150
|
-
|
|
151
|
-
<%# A small danger badge %>
|
|
152
|
-
<% danger_badge = BadgeComponent.new("Inactive", color: :danger, size: :sm) %>
|
|
153
|
-
<span <%= tag.attributes **danger_badge.ui.badge %>>
|
|
154
|
-
<%= danger_badge.text %>
|
|
155
|
-
</span>
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
#### View Usage .arb (Ruby Arbre)
|
|
159
|
-
|
|
160
|
-
```ruby
|
|
161
|
-
# A medium-sized success badge
|
|
162
|
-
success_badge = BadgeComponent.new("Active", color: :success, size: :md)
|
|
163
|
-
ui = success_badge.ui
|
|
164
|
-
|
|
165
|
-
span ui.badge do
|
|
166
|
-
success_badge.text
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
# A small danger badge
|
|
170
|
-
danger_badge = BadgeComponent.new("Inactive", color: :danger, size: :sm)
|
|
171
|
-
ui = danger_badge.ui
|
|
172
|
-
|
|
173
|
-
span ui.badge do
|
|
174
|
-
danger_badge.text
|
|
175
|
-
end
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
## Card Component
|
|
179
|
-
|
|
180
|
-
Cards are flexible containers for content. This recipe shows how to build a component with multiple parts (`header`, `body`, `footer`) using multiple `element` definitions.
|
|
181
|
-
|
|
182
|
-
### 1. Component Definition
|
|
183
|
-
|
|
184
|
-
```ruby
|
|
185
|
-
# app/components/card_component.rb
|
|
186
|
-
class CardComponent
|
|
187
|
-
include Tailmix
|
|
188
|
-
attr_reader :ui
|
|
189
|
-
|
|
190
|
-
# We can control the footer's top border with an option
|
|
191
|
-
def initialize(with_divider: true)
|
|
192
|
-
@ui = tailmix(with_divider: with_divider)
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
tailmix do
|
|
196
|
-
element :wrapper, "bg-white border rounded-lg shadow-sm"
|
|
197
|
-
element :header, "p-4 border-b"
|
|
198
|
-
element :body, "p-4"
|
|
199
|
-
element :footer, "p-4 bg-gray-50 rounded-b-lg" do
|
|
200
|
-
dimension :with_divider, default: true do
|
|
201
|
-
variant true, "border-t"
|
|
202
|
-
variant false, "" # No border
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
#### View Usage (ERB)
|
|
210
|
-
|
|
211
|
-
```html
|
|
212
|
-
<% card = CardComponent.new %>
|
|
213
|
-
|
|
214
|
-
<div <%= tag.attributes **card.ui.wrapper %>>
|
|
215
|
-
<div <%= tag.attributes **card.ui.header %>>
|
|
216
|
-
<h3 class="text-lg font-medium">Card Title</h3>
|
|
217
|
-
</div>
|
|
218
|
-
|
|
219
|
-
<div <%= tag.attributes **card.ui.body %>>
|
|
220
|
-
<p>This is the main content of the card. It can contain any information you need to display.</p>
|
|
221
|
-
</div>
|
|
222
|
-
|
|
223
|
-
<div <%= tag.attributes **card.ui.footer %>>
|
|
224
|
-
<button type="button">Action Button</button>
|
|
225
|
-
</div>
|
|
226
|
-
</div>
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
#### View Usage .arb (Ruby Arbre)
|
|
230
|
-
|
|
231
|
-
```ruby
|
|
232
|
-
# Card
|
|
233
|
-
card = CardComponent.new
|
|
234
|
-
ui = card.ui
|
|
235
|
-
|
|
236
|
-
div ui.wrapper do
|
|
237
|
-
div ui.header do
|
|
238
|
-
h3 "Card Title"
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
div ui.body do
|
|
242
|
-
para "This is the main content of the card. It can contain any information you need to display."
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
div ui.footer do
|
|
246
|
-
button "Action Button"
|
|
247
|
-
end
|
|
248
|
-
end
|
|
249
|
-
```
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "actions/element_builder"
|
|
4
|
-
|
|
5
|
-
module Tailmix
|
|
6
|
-
module Definition
|
|
7
|
-
module Contexts
|
|
8
|
-
class ActionBuilder
|
|
9
|
-
def initialize(method)
|
|
10
|
-
@method = method
|
|
11
|
-
@mutations = {}
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def element(name, &block)
|
|
15
|
-
builder = Actions::ElementBuilder.new(@method)
|
|
16
|
-
builder.instance_eval(&block)
|
|
17
|
-
|
|
18
|
-
commands = builder.build_commands
|
|
19
|
-
@mutations[name.to_sym] = commands unless commands.empty?
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def build_definition
|
|
23
|
-
Definition::Result::Action.new(
|
|
24
|
-
action: @method,
|
|
25
|
-
mutations: @mutations.freeze
|
|
26
|
-
)
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "attribute_builder"
|
|
4
|
-
require_relative "stimulus_builder"
|
|
5
|
-
require_relative "dimension_builder"
|
|
6
|
-
require_relative "variant_builder"
|
|
7
|
-
|
|
8
|
-
module Tailmix
|
|
9
|
-
module Definition
|
|
10
|
-
module Contexts
|
|
11
|
-
class ElementBuilder
|
|
12
|
-
def initialize(name)
|
|
13
|
-
@name = name
|
|
14
|
-
@dimensions = {}
|
|
15
|
-
@compound_variants = []
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def attributes
|
|
19
|
-
@attributes_builder ||= AttributeBuilder.new
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def stimulus
|
|
23
|
-
@stimulus_builder ||= StimulusBuilder.new
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def dimension(name, default: nil, &block)
|
|
27
|
-
builder = Contexts::DimensionBuilder.new(default: default)
|
|
28
|
-
builder.instance_eval(&block)
|
|
29
|
-
@dimensions[name.to_sym] = builder.build_dimension
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def compound_variant(on:, &block)
|
|
33
|
-
builder = VariantBuilder.new
|
|
34
|
-
builder.instance_eval(&block)
|
|
35
|
-
|
|
36
|
-
@compound_variants << {
|
|
37
|
-
on: on,
|
|
38
|
-
modifications: builder.build_variant
|
|
39
|
-
}
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def build_definition
|
|
44
|
-
Definition::Result::Element.new(
|
|
45
|
-
name: @name,
|
|
46
|
-
attributes: attributes.build_definition,
|
|
47
|
-
stimulus: stimulus.build_definition,
|
|
48
|
-
dimensions: @dimensions.freeze,
|
|
49
|
-
compound_variants: @compound_variants.freeze
|
|
50
|
-
)
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Tailmix
|
|
4
|
-
module Definition
|
|
5
|
-
module Contexts
|
|
6
|
-
class StimulusBuilder
|
|
7
|
-
attr_reader :definitions
|
|
8
|
-
|
|
9
|
-
def initialize
|
|
10
|
-
@definitions = []
|
|
11
|
-
@current_context = nil
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def controller(name)
|
|
15
|
-
@definitions << { type: :controller, name: name }
|
|
16
|
-
context(name)
|
|
17
|
-
end
|
|
18
|
-
alias_method :ctr, :controller
|
|
19
|
-
|
|
20
|
-
def context(name)
|
|
21
|
-
@current_context = name.to_s
|
|
22
|
-
self
|
|
23
|
-
end
|
|
24
|
-
alias_method :ctx, :context
|
|
25
|
-
|
|
26
|
-
def target(target_name)
|
|
27
|
-
raise "A controller context must be set..." unless @current_context
|
|
28
|
-
@definitions << { type: :target, controller: @current_context, name: target_name }
|
|
29
|
-
self
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def action(*args)
|
|
33
|
-
ensure_context!
|
|
34
|
-
|
|
35
|
-
action_data = case args.first
|
|
36
|
-
when String
|
|
37
|
-
{ type: :raw, content: args.first }
|
|
38
|
-
when Hash
|
|
39
|
-
{ type: :hash, content: args.first }
|
|
40
|
-
else
|
|
41
|
-
{ type: :tuple, content: args }
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
@definitions << { type: :action, controller: @current_context, data: action_data }
|
|
45
|
-
self
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def value(value_name, value: nil, call: nil, method: nil)
|
|
49
|
-
ensure_context!
|
|
50
|
-
|
|
51
|
-
source = if !value.nil?
|
|
52
|
-
{ type: :literal, content: value }
|
|
53
|
-
elsif call.is_a?(Proc)
|
|
54
|
-
{ type: :proc, content: call }
|
|
55
|
-
elsif method.is_a?(Symbol) || method.is_a?(String)
|
|
56
|
-
{ type: :method, content: method.to_sym }
|
|
57
|
-
else
|
|
58
|
-
raise ArgumentError, "You must provide one of value:, call:, or method: keyword arguments."
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
@definitions << {
|
|
62
|
-
type: :value,
|
|
63
|
-
controller: @current_context,
|
|
64
|
-
name: value_name,
|
|
65
|
-
source: source
|
|
66
|
-
}
|
|
67
|
-
self
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def action_payload(action_name, as: nil)
|
|
71
|
-
ensure_context!
|
|
72
|
-
value_name = as || "#{action_name}_action"
|
|
73
|
-
|
|
74
|
-
@definitions << {
|
|
75
|
-
type: :action_payload,
|
|
76
|
-
controller: @current_context,
|
|
77
|
-
action_name: action_name.to_sym,
|
|
78
|
-
value_name: value_name.to_sym
|
|
79
|
-
}
|
|
80
|
-
self
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def param(params_hash)
|
|
84
|
-
ensure_context!
|
|
85
|
-
@definitions << { type: :param, controller: @current_context, params: params_hash }
|
|
86
|
-
self
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def build_definition
|
|
90
|
-
Definition::Result::Stimulus.new(definitions: @definitions.freeze)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
private
|
|
94
|
-
|
|
95
|
-
def ensure_context!
|
|
96
|
-
raise "A controller context must be set via .controller() or .context() before this call." unless @current_context
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Tailmix
|
|
4
|
-
module Dev
|
|
5
|
-
class StimulusGenerator
|
|
6
|
-
def initialize(definition, component_name)
|
|
7
|
-
@definition = definition
|
|
8
|
-
@component_name = component_name
|
|
9
|
-
@stimulus_defs = definition.elements.values.flat_map(&:stimulus).flat_map(&:definitions)
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def scaffold(controller_name = nil, show_docs: false)
|
|
13
|
-
controllers_to_generate = controller_name ? [ controller_name.to_s ] : all_controllers
|
|
14
|
-
|
|
15
|
-
output = controllers_to_generate.map do |name|
|
|
16
|
-
defs = @stimulus_defs.select { |d| d[:controller] == name }
|
|
17
|
-
|
|
18
|
-
show_docs ? generate_docs_for(name, defs) + "\n" : generate_js_for(name, defs) + ("-" * 60) + "\n"
|
|
19
|
-
end
|
|
20
|
-
output.join("\n")
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
private
|
|
24
|
-
|
|
25
|
-
def generate_docs_for(controller_name, defs)
|
|
26
|
-
output = [ "Stimulus:" ]
|
|
27
|
-
output << " - on `#{controller_name}` controller:"
|
|
28
|
-
targets = defs.select { |d| d[:type] == :target }.map { |d| d[:name] }
|
|
29
|
-
output << " - Targets: #{targets.join(', ')}" if targets.any?
|
|
30
|
-
|
|
31
|
-
actions = action_methods(defs)
|
|
32
|
-
output << " - Actions: #{actions.join(', ')}" if actions.any?
|
|
33
|
-
|
|
34
|
-
values = defs.select { |d| d[:type] == :value }.map { |d| d[:name] }
|
|
35
|
-
output << " - Values: #{values.join(', ')}" if values.any?
|
|
36
|
-
|
|
37
|
-
output.join("\n")
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def generate_js_for(controller_name, defs)
|
|
41
|
-
targets = defs.select { |d| d[:type] == :target }.map { |d| "'#{d[:name]}'" }.uniq.join(", ")
|
|
42
|
-
|
|
43
|
-
payload_actions = defs.select { |d| d[:type] == :action_payload }
|
|
44
|
-
simple_values = defs.select { |d| d[:type] == :value }
|
|
45
|
-
|
|
46
|
-
value_names = (payload_actions.map { |d| d[:value_name] } + simple_values.map { |d| d[:name] })
|
|
47
|
-
.uniq.map { |name| "#{snake_to_camel(name.to_s)}: Object" }.join(", ")
|
|
48
|
-
|
|
49
|
-
isomorphic_methods = payload_actions.map do |payload_def|
|
|
50
|
-
action_name_camel = snake_to_camel(payload_def[:action_name].to_s)
|
|
51
|
-
value_name_camel = snake_to_camel(payload_def[:value_name].to_s)
|
|
52
|
-
|
|
53
|
-
" #{action_name_camel}(event) {\n if (event) event.preventDefault();\n Tailmix.run({ config: this.#{value_name_camel}Value, controller: this });\n }"
|
|
54
|
-
end.join.strip
|
|
55
|
-
|
|
56
|
-
standard_action_names = defs.select { |d| d[:type] == :action }
|
|
57
|
-
.flat_map { |d| extract_action_methods(d[:data]) }
|
|
58
|
-
.uniq
|
|
59
|
-
|
|
60
|
-
implemented_action_names = payload_actions.map { |d| d[:action_name].to_s }
|
|
61
|
-
stub_methods = (standard_action_names - implemented_action_names).map do |method_name|
|
|
62
|
-
method_name_camel = snake_to_camel(method_name)
|
|
63
|
-
" #{method_name_camel}() {\n console.log('#{controller_name}##{method_name_camel} fired');\n }"
|
|
64
|
-
end.join("\n\n")
|
|
65
|
-
|
|
66
|
-
js_methods = [isomorphic_methods, stub_methods].reject(&:empty?).join("\n\n")
|
|
67
|
-
|
|
68
|
-
<<~JAVASCRIPT
|
|
69
|
-
// Generated by Tailmix for the "#{controller_name}" controller
|
|
70
|
-
// Path: app/javascript/controllers/#{controller_name.tr('_', '-')}_controller.js
|
|
71
|
-
import { Controller } from "@hotwired/stimulus"
|
|
72
|
-
import Tailmix from "tailmix"
|
|
73
|
-
|
|
74
|
-
export default class extends Controller {
|
|
75
|
-
static targets = [#{targets}]
|
|
76
|
-
static values = { #{value_names} }
|
|
77
|
-
|
|
78
|
-
connect() {
|
|
79
|
-
console.log("#{controller_name} controller connected to", this.element);
|
|
80
|
-
}
|
|
81
|
-
#{js_methods}
|
|
82
|
-
}
|
|
83
|
-
JAVASCRIPT
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def extract_action_methods(action_data)
|
|
87
|
-
case action_data[:type]
|
|
88
|
-
when :raw
|
|
89
|
-
action_data[:content].to_s.scan(/#(\w+)/).flatten
|
|
90
|
-
when :hash
|
|
91
|
-
action_data[:content].values.map(&:to_s)
|
|
92
|
-
when :tuple
|
|
93
|
-
[action_data[:content][1].to_s]
|
|
94
|
-
else
|
|
95
|
-
[]
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def all_controllers
|
|
100
|
-
@stimulus_defs.map { |d| d[:controller] }.compact.uniq
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def action_methods(defs)
|
|
104
|
-
defs.select { |d| d[:type] == :action }.flat_map do |action_definition|
|
|
105
|
-
data = action_definition[:data]
|
|
106
|
-
case data[:type]
|
|
107
|
-
when :raw
|
|
108
|
-
data[:content].to_s.scan(/#(\w+)/).flatten
|
|
109
|
-
when :hash
|
|
110
|
-
data[:content].values.map(&:to_s)
|
|
111
|
-
when :tuple
|
|
112
|
-
[ data[:content][1].to_s ]
|
|
113
|
-
else
|
|
114
|
-
[]
|
|
115
|
-
end
|
|
116
|
-
end.uniq
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def snake_to_camel(str)
|
|
120
|
-
str.split("_").map.with_index { |word, i| i.zero? ? word : word.capitalize }.join
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
end
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Tailmix
|
|
4
|
-
module Runtime
|
|
5
|
-
module Stimulus
|
|
6
|
-
class Compiler
|
|
7
|
-
|
|
8
|
-
def self.call(definition:, data_map:, root_definition:, component:)
|
|
9
|
-
(definition.definitions || []).each do |rule|
|
|
10
|
-
builder = data_map.stimulus
|
|
11
|
-
|
|
12
|
-
case rule[:type]
|
|
13
|
-
when :controller
|
|
14
|
-
builder.controller(rule[:name])
|
|
15
|
-
when :action
|
|
16
|
-
action_data = rule[:data]
|
|
17
|
-
controller_name = rule[:controller]
|
|
18
|
-
|
|
19
|
-
action_string = case action_data[:type]
|
|
20
|
-
when :raw
|
|
21
|
-
action_data[:content]
|
|
22
|
-
when :hash
|
|
23
|
-
action_data[:content].map { |event, method| "#{event}->#{controller_name}##{method}" }.join(" ")
|
|
24
|
-
when :tuple
|
|
25
|
-
event, method = action_data[:content]
|
|
26
|
-
"#{event}->#{controller_name}##{method}"
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
builder.context(controller_name).action(action_string)
|
|
30
|
-
when :target
|
|
31
|
-
builder.context(rule[:controller]).target(rule[:name])
|
|
32
|
-
when :value
|
|
33
|
-
source = rule[:source]
|
|
34
|
-
|
|
35
|
-
resolved_value = case source[:type]
|
|
36
|
-
when :literal
|
|
37
|
-
source[:content]
|
|
38
|
-
when :proc
|
|
39
|
-
source[:content].call
|
|
40
|
-
when :method
|
|
41
|
-
component.public_send(source[:content])
|
|
42
|
-
else
|
|
43
|
-
# type code here
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
builder.context(rule[:controller]).value(rule[:name], resolved_value)
|
|
47
|
-
|
|
48
|
-
when :param
|
|
49
|
-
builder.context(rule[:controller]).param(rule[:params])
|
|
50
|
-
when :action_payload
|
|
51
|
-
action = root_definition.actions.fetch(rule[:action_name])
|
|
52
|
-
builder.context(rule[:controller]).value(rule[:value_name], action.to_h)
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|