tailmix 0.2.0 → 0.4.5
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/.rubocop.yml +3 -0
- data/README.md +147 -85
- data/app/javascript/tailmix/finder.js +12 -0
- data/app/javascript/tailmix/index.js +7 -0
- data/app/javascript/tailmix/mutator.js +28 -0
- data/app/javascript/tailmix/runner.js +7 -0
- data/app/javascript/tailmix/stimulus_adapter.js +37 -0
- data/examples/_modal_component.arb +36 -0
- data/examples/modal_component.rb +180 -0
- data/lib/generators/tailmix/install_generator.rb +19 -0
- data/lib/tailmix/definition/context_builder.rb +39 -0
- data/lib/tailmix/definition/contexts/action_builder.rb +31 -0
- data/lib/tailmix/definition/contexts/actions/element_builder.rb +30 -0
- data/lib/tailmix/definition/contexts/attribute_builder.rb +21 -0
- data/lib/tailmix/definition/contexts/dimension_builder.rb +16 -0
- data/lib/tailmix/definition/contexts/element_builder.rb +41 -0
- data/lib/tailmix/definition/contexts/stimulus_builder.rb +101 -0
- data/lib/tailmix/definition/merger.rb +93 -0
- data/lib/tailmix/definition/result.rb +31 -0
- data/lib/tailmix/definition.rb +11 -0
- data/lib/tailmix/dev/docs.rb +82 -0
- data/lib/tailmix/dev/stimulus_generator.rb +124 -0
- data/lib/tailmix/dev/tools.rb +26 -0
- data/lib/tailmix/engine.rb +17 -0
- data/lib/tailmix/html/attributes.rb +71 -0
- data/lib/tailmix/html/class_list.rb +79 -0
- data/lib/tailmix/html/data_map.rb +95 -0
- data/lib/tailmix/html/stimulus_builder.rb +65 -0
- data/lib/tailmix/runtime/action.rb +51 -0
- data/lib/tailmix/runtime/context.rb +66 -0
- data/lib/tailmix/runtime/facade_builder.rb +23 -0
- data/lib/tailmix/runtime/stimulus/compiler.rb +59 -0
- data/lib/tailmix/runtime.rb +14 -0
- data/lib/tailmix/version.rb +1 -1
- data/lib/tailmix.rb +48 -19
- metadata +33 -12
- data/examples/interactive_component.rb +0 -42
- data/examples/status_badge_component.rb +0 -44
- data/lib/tailmix/action.rb +0 -27
- data/lib/tailmix/dimension.rb +0 -18
- data/lib/tailmix/element.rb +0 -24
- data/lib/tailmix/manager.rb +0 -86
- data/lib/tailmix/part.rb +0 -39
- data/lib/tailmix/resolver.rb +0 -28
- data/lib/tailmix/schema.rb +0 -41
- data/lib/tailmix/utils.rb +0 -15
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
require_relative "class_list"
|
|
5
|
+
require_relative "data_map"
|
|
6
|
+
|
|
7
|
+
module Tailmix
|
|
8
|
+
module HTML
|
|
9
|
+
class Attributes < Hash
|
|
10
|
+
attr_reader :element_name
|
|
11
|
+
|
|
12
|
+
def initialize(initial_hash = {}, element_name: nil)
|
|
13
|
+
@element_name = element_name
|
|
14
|
+
super()
|
|
15
|
+
self[:class] = ClassList.new
|
|
16
|
+
self[:data] = DataMap.new
|
|
17
|
+
merge!(initial_hash)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def each(&block)
|
|
21
|
+
to_h.each(&block)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_h
|
|
25
|
+
final_attrs = select { |k, _| !%i[class data].include?(k.to_sym) }
|
|
26
|
+
class_string = self[:class].to_s
|
|
27
|
+
final_attrs[:class] = class_string unless class_string.empty?
|
|
28
|
+
final_attrs.merge!(self[:data].to_h)
|
|
29
|
+
|
|
30
|
+
selector_attr = Tailmix.configuration.element_selector_attribute
|
|
31
|
+
if selector_attr && @element_name
|
|
32
|
+
final_attrs[selector_attr] = @element_name
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
final_attrs
|
|
36
|
+
end
|
|
37
|
+
alias_method :to_hash, :to_h
|
|
38
|
+
|
|
39
|
+
def to_s
|
|
40
|
+
classes.to_s
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def classes
|
|
44
|
+
self[:class]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def data
|
|
48
|
+
self[:data]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def stimulus
|
|
52
|
+
data.stimulus
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def toggle(class_names)
|
|
56
|
+
classes.toggle(class_names)
|
|
57
|
+
self
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def add(class_names)
|
|
61
|
+
classes.add(class_names)
|
|
62
|
+
self
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def remove(class_names)
|
|
66
|
+
classes.remove(class_names)
|
|
67
|
+
self
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
5
|
+
module Tailmix
|
|
6
|
+
module HTML
|
|
7
|
+
# Manages a set of CSS classes with a fluent, chainable API.
|
|
8
|
+
# Inherits from Set to ensure uniqueness and leverage its performance.
|
|
9
|
+
class ClassList < Set
|
|
10
|
+
# Initializes a new ClassList.
|
|
11
|
+
# @param initial_classes [String, Array, Set, nil] The initial classes to add.
|
|
12
|
+
def initialize(initial_classes = nil)
|
|
13
|
+
super()
|
|
14
|
+
add(initial_classes) if initial_classes
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Adds one or more classes. Handles strings, arrays, or other sets.
|
|
18
|
+
# This method is MUTABLE and chainable.
|
|
19
|
+
# @param class_names [String, Array, Set, nil]
|
|
20
|
+
# @return [self]
|
|
21
|
+
def add(class_names)
|
|
22
|
+
each_token(class_names) { |token| super(token) }
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
alias << add
|
|
26
|
+
|
|
27
|
+
# Removes one or more classes.
|
|
28
|
+
# This method is MUTABLE and chainable.
|
|
29
|
+
# @param class_names [String, Array, Set, nil]
|
|
30
|
+
# @return [self]
|
|
31
|
+
def remove(class_names)
|
|
32
|
+
each_token(class_names) { |token| delete(token) }
|
|
33
|
+
self
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Toggles one or more classes.
|
|
37
|
+
# This method is MUTABLE and chainable.
|
|
38
|
+
# @param class_names [String, Array, Set, nil]
|
|
39
|
+
# @return [self]
|
|
40
|
+
def toggle(class_names)
|
|
41
|
+
each_token(class_names) { |token| include?(token) ? delete(token) : add(token) }
|
|
42
|
+
self
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns a new ClassList with the given classes added. IMMUTABLE.
|
|
46
|
+
def added(class_names)
|
|
47
|
+
dup.add(class_names)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Returns a new ClassList with the given classes removed. IMMUTABLE.
|
|
51
|
+
def removed(class_names)
|
|
52
|
+
dup.remove(class_names)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns a new ClassList with the given classes toggled. IMMUTABLE.
|
|
56
|
+
def toggled(class_names)
|
|
57
|
+
dup.toggle(class_names)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Renders the set of classes to a space-separated string for HTML.
|
|
61
|
+
# @return [String]
|
|
62
|
+
def to_s
|
|
63
|
+
to_a.join(" ")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
# A robust way to iterate over tokens from various input types.
|
|
69
|
+
def each_token(input)
|
|
70
|
+
return unless input
|
|
71
|
+
# Convert Set/ClassList to array before splitting strings inside
|
|
72
|
+
items = input.is_a?(Set) ? input.to_a : Array(input)
|
|
73
|
+
items.each do |item|
|
|
74
|
+
item.to_s.split.each { |token| yield token unless token.empty? }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
require "json"
|
|
5
|
+
require_relative "stimulus_builder"
|
|
6
|
+
|
|
7
|
+
module Tailmix
|
|
8
|
+
module HTML
|
|
9
|
+
class DataMap
|
|
10
|
+
MERGEABLE_LIST_ATTRIBUTES = %i[controller action target].freeze
|
|
11
|
+
|
|
12
|
+
def initialize(initial_data = {})
|
|
13
|
+
@data = {}
|
|
14
|
+
merge!(initial_data)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def stimulus
|
|
18
|
+
StimulusBuilder.new(self)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def merge!(other_data)
|
|
22
|
+
return self unless other_data
|
|
23
|
+
data_to_merge = other_data.is_a?(DataMap) ? other_data.instance_variable_get(:@data) : other_data
|
|
24
|
+
|
|
25
|
+
(data_to_merge || {}).each do |key, value|
|
|
26
|
+
key = key.to_sym
|
|
27
|
+
if value.is_a?(Hash) && @data[key].is_a?(Hash)
|
|
28
|
+
@data[key].merge!(value)
|
|
29
|
+
elsif MERGEABLE_LIST_ATTRIBUTES.include?(key)
|
|
30
|
+
add_to_set(key, value)
|
|
31
|
+
else
|
|
32
|
+
@data[key] = value
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
alias_method :add, :merge!
|
|
38
|
+
|
|
39
|
+
def merge(other_data)
|
|
40
|
+
dup.merge!(other_data)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def add_to_set(key, value)
|
|
44
|
+
@data[key] ||= Set.new
|
|
45
|
+
return unless value
|
|
46
|
+
items_to_process = value.is_a?(Set) ? value.to_a : Array(value)
|
|
47
|
+
items_to_process.each do |item|
|
|
48
|
+
item.to_s.split.each do |token|
|
|
49
|
+
@data[key].add(token) unless token.empty?
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def remove(other_data)
|
|
55
|
+
(other_data || {}).each do |key, _|
|
|
56
|
+
@data.delete(key.to_sym)
|
|
57
|
+
end
|
|
58
|
+
self
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def toggle(other_data)
|
|
62
|
+
(other_data || {}).each do |key, value|
|
|
63
|
+
key = key.to_sym
|
|
64
|
+
@data[key] == value ? @data.delete(key) : @data[key] = value
|
|
65
|
+
end
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def to_h
|
|
70
|
+
flatten_data_hash(@data)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def flatten_data_hash(hash, prefix = "data", accumulator = {})
|
|
76
|
+
hash.each do |key, value|
|
|
77
|
+
current_key = "#{prefix}-#{key.to_s.tr('_', '-')}"
|
|
78
|
+
if key.to_s.end_with?("_value")
|
|
79
|
+
serialized_value = case value
|
|
80
|
+
when Hash, Array then value.to_json
|
|
81
|
+
else value
|
|
82
|
+
end
|
|
83
|
+
accumulator[current_key] = serialized_value
|
|
84
|
+
elsif value.is_a?(Hash)
|
|
85
|
+
flatten_data_hash(value, current_key, accumulator)
|
|
86
|
+
else
|
|
87
|
+
serialized_value = value.is_a?(Set) ? value.to_a.join(" ") : value
|
|
88
|
+
accumulator[current_key] = serialized_value unless serialized_value.to_s.empty?
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
accumulator
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tailmix
|
|
4
|
+
module HTML
|
|
5
|
+
# A fluent DSL (builder) for constructing Stimulus data attributes.
|
|
6
|
+
# It acts as a proxy, modifying a DataMap instance directly.
|
|
7
|
+
class StimulusBuilder
|
|
8
|
+
def initialize(data_map)
|
|
9
|
+
@data_map = data_map
|
|
10
|
+
@context = nil # For context-aware attributes like targets and values
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Defines a controller and sets it as the current context.
|
|
14
|
+
# @return [self] for chaining.
|
|
15
|
+
def controller(controller_name)
|
|
16
|
+
@data_map.add_to_set(:controller, controller_name)
|
|
17
|
+
@context = controller_name.to_s
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Sets the controller context for subsequent calls.
|
|
22
|
+
def context(controller_name)
|
|
23
|
+
@context = controller_name.to_s
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Adds an action.
|
|
28
|
+
# @example
|
|
29
|
+
# .action("click->modal#open")
|
|
30
|
+
# @return [self]
|
|
31
|
+
def action(action_string)
|
|
32
|
+
@data_map.add_to_set(:action, action_string)
|
|
33
|
+
self
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Adds a target, scoped to the current controller context.
|
|
37
|
+
# @return [self]
|
|
38
|
+
def target(target_name)
|
|
39
|
+
ensure_context!
|
|
40
|
+
# `target` is a shared attribute, but names are scoped to a controller.
|
|
41
|
+
# So we add to the common `target` set.
|
|
42
|
+
@data_map.add_to_set(:"#{@context}-target", target_name)
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Adds a value, scoped to the current controller context.
|
|
47
|
+
# @return [self]
|
|
48
|
+
def value(value_name, value)
|
|
49
|
+
ensure_context!
|
|
50
|
+
@data_map.merge!("#{context_key(value_name)}_value" => value)
|
|
51
|
+
self
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def ensure_context!
|
|
57
|
+
raise "A controller context must be set via .controller() or .context() before this call." unless @context
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def context_key(name)
|
|
61
|
+
"#{@context}-#{name.to_s.tr('_', '-')}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tailmix
|
|
4
|
+
module Runtime
|
|
5
|
+
# Represents a callable action at runtime that can apply a set of
|
|
6
|
+
# predefined mutations to its context.
|
|
7
|
+
class Action
|
|
8
|
+
attr_reader :context, :definition
|
|
9
|
+
|
|
10
|
+
def initialize(context, action_name)
|
|
11
|
+
@context = context
|
|
12
|
+
@action_name = action_name.to_sym
|
|
13
|
+
@definition = context.definition.actions[@action_name]
|
|
14
|
+
raise Error, "Action `#{@action_name}` not found." unless @definition
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Applies the mutations to the context immutably, returning a new context.
|
|
18
|
+
# @return [Context] A new, modified context instance.
|
|
19
|
+
def apply
|
|
20
|
+
new_context = context.dup
|
|
21
|
+
|
|
22
|
+
action_on_clone = self.class.new(new_context, @action_name)
|
|
23
|
+
|
|
24
|
+
action_on_clone.apply!
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def apply!
|
|
28
|
+
# `definition.mutations` { element_name => [commands] }
|
|
29
|
+
definition.mutations.each do |element_name, commands|
|
|
30
|
+
attributes_object = context.live_attributes_for(element_name)
|
|
31
|
+
next unless attributes_object
|
|
32
|
+
|
|
33
|
+
commands.each do |command|
|
|
34
|
+
target_field = attributes_object.public_send(command[:field])
|
|
35
|
+
target_field.public_send(command[:method], command[:payload])
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
context
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Serializes the action's definition into a hash for the JS bridge.
|
|
42
|
+
# @return [Hash]
|
|
43
|
+
def to_h
|
|
44
|
+
{
|
|
45
|
+
method: definition.action,
|
|
46
|
+
mutations: definition.mutations
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tailmix
|
|
4
|
+
module Runtime
|
|
5
|
+
class Context
|
|
6
|
+
attr_reader :component_instance, :definition, :dimensions
|
|
7
|
+
|
|
8
|
+
def initialize(component_instance, definition, dimensions)
|
|
9
|
+
@component_instance = component_instance
|
|
10
|
+
@definition = definition
|
|
11
|
+
@dimensions = dimensions
|
|
12
|
+
@attributes_cache = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize_copy(source)
|
|
16
|
+
super
|
|
17
|
+
@attributes_cache = source.instance_variable_get(:@attributes_cache).transform_values(&:dup)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def live_attributes_for(element_name)
|
|
21
|
+
@attributes_cache[element_name] ||= build_attributes_for(element_name, @dimensions)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def attributes_for(element_name, runtime_dimensions = {})
|
|
25
|
+
merged_dimensions = @dimensions.merge(runtime_dimensions)
|
|
26
|
+
return @attributes_cache[element_name] if merged_dimensions == @dimensions && @attributes_cache[element_name]
|
|
27
|
+
|
|
28
|
+
attributes_object = build_attributes_for(element_name, merged_dimensions)
|
|
29
|
+
@attributes_cache[element_name] = attributes_object if merged_dimensions == @dimensions
|
|
30
|
+
attributes_object
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def action(name)
|
|
34
|
+
Action.new(self, name)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def build_attributes_for(element_name, dimensions)
|
|
40
|
+
element_def = @definition.elements.fetch(element_name)
|
|
41
|
+
initial_classes = element_def.attributes.classes
|
|
42
|
+
class_list = HTML::ClassList.new(initial_classes)
|
|
43
|
+
|
|
44
|
+
element_def.dimensions.each do |name, dim|
|
|
45
|
+
value = dimensions.fetch(name, dim[:default])
|
|
46
|
+
next if value.nil?
|
|
47
|
+
classes_for_option = dim.fetch(:options, {}).fetch(value, nil)
|
|
48
|
+
class_list.add(classes_for_option)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
data_map = HTML::DataMap.new
|
|
52
|
+
Stimulus::Compiler.call(
|
|
53
|
+
definition: element_def.stimulus,
|
|
54
|
+
data_map: data_map,
|
|
55
|
+
root_definition: @definition,
|
|
56
|
+
component: @component_instance
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
HTML::Attributes.new(
|
|
60
|
+
{ class: class_list, data: data_map },
|
|
61
|
+
element_name: element_def.name
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tailmix
|
|
4
|
+
module Runtime
|
|
5
|
+
class FacadeBuilder
|
|
6
|
+
def self.build(definition)
|
|
7
|
+
Class.new(Tailmix::Runtime::Context) do
|
|
8
|
+
definition.elements.each_key do |element_name|
|
|
9
|
+
define_method(element_name) do |runtime_dimensions = {}|
|
|
10
|
+
attributes_for(element_name, runtime_dimensions)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def inspect
|
|
15
|
+
component_name = @component_instance.class.name || "AnonymousComponent"
|
|
16
|
+
elements_list = @definition.elements.keys.join(", ")
|
|
17
|
+
"#<Tailmix::UI for #{component_name} elements=[#{elements_list}] dimensions=#{@dimensions.inspect}>"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
require_relative "runtime/context"
|
|
5
|
+
require_relative "runtime/facade_builder"
|
|
6
|
+
require_relative "runtime/stimulus/compiler"
|
|
7
|
+
require_relative "html/attributes"
|
|
8
|
+
require_relative "runtime/action"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
module Tailmix
|
|
12
|
+
module Runtime
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/tailmix/version.rb
CHANGED
data/lib/tailmix.rb
CHANGED
|
@@ -1,38 +1,67 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "tailmix/version"
|
|
4
|
-
require_relative "tailmix/
|
|
5
|
-
require_relative "tailmix/
|
|
6
|
-
require_relative "tailmix/
|
|
7
|
-
require_relative "tailmix/part"
|
|
8
|
-
require_relative "tailmix/dimension"
|
|
9
|
-
require_relative "tailmix/element"
|
|
10
|
-
require_relative "tailmix/utils"
|
|
11
|
-
require_relative "tailmix/action"
|
|
4
|
+
require_relative "tailmix/definition"
|
|
5
|
+
require_relative "tailmix/runtime"
|
|
6
|
+
require_relative "tailmix/dev/tools"
|
|
12
7
|
|
|
13
8
|
module Tailmix
|
|
14
|
-
|
|
15
|
-
base.extend(ClassMethods)
|
|
16
|
-
base.instance_variable_set(:@tailmix_schema, nil)
|
|
9
|
+
class Error < StandardError; end
|
|
17
10
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
class << self
|
|
12
|
+
attr_writer :configuration
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.configuration
|
|
16
|
+
@configuration ||= Configuration.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.configure
|
|
20
|
+
yield(configuration)
|
|
21
|
+
end
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
class Configuration
|
|
24
|
+
attr_accessor :element_selector_attribute
|
|
25
|
+
|
|
26
|
+
def initialize
|
|
27
|
+
element_selector_attribute = nil
|
|
24
28
|
end
|
|
25
29
|
end
|
|
26
30
|
|
|
27
31
|
module ClassMethods
|
|
28
32
|
def tailmix(&block)
|
|
29
|
-
|
|
33
|
+
child_context = Definition::ContextBuilder.new
|
|
34
|
+
child_context.instance_eval(&block)
|
|
35
|
+
child_definition = child_context.build_definition
|
|
36
|
+
|
|
37
|
+
if superclass.respond_to?(:tailmix_definition) && (parent_definition = superclass.tailmix_definition)
|
|
38
|
+
@tailmix_definition = Definition::Merger.call(parent_definition, child_definition)
|
|
39
|
+
else
|
|
40
|
+
@tailmix_definition = child_definition
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def tailmix_definition
|
|
45
|
+
@tailmix_definition || raise(Error, "Tailmix definition not found in #{name}")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def tailmix_facade_class
|
|
49
|
+
@_tailmix_facade_class ||= Runtime::FacadeBuilder.build(tailmix_definition)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def dev
|
|
53
|
+
Dev::Tools.new(self)
|
|
30
54
|
end
|
|
31
55
|
end
|
|
32
56
|
|
|
33
|
-
|
|
57
|
+
def self.included(base)
|
|
58
|
+
base.extend(ClassMethods)
|
|
59
|
+
end
|
|
34
60
|
|
|
35
61
|
def tailmix(options = {})
|
|
36
|
-
|
|
62
|
+
facade_class = self.class.tailmix_facade_class
|
|
63
|
+
facade_class.new(self, self.class.tailmix_definition, options)
|
|
37
64
|
end
|
|
38
65
|
end
|
|
66
|
+
|
|
67
|
+
require_relative "tailmix/engine" if defined?(Rails)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tailmix
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alexander Fokin
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-08-
|
|
11
|
+
date: 2025-08-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -68,17 +68,38 @@ files:
|
|
|
68
68
|
- LICENSE.txt
|
|
69
69
|
- README.md
|
|
70
70
|
- Rakefile
|
|
71
|
-
-
|
|
72
|
-
-
|
|
71
|
+
- app/javascript/tailmix/finder.js
|
|
72
|
+
- app/javascript/tailmix/index.js
|
|
73
|
+
- app/javascript/tailmix/mutator.js
|
|
74
|
+
- app/javascript/tailmix/runner.js
|
|
75
|
+
- app/javascript/tailmix/stimulus_adapter.js
|
|
76
|
+
- examples/_modal_component.arb
|
|
77
|
+
- examples/modal_component.rb
|
|
78
|
+
- lib/generators/tailmix/install_generator.rb
|
|
73
79
|
- lib/tailmix.rb
|
|
74
|
-
- lib/tailmix/
|
|
75
|
-
- lib/tailmix/
|
|
76
|
-
- lib/tailmix/
|
|
77
|
-
- lib/tailmix/
|
|
78
|
-
- lib/tailmix/
|
|
79
|
-
- lib/tailmix/
|
|
80
|
-
- lib/tailmix/
|
|
81
|
-
- lib/tailmix/
|
|
80
|
+
- lib/tailmix/definition.rb
|
|
81
|
+
- lib/tailmix/definition/context_builder.rb
|
|
82
|
+
- lib/tailmix/definition/contexts/action_builder.rb
|
|
83
|
+
- lib/tailmix/definition/contexts/actions/element_builder.rb
|
|
84
|
+
- lib/tailmix/definition/contexts/attribute_builder.rb
|
|
85
|
+
- lib/tailmix/definition/contexts/dimension_builder.rb
|
|
86
|
+
- lib/tailmix/definition/contexts/element_builder.rb
|
|
87
|
+
- lib/tailmix/definition/contexts/stimulus_builder.rb
|
|
88
|
+
- lib/tailmix/definition/merger.rb
|
|
89
|
+
- lib/tailmix/definition/result.rb
|
|
90
|
+
- lib/tailmix/dev/docs.rb
|
|
91
|
+
- lib/tailmix/dev/stimulus_generator.rb
|
|
92
|
+
- lib/tailmix/dev/tools.rb
|
|
93
|
+
- lib/tailmix/engine.rb
|
|
94
|
+
- lib/tailmix/html/attributes.rb
|
|
95
|
+
- lib/tailmix/html/class_list.rb
|
|
96
|
+
- lib/tailmix/html/data_map.rb
|
|
97
|
+
- lib/tailmix/html/stimulus_builder.rb
|
|
98
|
+
- lib/tailmix/runtime.rb
|
|
99
|
+
- lib/tailmix/runtime/action.rb
|
|
100
|
+
- lib/tailmix/runtime/context.rb
|
|
101
|
+
- lib/tailmix/runtime/facade_builder.rb
|
|
102
|
+
- lib/tailmix/runtime/stimulus/compiler.rb
|
|
82
103
|
- lib/tailmix/version.rb
|
|
83
104
|
- sig/tailmix.rbs
|
|
84
105
|
homepage: https://github.com/alexander-s-f/tailmix
|