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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +76 -145
  4. data/app/javascript/tailmix/runtime/action_dispatcher.js +132 -0
  5. data/app/javascript/tailmix/runtime/component.js +130 -0
  6. data/app/javascript/tailmix/runtime/index.js +130 -0
  7. data/app/javascript/tailmix/runtime/plugins.js +35 -0
  8. data/app/javascript/tailmix/runtime/updater.js +140 -0
  9. data/docs/01_getting_started.md +39 -0
  10. data/examples/_modal_component.arb +17 -25
  11. data/examples/button.rb +81 -0
  12. data/examples/helpers.rb +25 -0
  13. data/examples/modal_component.rb +32 -164
  14. data/lib/tailmix/definition/builders/action_builder.rb +39 -0
  15. data/lib/tailmix/definition/{contexts → builders}/actions/element_builder.rb +1 -1
  16. data/lib/tailmix/definition/{contexts → builders}/attribute_builder.rb +1 -1
  17. data/lib/tailmix/definition/builders/component_builder.rb +81 -0
  18. data/lib/tailmix/definition/{contexts → builders}/dimension_builder.rb +2 -2
  19. data/lib/tailmix/definition/builders/element_builder.rb +83 -0
  20. data/lib/tailmix/definition/builders/reactor_builder.rb +43 -0
  21. data/lib/tailmix/definition/builders/rule_builder.rb +58 -0
  22. data/lib/tailmix/definition/builders/state_builder.rb +21 -0
  23. data/lib/tailmix/definition/{contexts → builders}/variant_builder.rb +17 -2
  24. data/lib/tailmix/definition/context_builder.rb +19 -13
  25. data/lib/tailmix/definition/merger.rb +2 -1
  26. data/lib/tailmix/definition/payload_proxy.rb +16 -0
  27. data/lib/tailmix/definition/result.rb +17 -18
  28. data/lib/tailmix/dev/docs.rb +32 -7
  29. data/lib/tailmix/dev/tools.rb +0 -5
  30. data/lib/tailmix/dsl.rb +3 -5
  31. data/lib/tailmix/engine.rb +11 -1
  32. data/lib/tailmix/html/attributes.rb +22 -12
  33. data/lib/tailmix/middleware/registry_cleaner.rb +17 -0
  34. data/lib/tailmix/registry.rb +37 -0
  35. data/lib/tailmix/runtime/action_proxy.rb +29 -0
  36. data/lib/tailmix/runtime/attribute_builder.rb +102 -0
  37. data/lib/tailmix/runtime/attribute_cache.rb +23 -0
  38. data/lib/tailmix/runtime/context.rb +51 -47
  39. data/lib/tailmix/runtime/facade_builder.rb +8 -5
  40. data/lib/tailmix/runtime/state.rb +36 -0
  41. data/lib/tailmix/runtime/state_proxy.rb +34 -0
  42. data/lib/tailmix/runtime.rb +0 -1
  43. data/lib/tailmix/version.rb +1 -1
  44. data/lib/tailmix/view_helpers.rb +49 -0
  45. data/lib/tailmix.rb +4 -2
  46. metadata +34 -21
  47. data/app/javascript/tailmix/finder.js +0 -15
  48. data/app/javascript/tailmix/index.js +0 -7
  49. data/app/javascript/tailmix/mutator.js +0 -28
  50. data/app/javascript/tailmix/runner.js +0 -7
  51. data/app/javascript/tailmix/stimulus_adapter.js +0 -37
  52. data/lib/tailmix/definition/contexts/action_builder.rb +0 -31
  53. data/lib/tailmix/definition/contexts/element_builder.rb +0 -41
  54. data/lib/tailmix/definition/contexts/stimulus_builder.rb +0 -101
  55. data/lib/tailmix/dev/stimulus_generator.rb +0 -124
  56. data/lib/tailmix/runtime/stimulus/compiler.rb +0 -59
@@ -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