tailmix 0.4.6 → 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 +76 -145
- 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 +39 -0
- data/examples/_modal_component.arb +17 -25
- data/examples/button.rb +81 -0
- data/examples/helpers.rb +25 -0
- data/examples/modal_component.rb +32 -164
- 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 +19 -13
- data/lib/tailmix/definition/merger.rb +2 -1
- data/lib/tailmix/definition/payload_proxy.rb +16 -0
- data/lib/tailmix/definition/result.rb +17 -18
- data/lib/tailmix/dev/docs.rb +32 -7
- 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 -47
- 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 +34 -21
- 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/lib/tailmix/definition/contexts/action_builder.rb +0 -31
- data/lib/tailmix/definition/contexts/element_builder.rb +0 -41
- 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
|
@@ -1,39 +1,45 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "
|
|
4
|
-
require_relative "
|
|
3
|
+
require_relative "builders/action_builder"
|
|
4
|
+
require_relative "builders/element_builder"
|
|
5
|
+
require_relative "builders/variant_builder"
|
|
6
|
+
require_relative "payload_proxy"
|
|
5
7
|
|
|
6
8
|
module Tailmix
|
|
7
9
|
module Definition
|
|
8
10
|
class ContextBuilder
|
|
9
|
-
attr_reader :elements, :actions
|
|
11
|
+
attr_reader :elements, :actions, :component_name
|
|
10
12
|
|
|
11
|
-
def initialize
|
|
13
|
+
def initialize(component_name:)
|
|
12
14
|
@elements = {}
|
|
13
15
|
@actions = {}
|
|
16
|
+
@component_name = component_name
|
|
14
17
|
end
|
|
15
18
|
|
|
16
|
-
def element(name,
|
|
17
|
-
builder =
|
|
18
|
-
builder.attributes.classes(
|
|
19
|
+
def element(name, classes = "", &block)
|
|
20
|
+
builder = Builders::ElementBuilder.new(name)
|
|
21
|
+
builder.attributes.classes(classes.split)
|
|
19
22
|
|
|
20
23
|
builder.instance_eval(&block) if block
|
|
21
|
-
|
|
22
24
|
@elements[name.to_sym] = builder
|
|
25
|
+
|
|
26
|
+
@actions.merge!(builder.auto_actions)
|
|
23
27
|
end
|
|
24
28
|
|
|
25
|
-
def action(name,
|
|
26
|
-
builder =
|
|
27
|
-
|
|
29
|
+
def action(name, &block)
|
|
30
|
+
builder = Builders::ActionBuilder.new
|
|
31
|
+
proxy = Builders::PayloadProxy.new
|
|
32
|
+
builder.instance_exec(proxy, &block)
|
|
28
33
|
@actions[name.to_sym] = builder
|
|
29
34
|
end
|
|
30
35
|
|
|
31
36
|
def build_definition
|
|
32
37
|
Definition::Result::Context.new(
|
|
38
|
+
name: component_name,
|
|
33
39
|
elements: @elements.transform_values(&:build_definition).freeze,
|
|
34
|
-
actions: @actions.transform_values(&:build_definition).freeze
|
|
40
|
+
actions: @actions.transform_values(&:build_definition).freeze,
|
|
35
41
|
)
|
|
36
42
|
end
|
|
37
43
|
end
|
|
38
44
|
end
|
|
39
|
-
end
|
|
45
|
+
end
|
|
@@ -47,7 +47,8 @@ module Tailmix
|
|
|
47
47
|
name: parent_el.name,
|
|
48
48
|
attributes: merge_attributes(parent_el.attributes, child_el.attributes),
|
|
49
49
|
dimensions: merge_dimensions(parent_el.dimensions, child_el.dimensions),
|
|
50
|
-
stimulus: merge_stimulus(parent_el.stimulus, child_el.stimulus)
|
|
50
|
+
stimulus: merge_stimulus(parent_el.stimulus, child_el.stimulus),
|
|
51
|
+
compound_variants: parent_el.compound_variants + child_el.compound_variants
|
|
51
52
|
)
|
|
52
53
|
end
|
|
53
54
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tailmix
|
|
4
|
+
module Definition
|
|
5
|
+
# A marker indicating that the value should be taken from the runtime payload.
|
|
6
|
+
PayloadValue = Struct.new(:key)
|
|
7
|
+
|
|
8
|
+
# A proxy object that is passed to the `action do |payload|` block.
|
|
9
|
+
# It creates PayloadValue markers instead of containing the actual data.
|
|
10
|
+
class PayloadProxy
|
|
11
|
+
def [](key)
|
|
12
|
+
PayloadValue.new(key.to_sym)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -3,20 +3,25 @@
|
|
|
3
3
|
module Tailmix
|
|
4
4
|
module Definition
|
|
5
5
|
module Result
|
|
6
|
-
Context = Struct.new(:
|
|
6
|
+
Context = Struct.new(:name, :states, :actions, :elements, :plugins, :reactions, keyword_init: true) do
|
|
7
7
|
def to_h
|
|
8
8
|
{
|
|
9
|
+
name: name,
|
|
10
|
+
states: states,
|
|
11
|
+
actions: actions.transform_values(&:to_h),
|
|
9
12
|
elements: elements.transform_values(&:to_h),
|
|
10
|
-
|
|
13
|
+
plugins: plugins,
|
|
14
|
+
reactions: reactions
|
|
11
15
|
}
|
|
12
16
|
end
|
|
13
17
|
end
|
|
14
18
|
|
|
15
|
-
Element = Struct.new(:name, :attributes, :dimensions, :
|
|
19
|
+
Element = Struct.new(:name, :attributes, :dimensions, :compound_variants, :event_bindings, :attribute_bindings, :model_bindings, :default_attributes, keyword_init: true) do
|
|
16
20
|
def to_h
|
|
17
21
|
{
|
|
18
22
|
name: name,
|
|
19
23
|
attributes: attributes.to_h,
|
|
24
|
+
default_attributes: default_attributes,
|
|
20
25
|
dimensions: dimensions.transform_values do |dimension|
|
|
21
26
|
dimension.transform_values do |value|
|
|
22
27
|
case value
|
|
@@ -29,12 +34,14 @@ module Tailmix
|
|
|
29
34
|
end
|
|
30
35
|
end
|
|
31
36
|
end,
|
|
32
|
-
|
|
37
|
+
compound_variants: compound_variants,
|
|
38
|
+
attribute_bindings: attribute_bindings,
|
|
39
|
+
model_bindings: model_bindings,
|
|
33
40
|
}
|
|
34
41
|
end
|
|
35
42
|
end
|
|
36
43
|
|
|
37
|
-
Variant = Struct.new(:class_groups, :data, :aria, keyword_init: true) do
|
|
44
|
+
Variant = Struct.new(:class_groups, :data, :aria, :attributes, keyword_init: true) do
|
|
38
45
|
def classes
|
|
39
46
|
class_groups.flat_map { |group| group[:classes] }
|
|
40
47
|
end
|
|
@@ -44,7 +51,8 @@ module Tailmix
|
|
|
44
51
|
classes: classes,
|
|
45
52
|
class_groups: class_groups,
|
|
46
53
|
data: data,
|
|
47
|
-
aria: aria
|
|
54
|
+
aria: aria,
|
|
55
|
+
attributes: attributes
|
|
48
56
|
}
|
|
49
57
|
end
|
|
50
58
|
end
|
|
@@ -57,22 +65,13 @@ module Tailmix
|
|
|
57
65
|
end
|
|
58
66
|
end
|
|
59
67
|
|
|
60
|
-
|
|
61
|
-
def to_h
|
|
62
|
-
{
|
|
63
|
-
definitions: definitions
|
|
64
|
-
}
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
Action = Struct.new(:action, :mutations, keyword_init: true) do
|
|
68
|
+
Action = Struct.new(:transitions, keyword_init: true) do
|
|
69
69
|
def to_h
|
|
70
70
|
{
|
|
71
|
-
|
|
72
|
-
mutations: mutations
|
|
71
|
+
transitions: transitions,
|
|
73
72
|
}
|
|
74
73
|
end
|
|
75
74
|
end
|
|
76
75
|
end
|
|
77
76
|
end
|
|
78
|
-
end
|
|
77
|
+
end
|
data/lib/tailmix/dev/docs.rb
CHANGED
|
@@ -18,10 +18,8 @@ module Tailmix
|
|
|
18
18
|
output << ""
|
|
19
19
|
|
|
20
20
|
output << generate_dimensions_docs
|
|
21
|
-
output <<
|
|
21
|
+
output << generate_compound_variants_docs
|
|
22
22
|
output << generate_actions_docs
|
|
23
|
-
output << ""
|
|
24
|
-
output << generate_stimulus_docs
|
|
25
23
|
|
|
26
24
|
output.join("\n")
|
|
27
25
|
end
|
|
@@ -60,6 +58,37 @@ module Tailmix
|
|
|
60
58
|
output.join("\n")
|
|
61
59
|
end
|
|
62
60
|
|
|
61
|
+
def generate_compound_variants_docs
|
|
62
|
+
output = []
|
|
63
|
+
|
|
64
|
+
compound_variants_by_element = @definition.elements.values.select do |el|
|
|
65
|
+
el.compound_variants.any?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
if compound_variants_by_element.any?
|
|
69
|
+
output << ""
|
|
70
|
+
output << "Compound Variants:"
|
|
71
|
+
compound_variants_by_element.each do |element|
|
|
72
|
+
output << " - on element `:#{element.name}`:"
|
|
73
|
+
element.compound_variants.each do |cv|
|
|
74
|
+
conditions = cv[:on].map { |k, v| "#{k}: :#{v}" }.join(", ")
|
|
75
|
+
output << " - on: { #{conditions} }"
|
|
76
|
+
|
|
77
|
+
modifications = cv[:modifications]
|
|
78
|
+
modifications.class_groups.each do |group|
|
|
79
|
+
label = group[:options][:group] ? "(group: :#{group[:options][:group]})" : ""
|
|
80
|
+
output << " - classes #{label}: \"#{group[:classes].join(' ')}\""
|
|
81
|
+
end
|
|
82
|
+
output << " - data: #{modifications.data.inspect}" if modifications.data.any?
|
|
83
|
+
output << " - aria: #{modifications.aria.inspect}" if modifications.aria.any?
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
output << ""
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
output.join("\n")
|
|
90
|
+
end
|
|
91
|
+
|
|
63
92
|
def generate_actions_docs
|
|
64
93
|
output = []
|
|
65
94
|
actions = @definition.actions
|
|
@@ -76,10 +105,6 @@ module Tailmix
|
|
|
76
105
|
output.join("\n")
|
|
77
106
|
end
|
|
78
107
|
|
|
79
|
-
def generate_stimulus_docs
|
|
80
|
-
@tools.stimulus.scaffold(show_docs: true)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
108
|
def all_dimensions
|
|
84
109
|
@_all_dimensions ||= @definition.elements.values.flat_map(&:dimensions).reduce({}, :merge)
|
|
85
110
|
end
|
data/lib/tailmix/dev/tools.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "stimulus_generator"
|
|
4
3
|
require_relative "docs"
|
|
5
4
|
|
|
6
5
|
module Tailmix
|
|
@@ -18,10 +17,6 @@ module Tailmix
|
|
|
18
17
|
end
|
|
19
18
|
alias_method :help, :docs
|
|
20
19
|
|
|
21
|
-
def stimulus
|
|
22
|
-
StimulusGenerator.new(@definition, @component_class.name)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
20
|
def elements
|
|
26
21
|
@definition.elements.values.map(&:name)
|
|
27
22
|
end
|
data/lib/tailmix/dsl.rb
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "definition/
|
|
3
|
+
require_relative "definition/builders/component_builder"
|
|
4
4
|
require_relative "definition/merger"
|
|
5
5
|
require_relative "dev/tools"
|
|
6
6
|
|
|
7
7
|
module Tailmix
|
|
8
|
-
# The main DSL for defining component styles and behaviors.
|
|
9
|
-
# This module is extended into any class that includes Tailmix.
|
|
10
8
|
module DSL
|
|
11
9
|
def tailmix(&block)
|
|
12
|
-
child_context = Definition::
|
|
10
|
+
child_context = Definition::Builders::ComponentBuilder.new(component_name: self)
|
|
13
11
|
child_context.instance_eval(&block)
|
|
14
12
|
child_definition = child_context.build_definition
|
|
15
13
|
|
|
@@ -32,4 +30,4 @@ module Tailmix
|
|
|
32
30
|
Dev::Tools.new(self)
|
|
33
31
|
end
|
|
34
32
|
end
|
|
35
|
-
end
|
|
33
|
+
end
|
data/lib/tailmix/engine.rb
CHANGED
|
@@ -6,12 +6,22 @@ module Tailmix
|
|
|
6
6
|
Rails.application.config.assets.paths << Engine.root.join("app/javascript")
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
PRECOMPILE_ASSETS = %w[
|
|
9
|
+
PRECOMPILE_ASSETS = %w[ runtime/action_dispatcher.js runtime/component.js runtime/updater.js runtime/index.js runtime/plugins.js ]
|
|
10
10
|
|
|
11
11
|
initializer "tailmix.assets" do
|
|
12
12
|
if Rails.application.config.respond_to?(:assets)
|
|
13
13
|
Rails.application.config.assets.precompile += PRECOMPILE_ASSETS
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
|
+
|
|
17
|
+
initializer "tailmix.add_middleware" do |app|
|
|
18
|
+
app.middleware.use Tailmix::Middleware::RegistryCleaner
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
initializer "tailmix.helpers" do
|
|
22
|
+
ActiveSupport.on_load(:action_controller_base) do
|
|
23
|
+
helper Tailmix::ViewHelpers
|
|
24
|
+
end
|
|
25
|
+
end
|
|
16
26
|
end
|
|
17
27
|
end
|
|
@@ -3,16 +3,15 @@
|
|
|
3
3
|
require "erb"
|
|
4
4
|
require_relative "class_list"
|
|
5
5
|
require_relative "data_map"
|
|
6
|
-
require_relative "selector"
|
|
7
6
|
|
|
8
7
|
module Tailmix
|
|
9
8
|
module HTML
|
|
10
9
|
class Attributes < Hash
|
|
11
|
-
attr_reader :element_name
|
|
10
|
+
attr_reader :element_name
|
|
12
11
|
|
|
13
|
-
def initialize(initial_hash = {}, element_name: nil,
|
|
12
|
+
def initialize(initial_hash = {}, element_name: nil, context: nil)
|
|
14
13
|
@element_name = element_name
|
|
15
|
-
@
|
|
14
|
+
@context = context
|
|
16
15
|
super()
|
|
17
16
|
|
|
18
17
|
attrs_to_merge = initial_hash.dup
|
|
@@ -24,24 +23,27 @@ module Tailmix
|
|
|
24
23
|
self[:class] = ClassList.new(initial_classes)
|
|
25
24
|
self[:data] = DataMap.new("data", initial_data || {})
|
|
26
25
|
self[:aria] = DataMap.new("aria", initial_aria || {})
|
|
27
|
-
self[:tailmix] = Selector.new(element_name, variant_string)
|
|
28
26
|
|
|
29
27
|
merge!(attrs_to_merge)
|
|
30
28
|
end
|
|
31
29
|
|
|
30
|
+
|
|
32
31
|
def each(&block)
|
|
33
32
|
to_h.each(&block)
|
|
34
33
|
end
|
|
34
|
+
alias_method :each_pair, :each
|
|
35
35
|
|
|
36
36
|
def to_h
|
|
37
|
-
final_attrs = select { |k, _| !%i[class data aria
|
|
37
|
+
final_attrs = select { |k, _| !%i[class data aria].include?(k.to_sym) }
|
|
38
38
|
|
|
39
39
|
class_string = self[:class].to_s
|
|
40
40
|
final_attrs[:class] = class_string unless class_string.empty?
|
|
41
41
|
|
|
42
42
|
final_attrs.merge!(self[:data].to_h)
|
|
43
43
|
final_attrs.merge!(self[:aria].to_h)
|
|
44
|
-
|
|
44
|
+
|
|
45
|
+
final_attrs["data-tailmix-element"] = @element_name if @element_name
|
|
46
|
+
final_attrs["data-tailmix-id"] = @context.id if @context.id
|
|
45
47
|
|
|
46
48
|
final_attrs
|
|
47
49
|
end
|
|
@@ -67,10 +69,6 @@ module Tailmix
|
|
|
67
69
|
data.stimulus
|
|
68
70
|
end
|
|
69
71
|
|
|
70
|
-
def tailmix
|
|
71
|
-
self[:tailmix]
|
|
72
|
-
end
|
|
73
|
-
|
|
74
72
|
def toggle(class_names)
|
|
75
73
|
classes.toggle(class_names)
|
|
76
74
|
self
|
|
@@ -87,8 +85,20 @@ module Tailmix
|
|
|
87
85
|
end
|
|
88
86
|
|
|
89
87
|
def each_attribute(&block)
|
|
90
|
-
[ classes: classes, data: data.to_h, aria: aria.to_h
|
|
88
|
+
[ classes: classes, data: data.to_h, aria: aria.to_h ].each(&block)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def component
|
|
92
|
+
raise "No context available to build component root" unless @context
|
|
93
|
+
|
|
94
|
+
root_attrs = {
|
|
95
|
+
"data-tailmix-component" => @context.component_name,
|
|
96
|
+
"data-tailmix-state" => @context.state_payload,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
self.class.new(self.to_h.merge(root_attrs), element_name: @element_name, context: @context)
|
|
91
100
|
end
|
|
101
|
+
alias_method :root, :component
|
|
92
102
|
end
|
|
93
103
|
end
|
|
94
104
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "singleton"
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
5
|
+
module Tailmix
|
|
6
|
+
# A per-request registry to store unique component classes rendered
|
|
7
|
+
# during a request-response cycle.
|
|
8
|
+
class Registry
|
|
9
|
+
include Singleton
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@component_classes = Set.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Registers a component class.
|
|
16
|
+
# @param component_class [Class] The component class to register.
|
|
17
|
+
def register(component_class)
|
|
18
|
+
@component_classes.add(component_class)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Gathers definitions from all registered classes.
|
|
22
|
+
# @return [Hash] A hash mapping component names to their definitions.
|
|
23
|
+
def definitions
|
|
24
|
+
@component_classes.each_with_object({}) do |klass, hash|
|
|
25
|
+
component_name = klass.name
|
|
26
|
+
if component_name && klass.respond_to?(:tailmix_definition)
|
|
27
|
+
hash[component_name] = klass.tailmix_definition.to_h
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Clears the registry. Must be called after each request.
|
|
33
|
+
def clear!
|
|
34
|
+
@component_classes.clear
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tailmix
|
|
4
|
+
module Runtime
|
|
5
|
+
# Proxy for convenient calling of actions (ui.action).
|
|
6
|
+
class ActionProxy
|
|
7
|
+
def initialize(context)
|
|
8
|
+
@context = context
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def method_missing(method_name, *args, &block)
|
|
12
|
+
action_name = method_name.to_sym
|
|
13
|
+
action_def = @context.definition.actions[action_name]
|
|
14
|
+
|
|
15
|
+
unless action_def
|
|
16
|
+
raise NoMethodError, "undefined action `#{action_name}` for #{@context.component_name}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# We return an object that can be called using .call.
|
|
20
|
+
# This allows you to write ui.action.save.call(payload)
|
|
21
|
+
->(payload = {}) { @context.run_action(action_def, payload) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
25
|
+
@context.definition.actions.key?(method_name.to_sym) || super
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tailmix
|
|
4
|
+
module Runtime
|
|
5
|
+
class AttributeBuilder
|
|
6
|
+
def initialize(element_def, state, context)
|
|
7
|
+
@element_def = element_def
|
|
8
|
+
@state = state
|
|
9
|
+
@context = context
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def build
|
|
13
|
+
attributes = create_base_attributes
|
|
14
|
+
|
|
15
|
+
apply_dimensions(attributes)
|
|
16
|
+
apply_compound_variants(attributes)
|
|
17
|
+
apply_attribute_bindings(attributes)
|
|
18
|
+
apply_model_bindings(attributes)
|
|
19
|
+
apply_event_bindings(attributes)
|
|
20
|
+
|
|
21
|
+
attributes
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def create_base_attributes
|
|
27
|
+
base_attrs = @element_def.default_attributes.merge(
|
|
28
|
+
class: @element_def.attributes.classes
|
|
29
|
+
)
|
|
30
|
+
HTML::Attributes.new(base_attrs, element_name: @element_def.name, context: @context)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Applies classes and data/aria attributes from `dimension`.
|
|
34
|
+
def apply_dimensions(attributes)
|
|
35
|
+
@element_def.dimensions.each do |name, dim_def|
|
|
36
|
+
value = @state[name] || dim_def[:default]
|
|
37
|
+
next if value.nil?
|
|
38
|
+
|
|
39
|
+
variant_def = dim_def.fetch(:variants, {}).fetch(value, nil)
|
|
40
|
+
|
|
41
|
+
next unless variant_def
|
|
42
|
+
attributes.classes.add(variant_def.classes)
|
|
43
|
+
attributes.data.merge!(variant_def.data)
|
|
44
|
+
attributes.aria.merge!(variant_def.aria)
|
|
45
|
+
attributes.merge!(variant_def.attributes)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Applies classes and data/aria attributes from `compound_variant`.
|
|
50
|
+
def apply_compound_variants(attributes)
|
|
51
|
+
@element_def.compound_variants.each do |cv|
|
|
52
|
+
next unless cv[:on].all? { |key, value| @state[key] == value }
|
|
53
|
+
|
|
54
|
+
modifications = cv[:modifications]
|
|
55
|
+
attributes.classes.add(modifications.classes)
|
|
56
|
+
attributes.data.merge!(modifications.data) if modifications.data
|
|
57
|
+
attributes.aria.merge!(modifications.aria) if modifications.aria
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Applies one-way attribute bindings (`bind :src, to: :url`).
|
|
62
|
+
def apply_attribute_bindings(attributes)
|
|
63
|
+
@element_def.attribute_bindings&.each do |attr_name, state_key_or_proc|
|
|
64
|
+
next if %i[text html].include?(attr_name)
|
|
65
|
+
|
|
66
|
+
value = if state_key_or_proc.is_a?(Proc)
|
|
67
|
+
state_key_or_proc.call(@state)
|
|
68
|
+
else
|
|
69
|
+
@state[state_key_or_proc]
|
|
70
|
+
end
|
|
71
|
+
attributes[attr_name] = value if value
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Applies two-way bindings (`model :value, to: :query`).
|
|
76
|
+
def apply_model_bindings(attributes)
|
|
77
|
+
@element_def.model_bindings&.each do |attr_name, binding_def|
|
|
78
|
+
state_key = binding_def[:state]
|
|
79
|
+
value = @state[state_key]
|
|
80
|
+
attributes[attr_name] = value if value
|
|
81
|
+
|
|
82
|
+
# We are adding data attributes that will "bring to life" the client-side JS.
|
|
83
|
+
attributes.data.add("tailmix-model-attr": attr_name)
|
|
84
|
+
attributes.data.add("tailmix-model-state": state_key)
|
|
85
|
+
attributes.data.add("tailmix-model-event": binding_def[:event])
|
|
86
|
+
attributes.data.add("tailmix-model-action": binding_def[:action]) if binding_def[:action]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Applies event handlers (`on :click, :save`).
|
|
91
|
+
def apply_event_bindings(attributes)
|
|
92
|
+
return unless @element_def.event_bindings&.any?
|
|
93
|
+
|
|
94
|
+
action_string = @element_def.event_bindings.map { |b| "#{b[:event]}->#{b[:action]}" }.join(" ")
|
|
95
|
+
with_map = @element_def.event_bindings.map { |b| b[:with] }.compact.reduce({}, :merge)
|
|
96
|
+
|
|
97
|
+
attributes.data.add(tailmix_action: action_string)
|
|
98
|
+
attributes.data.add(tailmix_action_with: with_map.to_json) unless with_map.empty?
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tailmix
|
|
4
|
+
module Runtime
|
|
5
|
+
class AttributeCache
|
|
6
|
+
def initialize
|
|
7
|
+
@cache = {}
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def get(element_name)
|
|
11
|
+
@cache[element_name.to_sym]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def set(element_name, attributes)
|
|
15
|
+
@cache[element_name.to_sym] = attributes
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def clear!
|
|
19
|
+
@cache.clear
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|