live_component 0.1.0
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 +7 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +1 -0
- data/LICENSE +21 -0
- data/Rakefile +41 -0
- data/app/channels/live_component_channel.rb +23 -0
- data/app/components/live_component/render_component.rb +43 -0
- data/app/controllers/live_component/render_controller.rb +9 -0
- data/app/helpers/live_component/application_helper.rb +40 -0
- data/app/views/live_component/render/show.html.erb +1 -0
- data/config/routes.rb +5 -0
- data/ext/view_component_patch.rb +22 -0
- data/lib/live_component/action.rb +31 -0
- data/lib/live_component/base.rb +267 -0
- data/lib/live_component/big_decimal_serializer.rb +17 -0
- data/lib/live_component/component.html.erb +4 -0
- data/lib/live_component/controller_methods.rb +9 -0
- data/lib/live_component/date_serializer.rb +15 -0
- data/lib/live_component/date_time_serializer.rb +11 -0
- data/lib/live_component/duration_serializer.rb +18 -0
- data/lib/live_component/engine.rb +21 -0
- data/lib/live_component/inline_serializer.rb +34 -0
- data/lib/live_component/middleware.rb +25 -0
- data/lib/live_component/model_serializer.rb +65 -0
- data/lib/live_component/module_serializer.rb +15 -0
- data/lib/live_component/object_serializer.rb +29 -0
- data/lib/live_component/range_serializer.rb +19 -0
- data/lib/live_component/react.rb +24 -0
- data/lib/live_component/record_proxy.rb +85 -0
- data/lib/live_component/safe_dispatcher.rb +64 -0
- data/lib/live_component/serializer.rb +202 -0
- data/lib/live_component/state.rb +91 -0
- data/lib/live_component/tag_builder.rb +20 -0
- data/lib/live_component/target.rb +26 -0
- data/lib/live_component/time_object_serializer.rb +13 -0
- data/lib/live_component/time_serializer.rb +11 -0
- data/lib/live_component/time_with_zone_serializer.rb +20 -0
- data/lib/live_component/utils.rb +139 -0
- data/lib/live_component/version.rb +5 -0
- data/lib/live_component.rb +55 -0
- data/lib/tasks/test.rake +1 -0
- data/live_component.gemspec +21 -0
- metadata +136 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LiveComponent
|
|
4
|
+
class TimeWithZoneSerializer < ObjectSerializer
|
|
5
|
+
NANO_PRECISION = 9
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def object_to_hash(time_with_zone)
|
|
10
|
+
{
|
|
11
|
+
"value" => time_with_zone.iso8601(NANO_PRECISION),
|
|
12
|
+
"time_zone" => time_with_zone.time_zone.tzinfo.name
|
|
13
|
+
}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def hash_to_object(hash)
|
|
17
|
+
Time.iso8601(hash["value"]).in_time_zone(hash["time_zone"] || Time.zone)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LiveComponent
|
|
4
|
+
module Utils
|
|
5
|
+
WHITESPACE_REGEX = /[\f\n\r\t\v ]{2,}/
|
|
6
|
+
PLURAL_DATA_ATTRIBUTES = %i[action target].freeze
|
|
7
|
+
|
|
8
|
+
def normalize_html_whitespace(str)
|
|
9
|
+
str.gsub(WHITESPACE_REGEX, " ")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def lookup_component_class(const_str)
|
|
13
|
+
const = safe_lookup_component_class(const_str)
|
|
14
|
+
return const if const
|
|
15
|
+
|
|
16
|
+
raise UnexpectedConstantError,
|
|
17
|
+
"cannot find constant '#{const_str}' that does not inherit from ViewComponent::Base"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def safe_lookup_component_class(const_str)
|
|
21
|
+
const = const_str.safe_constantize
|
|
22
|
+
|
|
23
|
+
if const && const < ::ViewComponent::Base
|
|
24
|
+
const
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def html_params_for_rerender(object)
|
|
29
|
+
case object
|
|
30
|
+
when :self, :parent
|
|
31
|
+
return { "data-rerender-target" => ":#{object}" }
|
|
32
|
+
when Symbol
|
|
33
|
+
const = safe_lookup_component_class(object.to_s)
|
|
34
|
+
return({ "data-rerender-target" => const.name }) if const
|
|
35
|
+
|
|
36
|
+
raise UnexpectedConstantError, "cannot find constant '#{object}' to use as a rerender target"
|
|
37
|
+
when String
|
|
38
|
+
return { "data-rerender-id" => object }
|
|
39
|
+
when Class
|
|
40
|
+
if object < LiveComponent::Base
|
|
41
|
+
return { "data-rerender-target" => object.name }
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
if object.respond_to?(:__lc_id)
|
|
45
|
+
return { "data-rerender-id" => object.__lc_id }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def translate_all_attrs(kwargs)
|
|
53
|
+
# TODO: allow folks to add their own attr classes?
|
|
54
|
+
[Action, Target].inject(kwargs) do |memo, attr_klass|
|
|
55
|
+
if kwargs.include?(attr_klass.attr_name)
|
|
56
|
+
translate_attrs(attr_klass, memo)
|
|
57
|
+
else
|
|
58
|
+
memo
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def translate_attrs(attr_klass, kwargs)
|
|
64
|
+
if kwargs[attr_klass.attr_name].is_a?(Array)
|
|
65
|
+
return kwargs unless kwargs[attr_klass.attr_name].all? { |action| action.is_a?(attr_klass) }
|
|
66
|
+
else
|
|
67
|
+
return kwargs unless kwargs[attr_klass.attr_name].is_a?(attr_klass)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
attrs = Array(kwargs.delete(attr_klass.attr_name))
|
|
71
|
+
|
|
72
|
+
attrs.each do |attr|
|
|
73
|
+
kwargs[:data] = merge_data(kwargs, attr.to_attributes)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
kwargs
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Borrowed from primer_view_components
|
|
80
|
+
# See: https://github.com/primer/view_components/blob/b0acdfffaa30e606a07db657d9b444b4de8ca860/app/lib/primer/attributes_helper.rb
|
|
81
|
+
#
|
|
82
|
+
# Merges hashes that contain "data-*" keys and nested data: hashes. Removes keys from
|
|
83
|
+
# each hash and returns them in the new hash.
|
|
84
|
+
#
|
|
85
|
+
# Eg. merge_data({ "data-foo": "true" }, { data: { bar: "true" } })
|
|
86
|
+
# => { foo: "true", bar: "true" }
|
|
87
|
+
#
|
|
88
|
+
# Certain data attributes can contain multiple values separated by spaces. merge_data
|
|
89
|
+
# will combine these plural attributes into a composite string.
|
|
90
|
+
#
|
|
91
|
+
# Eg. merge_data({ "data-target": "foo" }, { data: { target: "bar" } })
|
|
92
|
+
# => { target: "foo bar" }
|
|
93
|
+
def merge_data(*hashes)
|
|
94
|
+
merge_prefixed_attribute_hashes(
|
|
95
|
+
*hashes, prefix: :data, plural_keys: PLURAL_DATA_ATTRIBUTES
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def merge_prefixed_attribute_hashes(*hashes, prefix:, plural_keys:)
|
|
100
|
+
{}.tap do |result|
|
|
101
|
+
hashes.each do |hash|
|
|
102
|
+
next unless hash
|
|
103
|
+
|
|
104
|
+
prefix_hash = hash.delete(prefix) || {}
|
|
105
|
+
|
|
106
|
+
prefix_hash.each_pair do |key, val|
|
|
107
|
+
result[key] =
|
|
108
|
+
if plural_keys.include?(key)
|
|
109
|
+
[*(result[key] || "").split, val].join(" ").strip
|
|
110
|
+
else
|
|
111
|
+
val
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
hash.delete_if do |key, val|
|
|
116
|
+
key_s = key.to_s
|
|
117
|
+
|
|
118
|
+
if key.start_with?("#{prefix}-")
|
|
119
|
+
bare_key = key_s.sub("#{prefix}-", "").to_sym
|
|
120
|
+
|
|
121
|
+
result[bare_key] =
|
|
122
|
+
if plural_keys.include?(bare_key)
|
|
123
|
+
[*(result[bare_key] || "").split, val].join(" ").strip
|
|
124
|
+
else
|
|
125
|
+
val
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
true
|
|
129
|
+
else
|
|
130
|
+
false
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
extend self
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LiveComponent
|
|
4
|
+
autoload :Action, "live_component/action"
|
|
5
|
+
autoload :Base, "live_component/base"
|
|
6
|
+
autoload :BigDecimalSerializer, "live_component/big_decimal_serializer"
|
|
7
|
+
autoload :ControllerMethods, "live_component/controller_methods"
|
|
8
|
+
autoload :DateSerializer, "live_component/date_serializer"
|
|
9
|
+
autoload :DateTimeSerializer, "live_component/date_time_serializer"
|
|
10
|
+
autoload :DurationSerializer, "live_component/duration_serializer"
|
|
11
|
+
autoload :InlineSerializer, "live_component/inline_serializer"
|
|
12
|
+
autoload :Middleware, "live_component/middleware"
|
|
13
|
+
autoload :ModelSerializer, "live_component/model_serializer"
|
|
14
|
+
autoload :ModuleSerializer, "live_component/module_serializer"
|
|
15
|
+
autoload :ObjectSerializer, "live_component/object_serializer"
|
|
16
|
+
autoload :RangeSerializer, "live_component/range_serializer"
|
|
17
|
+
autoload :RecordProxy, "live_component/record_proxy"
|
|
18
|
+
autoload :SafeDispatcher, "live_component/safe_dispatcher"
|
|
19
|
+
autoload :Serializer, "live_component/serializer"
|
|
20
|
+
autoload :TimeWithZoneSerializer, "live_component/time_with_zone_serializer"
|
|
21
|
+
autoload :TagBuilder, "live_component/tag_builder"
|
|
22
|
+
autoload :Target, "live_component/target"
|
|
23
|
+
autoload :TimeObjectSerializer, "live_component/time_object_serializer"
|
|
24
|
+
autoload :TimeSerializer, "live_component/time_serializer"
|
|
25
|
+
autoload :TimeWithZoneSerializer, "live_component/time_with_zone_serializer"
|
|
26
|
+
autoload :React, "live_component/react"
|
|
27
|
+
autoload :State, "live_component/state"
|
|
28
|
+
autoload :Utils, "live_component/utils"
|
|
29
|
+
|
|
30
|
+
class UnexpectedConstantError < StandardError; end
|
|
31
|
+
class SerializationError < ArgumentError; end
|
|
32
|
+
class SafeDispatchError < StandardError; end
|
|
33
|
+
|
|
34
|
+
class << self
|
|
35
|
+
def register_prop_serializer(name, klass)
|
|
36
|
+
registered_prop_serializers[name] = klass
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def registered_prop_serializers
|
|
40
|
+
@registered_prop_serializers ||= {}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def serializer
|
|
44
|
+
@serializer ||= Serializer.make
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
LiveComponent.register_prop_serializer(:model_serializer, LiveComponent::ModelSerializer)
|
|
50
|
+
|
|
51
|
+
if defined?(Rails)
|
|
52
|
+
require "live_component/engine"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
require File.join(File.dirname(__dir__), "ext", "view_component_patch")
|
data/lib/tasks/test.rake
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# System tests are defined in the Rakefile to avoid Rails test infrastructure issues
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), "lib")
|
|
2
|
+
require "live_component/version"
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |s|
|
|
5
|
+
s.name = "live_component"
|
|
6
|
+
s.version = ::LiveComponent::VERSION
|
|
7
|
+
s.authors = ["Cameron Dutro"]
|
|
8
|
+
s.email = ["camertron@gmail.com"]
|
|
9
|
+
s.homepage = "http://github.com/camertron/live_component"
|
|
10
|
+
s.description = s.summary = "Client-side rendering and state management for ViewComponent."
|
|
11
|
+
s.platform = Gem::Platform::RUBY
|
|
12
|
+
|
|
13
|
+
s.add_dependency "railties", "~> 8.0"
|
|
14
|
+
s.add_dependency "view_component", "~> 4.0"
|
|
15
|
+
s.add_dependency "use_context", "~> 1.2"
|
|
16
|
+
s.add_dependency "weak_key_map", "~> 1.0"
|
|
17
|
+
|
|
18
|
+
s.require_path = "lib"
|
|
19
|
+
|
|
20
|
+
s.files = Dir["{app,config,ext,lib}/**/*", "Gemfile", "LICENSE", "CHANGELOG.md", "README.md", "Rakefile", "live_component.gemspec"]
|
|
21
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: live_component
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Cameron Dutro
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: railties
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '8.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '8.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: view_component
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '4.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '4.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: use_context
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.2'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.2'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: weak_key_map
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '1.0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '1.0'
|
|
68
|
+
description: Client-side rendering and state management for ViewComponent.
|
|
69
|
+
email:
|
|
70
|
+
- camertron@gmail.com
|
|
71
|
+
executables: []
|
|
72
|
+
extensions: []
|
|
73
|
+
extra_rdoc_files: []
|
|
74
|
+
files:
|
|
75
|
+
- CHANGELOG.md
|
|
76
|
+
- Gemfile
|
|
77
|
+
- LICENSE
|
|
78
|
+
- Rakefile
|
|
79
|
+
- app/channels/live_component_channel.rb
|
|
80
|
+
- app/components/live_component/render_component.rb
|
|
81
|
+
- app/controllers/live_component/render_controller.rb
|
|
82
|
+
- app/helpers/live_component/application_helper.rb
|
|
83
|
+
- app/views/live_component/render/show.html.erb
|
|
84
|
+
- config/routes.rb
|
|
85
|
+
- ext/view_component_patch.rb
|
|
86
|
+
- lib/live_component.rb
|
|
87
|
+
- lib/live_component/action.rb
|
|
88
|
+
- lib/live_component/base.rb
|
|
89
|
+
- lib/live_component/big_decimal_serializer.rb
|
|
90
|
+
- lib/live_component/component.html.erb
|
|
91
|
+
- lib/live_component/controller_methods.rb
|
|
92
|
+
- lib/live_component/date_serializer.rb
|
|
93
|
+
- lib/live_component/date_time_serializer.rb
|
|
94
|
+
- lib/live_component/duration_serializer.rb
|
|
95
|
+
- lib/live_component/engine.rb
|
|
96
|
+
- lib/live_component/inline_serializer.rb
|
|
97
|
+
- lib/live_component/middleware.rb
|
|
98
|
+
- lib/live_component/model_serializer.rb
|
|
99
|
+
- lib/live_component/module_serializer.rb
|
|
100
|
+
- lib/live_component/object_serializer.rb
|
|
101
|
+
- lib/live_component/range_serializer.rb
|
|
102
|
+
- lib/live_component/react.rb
|
|
103
|
+
- lib/live_component/record_proxy.rb
|
|
104
|
+
- lib/live_component/safe_dispatcher.rb
|
|
105
|
+
- lib/live_component/serializer.rb
|
|
106
|
+
- lib/live_component/state.rb
|
|
107
|
+
- lib/live_component/tag_builder.rb
|
|
108
|
+
- lib/live_component/target.rb
|
|
109
|
+
- lib/live_component/time_object_serializer.rb
|
|
110
|
+
- lib/live_component/time_serializer.rb
|
|
111
|
+
- lib/live_component/time_with_zone_serializer.rb
|
|
112
|
+
- lib/live_component/utils.rb
|
|
113
|
+
- lib/live_component/version.rb
|
|
114
|
+
- lib/tasks/test.rake
|
|
115
|
+
- live_component.gemspec
|
|
116
|
+
homepage: http://github.com/camertron/live_component
|
|
117
|
+
licenses: []
|
|
118
|
+
metadata: {}
|
|
119
|
+
rdoc_options: []
|
|
120
|
+
require_paths:
|
|
121
|
+
- lib
|
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
123
|
+
requirements:
|
|
124
|
+
- - ">="
|
|
125
|
+
- !ruby/object:Gem::Version
|
|
126
|
+
version: '0'
|
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0'
|
|
132
|
+
requirements: []
|
|
133
|
+
rubygems_version: 3.6.7
|
|
134
|
+
specification_version: 4
|
|
135
|
+
summary: Client-side rendering and state management for ViewComponent.
|
|
136
|
+
test_files: []
|