view_component_atomix 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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +744 -0
  5. data/lib/view_component/atomix/accessors.rb +129 -0
  6. data/lib/view_component/atomix/attribute_helpers.rb +15 -0
  7. data/lib/view_component/atomix/attrs.rb +17 -0
  8. data/lib/view_component/atomix/base.rb +226 -0
  9. data/lib/view_component/atomix/builder.rb +77 -0
  10. data/lib/view_component/atomix/compiler.rb +147 -0
  11. data/lib/view_component/atomix/condition_resolver.rb +79 -0
  12. data/lib/view_component/atomix/configuration.rb +103 -0
  13. data/lib/view_component/atomix/declaration_queue.rb +50 -0
  14. data/lib/view_component/atomix/dynamic_properties/literal_key.rb +68 -0
  15. data/lib/view_component/atomix/dynamic_properties/source_cache.rb +31 -0
  16. data/lib/view_component/atomix/dynamic_properties/source_parser.rb +61 -0
  17. data/lib/view_component/atomix/dynamic_properties.rb +75 -0
  18. data/lib/view_component/atomix/dynamic_style.rb +109 -0
  19. data/lib/view_component/atomix/enumerated_style.rb +39 -0
  20. data/lib/view_component/atomix/hashing.rb +47 -0
  21. data/lib/view_component/atomix/helper.rb +31 -0
  22. data/lib/view_component/atomix/immutable.rb +20 -0
  23. data/lib/view_component/atomix/known_properties.rb +102 -0
  24. data/lib/view_component/atomix/owner_state.rb +48 -0
  25. data/lib/view_component/atomix/property.rb +183 -0
  26. data/lib/view_component/atomix/railtie.rb +46 -0
  27. data/lib/view_component/atomix/registry.rb +208 -0
  28. data/lib/view_component/atomix/resolution_session.rb +81 -0
  29. data/lib/view_component/atomix/rule.rb +42 -0
  30. data/lib/view_component/atomix/sheet.rb +98 -0
  31. data/lib/view_component/atomix/style.rb +80 -0
  32. data/lib/view_component/atomix/tasks/build.rake +17 -0
  33. data/lib/view_component/atomix/theme_compiler.rb +19 -0
  34. data/lib/view_component/atomix/theme_resolution.rb +66 -0
  35. data/lib/view_component/atomix/trait_classifier.rb +111 -0
  36. data/lib/view_component/atomix/vars.rb +97 -0
  37. data/lib/view_component/atomix/version.rb +7 -0
  38. data/lib/view_component/atomix/weak_collection.rb +62 -0
  39. data/lib/view_component/atomix.rb +301 -0
  40. data/lib/view_component_atomix.rb +3 -0
  41. metadata +162 -0
@@ -0,0 +1,301 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "fileutils"
5
+ require "pathname"
6
+ require "digest/sha1"
7
+ require "monitor"
8
+ require "tempfile"
9
+ require "strscan"
10
+ require "active_support"
11
+ require "active_support/concern"
12
+ require "active_support/core_ext/string/output_safety"
13
+ require "active_support/core_ext/string/inflections"
14
+ require "active_support/core_ext/class/subclasses"
15
+ require "view_component"
16
+
17
+ require_relative "atomix/version"
18
+ require_relative "atomix/configuration"
19
+ require_relative "atomix/hashing"
20
+ require_relative "atomix/property"
21
+ require_relative "atomix/known_properties"
22
+ require_relative "atomix/immutable"
23
+ require_relative "atomix/condition_resolver"
24
+ require_relative "atomix/rule"
25
+ require_relative "atomix/registry"
26
+ require_relative "atomix/weak_collection"
27
+ require_relative "atomix/attrs"
28
+ require_relative "atomix/style"
29
+ require_relative "atomix/dynamic_properties"
30
+ require_relative "atomix/dynamic_style"
31
+ require_relative "atomix/enumerated_style"
32
+ require_relative "atomix/builder"
33
+ require_relative "atomix/sheet"
34
+ require_relative "atomix/vars"
35
+ require_relative "atomix/trait_classifier"
36
+ require_relative "atomix/compiler"
37
+ require_relative "atomix/attribute_helpers"
38
+ require_relative "atomix/helper"
39
+ require_relative "atomix/resolution_session"
40
+ require_relative "atomix/declaration_queue"
41
+ require_relative "atomix/owner_state"
42
+ require_relative "atomix/accessors"
43
+ require_relative "atomix/theme_compiler"
44
+ require_relative "atomix/theme_resolution"
45
+ require_relative "atomix/base"
46
+
47
+ module ViewComponent
48
+ module Atomix
49
+ extend ActiveSupport::Concern
50
+
51
+ class Error < StandardError; end
52
+ class VarsNotConfigured < Error; end
53
+
54
+ RESOLUTION_MONITOR = Monitor.new
55
+
56
+ class << self
57
+ def config
58
+ @config ||= Configuration.new
59
+ end
60
+
61
+ def configure
62
+ yield config
63
+ end
64
+
65
+ def registry
66
+ @registry ||= Registry.new
67
+ end
68
+
69
+ def includers
70
+ @includers ||= WeakCollection.new
71
+ end
72
+
73
+ def dynamic_styles
74
+ @dynamic_styles ||= WeakCollection.new
75
+ end
76
+
77
+ def compile_dynamic_styles!
78
+ dynamic_styles.each(&:compile!)
79
+ end
80
+
81
+ def create(definitions = nil, name: nil, **inline_definitions, &block)
82
+ breakpoints = {}
83
+ if block && definitions.nil? && inline_definitions.empty?
84
+ definitions, breakpoints = Builder.evaluate(nil, block)
85
+ end
86
+ definitions ||= inline_definitions.empty? ? nil : inline_definitions
87
+ raise ArgumentError, "ViewComponent::Atomix.create requires a Hash or a block" if definitions.nil?
88
+
89
+ Compiler.compile(definitions, name: name || caller_name, breakpoints: breakpoints)
90
+ end
91
+
92
+ def define_vars(name: nil, **vars)
93
+ Vars.new(name || caller_name, vars)
94
+ end
95
+
96
+ def create_theme(vars, **overrides)
97
+ raise ArgumentError, "create_theme expects a Vars object" unless vars.is_a?(Vars)
98
+
99
+ ThemeCompiler.compile(vars.id, vars.names, overrides)
100
+ end
101
+
102
+ def vars
103
+ configured_vars ||
104
+ raise(VarsNotConfigured,
105
+ "No vars configured. Set `config.view_component.atomix.vars = -> { Brand::VARS }` " \
106
+ "in an initializer, or assign `ViewComponent::Atomix.vars = ViewComponent::Atomix.define_vars(...)`.")
107
+ end
108
+
109
+ def vars=(vars)
110
+ @vars = vars
111
+ end
112
+
113
+ def configured_vars
114
+ resolved = @vars || config.vars
115
+ resolved = resolved.call if resolved.respond_to?(:call)
116
+ resolved = Object.const_get(resolved) if resolved.is_a?(String)
117
+ resolved.is_a?(Vars) ? resolved : nil
118
+ end
119
+
120
+ def merge(*styles)
121
+ Style.combine(styles.flatten.compact.map { |style| coerce_style(style) })
122
+ end
123
+
124
+ def props(*styles)
125
+ merge(*styles).to_attrs
126
+ end
127
+
128
+ def sx(*styles)
129
+ props(*styles).to_s
130
+ end
131
+
132
+ def eager_load!
133
+ if defined?(Rails) && Rails.respond_to?(:autoloaders) && Rails.application
134
+ config.eager_load_paths.each do |relative|
135
+ dir = Rails.root.join(relative)
136
+ next unless dir.directory?
137
+
138
+ Rails.autoloaders.main.eager_load_dir(dir.to_s)
139
+ end
140
+ end
141
+
142
+ includers.each do |klass|
143
+ targets = klass.is_a?(Class) ? [klass, *klass.descendants] : [klass]
144
+ targets.each do |k|
145
+ next if k.send(:styles_unloaded?)
146
+ k.send(:resolved_styles)
147
+ k.send(:resolved_theme) if k < Theme
148
+ end
149
+ end
150
+ registry
151
+ end
152
+
153
+ def write_css(path = nil, force: false)
154
+ root = defined?(Rails) && Rails.respond_to?(:root) && Rails.root ? Rails.root : Pathname.new(Dir.pwd)
155
+ path = root.join(path || config.output_path)
156
+ css = "#{registry.to_css}\n"
157
+ return path if !force && path.file? && File.binread(path) == css.b
158
+
159
+ FileUtils.mkdir_p(path.dirname)
160
+ Tempfile.create([".view_component_atomix", ".css"], path.dirname) do |file|
161
+ file.chmod(path.exist? ? path.stat.mode & 0o777 : 0o666 & ~File.umask)
162
+ file.write(css)
163
+ file.flush
164
+ File.rename(file.path, path)
165
+ end
166
+ path
167
+ end
168
+
169
+ # A full reset discards declarations on existing owners as well as rules.
170
+ # Standalone Style/Sheet/Vars objects must be recreated by the caller.
171
+ def reset!
172
+ RESOLUTION_MONITOR.synchronize do
173
+ owners.each { |owner| owner.send(:reset_styles!) }
174
+ registry.clear
175
+ dynamic_styles.clear
176
+ @vars = nil
177
+ config.reset! # Keep Rails configuration references valid.
178
+ end
179
+ end
180
+
181
+ def owners
182
+ includers.flat_map { |owner| owner.is_a?(Class) ? [owner, *owner.descendants] : [owner] }.uniq
183
+ end
184
+
185
+ def invalidate_caches!
186
+ RESOLUTION_MONITOR.synchronize do
187
+ owners.each do |owner|
188
+ owner.send(:invalidate_styles_cache!)
189
+ end
190
+ end
191
+ end
192
+
193
+ def unload!(owner, source)
194
+ if owner.is_a?(Module) && owner.respond_to?(:unload_styles!, true)
195
+ owner.send(:unload_styles!)
196
+ includers.delete_if { |candidate| candidate.equal?(owner) }
197
+ end
198
+ dynamic_styles.delete_if { |style| style.source_path == source }
199
+ end
200
+
201
+ private
202
+
203
+ def coerce_style(style)
204
+ case style
205
+ when Style then style
206
+ when false then Style::EMPTY
207
+ when EnumeratedStyle then style.call
208
+ when DynamicStyle
209
+ raise ArgumentError, "Dynamic trait #{style.set.inspect} must be called with arguments before use"
210
+ else
211
+ raise ArgumentError, "Expected a ViewComponent::Atomix::Style, got #{style.class}"
212
+ end
213
+ end
214
+
215
+ def caller_name
216
+ location = caller_locations(2, 1)&.first
217
+ return "anonymous" unless location
218
+
219
+ path = location.path
220
+ root = defined?(Rails) && Rails.respond_to?(:root) && Rails.root ? Rails.root.to_s : Dir.pwd
221
+ path = path.delete_prefix("#{root}/")
222
+ "#{path}:#{location.lineno}"
223
+ end
224
+ end
225
+
226
+ module Mixin
227
+ def append_features(base)
228
+ [ViewComponent::Atomix, Variables, Theme].each do |concern|
229
+ base.include(concern) if self < concern && !(base < concern)
230
+ end
231
+ super
232
+ base.send(:add_styles_mixin, self)
233
+ end
234
+ end
235
+
236
+ module Variables
237
+ extend ActiveSupport::Concern
238
+ include Base
239
+
240
+ included do
241
+ install_styles_method(ViewComponent::Atomix.config.styles_method)
242
+ end
243
+
244
+ class_methods do
245
+ def style_vars(definitions = nil, name: nil, **inline, &block)
246
+ label = macro_label(name)
247
+ source = caller_locations(1, 1).first.path
248
+ styles_resolution_monitor.synchronize do
249
+ if block
250
+ styles_state.variables << DeclarationQueue::VariableDeclaration.new(
251
+ label: label, block: block, source: block.source_location.first
252
+ )
253
+ else
254
+ definitions = inline if definitions.nil?
255
+ Vars.validate_definitions!(definitions)
256
+ raise ArgumentError, "style_vars requires a Hash or a block" if definitions.empty?
257
+
258
+ compile_pending_vars
259
+ ViewComponent::Atomix.registry.with_source(source) { add_vars(label, definitions) }
260
+ end
261
+ invalidate_styles
262
+ end
263
+ nil
264
+ end
265
+ end
266
+ end
267
+
268
+ module Theme
269
+ extend ActiveSupport::Concern
270
+ include Base
271
+
272
+ class_methods do
273
+ def theme(vars = nil, **overrides, &block)
274
+ return resolved_theme if vars.nil? && overrides.empty? && block.nil?
275
+
276
+ raise ArgumentError, "theme expects a Vars object as its first argument" if vars && !vars.is_a?(Vars)
277
+
278
+ source = block ? block.source_location.first : caller_locations(1, 1).first.path
279
+ styles_resolution_monitor.synchronize do
280
+ styles_state.themes << DeclarationQueue::ThemeDeclaration.new(
281
+ vars: vars, overrides: overrides, block: block, source: source
282
+ )
283
+ invalidate_styles
284
+ end
285
+ nil
286
+ end
287
+ end
288
+
289
+ def theme
290
+ self.class.send(:resolved_theme)
291
+ end
292
+ end
293
+
294
+ include Variables
295
+ include Theme
296
+
297
+ include AttributeHelpers
298
+ end
299
+ end
300
+
301
+ require_relative "atomix/railtie" if defined?(Rails::Railtie)
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "view_component/atomix"
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: view_component_atomix
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Peter Park
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: activesupport
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: actionview
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '7.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '7.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: railties
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '7.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '7.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: zeitwerk
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 2.6.2
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 2.6.2
68
+ - !ruby/object:Gem::Dependency
69
+ name: view_component
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '3.0'
75
+ - - "<"
76
+ - !ruby/object:Gem::Version
77
+ version: '5'
78
+ type: :runtime
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '3.0'
85
+ - - "<"
86
+ - !ruby/object:Gem::Version
87
+ version: '5'
88
+ description: Define styles as Ruby hashes next to your components. Compiles to deterministic
89
+ atomic classes with per-property merging, conditions, breakpoints, dynamic values
90
+ and CSS-variable theming. No JavaScript build step.
91
+ email:
92
+ - github@peterpark.io
93
+ executables: []
94
+ extensions: []
95
+ extra_rdoc_files: []
96
+ files:
97
+ - CHANGELOG.md
98
+ - LICENSE.txt
99
+ - README.md
100
+ - lib/view_component/atomix.rb
101
+ - lib/view_component/atomix/accessors.rb
102
+ - lib/view_component/atomix/attribute_helpers.rb
103
+ - lib/view_component/atomix/attrs.rb
104
+ - lib/view_component/atomix/base.rb
105
+ - lib/view_component/atomix/builder.rb
106
+ - lib/view_component/atomix/compiler.rb
107
+ - lib/view_component/atomix/condition_resolver.rb
108
+ - lib/view_component/atomix/configuration.rb
109
+ - lib/view_component/atomix/declaration_queue.rb
110
+ - lib/view_component/atomix/dynamic_properties.rb
111
+ - lib/view_component/atomix/dynamic_properties/literal_key.rb
112
+ - lib/view_component/atomix/dynamic_properties/source_cache.rb
113
+ - lib/view_component/atomix/dynamic_properties/source_parser.rb
114
+ - lib/view_component/atomix/dynamic_style.rb
115
+ - lib/view_component/atomix/enumerated_style.rb
116
+ - lib/view_component/atomix/hashing.rb
117
+ - lib/view_component/atomix/helper.rb
118
+ - lib/view_component/atomix/immutable.rb
119
+ - lib/view_component/atomix/known_properties.rb
120
+ - lib/view_component/atomix/owner_state.rb
121
+ - lib/view_component/atomix/property.rb
122
+ - lib/view_component/atomix/railtie.rb
123
+ - lib/view_component/atomix/registry.rb
124
+ - lib/view_component/atomix/resolution_session.rb
125
+ - lib/view_component/atomix/rule.rb
126
+ - lib/view_component/atomix/sheet.rb
127
+ - lib/view_component/atomix/style.rb
128
+ - lib/view_component/atomix/tasks/build.rake
129
+ - lib/view_component/atomix/theme_compiler.rb
130
+ - lib/view_component/atomix/theme_resolution.rb
131
+ - lib/view_component/atomix/trait_classifier.rb
132
+ - lib/view_component/atomix/vars.rb
133
+ - lib/view_component/atomix/version.rb
134
+ - lib/view_component/atomix/weak_collection.rb
135
+ - lib/view_component_atomix.rb
136
+ homepage: https://github.com/pparker13122/view_component_atomix
137
+ licenses:
138
+ - MIT
139
+ metadata:
140
+ homepage_uri: https://github.com/pparker13122/view_component_atomix
141
+ source_code_uri: https://github.com/pparker13122/view_component_atomix
142
+ changelog_uri: https://github.com/pparker13122/view_component_atomix/blob/main/CHANGELOG.md
143
+ bug_tracker_uri: https://github.com/pparker13122/view_component_atomix/issues
144
+ rubygems_mfa_required: 'true'
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '3.1'
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ requirements: []
159
+ rubygems_version: 4.0.3
160
+ specification_version: 4
161
+ summary: StyleX-style atomic CSS for ViewComponent and Rails
162
+ test_files: []