style_capsule 1.0.2 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a5a88ff582594a22522f2b0349e54bfb235d42839a80b29a9118488a09f82df
4
- data.tar.gz: 4bf81ae48a9cfe5ef9e0b7c5f8ae00abeb38dbdf7ea2dc930293036b1c375832
3
+ metadata.gz: 9ca5bf9bf535558147384d22380ad4b54c6b21f85054e578a84f74d35f1d6296
4
+ data.tar.gz: 93c5a155435df7d39f6a2a739d0e6c0be1b99274bf8ebf658e2aab76f065bda1
5
5
  SHA512:
6
- metadata.gz: a75e75f5c7d04616306d2ddaaea7a528eb2d4179071cb96ff6e360d854e940dddcfbfe271bf65fb3e6b4d5957961e5c116708f4017d5e9becd1ee3a76053bce3
7
- data.tar.gz: b7981f706126585a59659ed0c6e3ec61c40a85767ac72c16a9908c6739a5956bcd2bb9c24f20593bc42402ecccfc5a747b042f85b13a07268dfe747555cb56c4
6
+ metadata.gz: 34c0a4456e242d881d0c74ca58a726ad66f953ef2fddb41ba5d38fd98219588b6ed85cd07afa2fe352b34d3c75666496af28b6c2e94bbc8b24edcc65823738b3
7
+ data.tar.gz: 8cac6b0eb300d2030883313d70d69fc16063630bc9dd4efac6ed4396049814187aa83abe56b14d753baabb9afdc3eb3a0c7ce14aba8c49543aa9b7e4ce7ca381
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.1.0 (2025-11-21)
4
+
5
+ - Made Rails dependencies optional: `railties` and `activesupport` moved to development dependencies
6
+ - Core functionality now works without Rails (Sinatra, Hanami, plain Ruby, etc.)
7
+ - Rails integration remains fully supported via Railtie
8
+ - Added `StyleCapsule::StandaloneHelper` for non-Rails frameworks
9
+ - `StylesheetRegistry` now works without `ActiveSupport::CurrentAttributes` using thread-local storage fallback
10
+ - Renamed `stylesheet_registrymap_tags` to `stylesheet_registry_tags` (old name kept as deprecated alias)
11
+ - Extracted CSS building logic from Rake tasks into `StyleCapsule::ComponentBuilder`
12
+ - Fixed XSS vulnerability in `escape_html_attr` by using `CGI.escapeHTML` for proper HTML entity escaping
13
+ - Optimized ActiveSupport require to avoid exception handling overhead in Rails apps
14
+
3
15
  ## 1.0.2 (2025-11-21)
4
16
 
5
17
  - Fix default output directory for CSS files to app/assets/builds/capsules/
@@ -26,4 +38,3 @@
26
38
  - Security features: path traversal protection, CSS size limits (1MB), scope ID validation, filename validation
27
39
  - Ruby >= 3.0 requirement
28
40
  - Comprehensive test suite with > 93% coverage
29
-
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # style_capsule
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/style_capsule.svg?v=1.0.1)](https://badge.fury.io/rb/style_capsule) [![Test Status](https://github.com/amkisko/style_capsule.rb/actions/workflows/test.yml/badge.svg)](https://github.com/amkisko/style_capsule.rb/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/amkisko/style_capsule.rb/graph/badge.svg?token=2U6NXJOVVM)](https://codecov.io/gh/amkisko/style_capsule.rb)
3
+ [![Gem Version](https://badge.fury.io/rb/style_capsule.svg?v=1.1.0)](https://badge.fury.io/rb/style_capsule) [![Test Status](https://github.com/amkisko/style_capsule.rb/actions/workflows/test.yml/badge.svg)](https://github.com/amkisko/style_capsule.rb/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/amkisko/style_capsule.rb/graph/badge.svg?token=2U6NXJOVVM)](https://codecov.io/gh/amkisko/style_capsule.rb)
4
4
 
5
- CSS scoping extension for Rails components. Provides attribute-based style encapsulation for Phlex, ViewComponent, and ERB templates to prevent style leakage between components. Includes configurable caching strategies for optimal performance.
5
+ CSS scoping extension for Ruby components. Provides attribute-based style encapsulation for Phlex, ViewComponent, and ERB templates to prevent style leakage between components. Works with Rails and can be used standalone in other Ruby frameworks (Sinatra, Hanami, etc.) or plain Ruby scripts. Includes configurable caching strategies for optimal performance.
6
6
 
7
7
  Sponsored by [Kisko Labs](https://www.kiskolabs.com).
8
8
 
@@ -161,8 +161,8 @@ Then in your layout:
161
161
 
162
162
  ```erb
163
163
  <head>
164
- <%= stylesheet_registrymap_tags %>
165
- <%= stylesheet_registrymap_tags(namespace: :admin) %>
164
+ <%= stylesheet_registry_tags %>
165
+ <%= stylesheet_registry_tags(namespace: :admin) %>
166
166
  </head>
167
167
  ```
168
168
 
@@ -170,7 +170,7 @@ Or in Phlex (requires including `StyleCapsule::PhlexHelper`):
170
170
 
171
171
  ```ruby
172
172
  head do
173
- stylesheet_registrymap_tags
173
+ stylesheet_registry_tags
174
174
  end
175
175
  ```
176
176
 
@@ -205,7 +205,7 @@ def call
205
205
  end
206
206
  ```
207
207
 
208
- Registered files are rendered via `stylesheet_registrymap_tags` in your layout, just like inline CSS.
208
+ Registered files are rendered via `stylesheet_registry_tags` in your layout, just like inline CSS.
209
209
 
210
210
  ## Caching Strategies
211
211
 
@@ -332,6 +332,90 @@ end
332
332
  - Component-scoped selectors: `:host`, `:host(.active)`, `:host-context(.theme-dark)`
333
333
  - Media queries: `@media (max-width: 768px) { ... }`
334
334
 
335
+ ## Requirements
336
+
337
+ - Ruby >= 3.0
338
+ - Rails >= 6.0, < 9.0 (optional, for Rails integration)
339
+ - ActiveSupport >= 6.0, < 9.0 (optional, for Rails integration)
340
+
341
+ **Note**: The gem can be used without Rails! See [Non-Rails Support](#non-rails-support) below.
342
+
343
+ ## Non-Rails Support
344
+
345
+ StyleCapsule can be used without Rails! The core functionality is framework-agnostic.
346
+
347
+ ### Standalone Usage
348
+
349
+ ```ruby
350
+ require 'style_capsule'
351
+
352
+ # Direct CSS processing
353
+ css = ".section { color: red; }"
354
+ capsule_id = "abc123"
355
+ scoped = StyleCapsule::CssProcessor.scope_selectors(css, capsule_id)
356
+ # => "[data-capsule=\"abc123\"] .section { color: red; }"
357
+ ```
358
+
359
+ ### Phlex Without Rails
360
+
361
+ ```ruby
362
+ require 'phlex'
363
+ require 'style_capsule'
364
+
365
+ class MyComponent < Phlex::HTML
366
+ include StyleCapsule::Component
367
+
368
+ def component_styles
369
+ <<~CSS
370
+ .section { color: red; }
371
+ CSS
372
+ end
373
+
374
+ def view_template
375
+ div(class: "section") { "Hello" }
376
+ end
377
+ end
378
+ ```
379
+
380
+ ### Sinatra
381
+
382
+ ```ruby
383
+ require 'sinatra'
384
+ require 'style_capsule'
385
+
386
+ class MyApp < Sinatra::Base
387
+ helpers StyleCapsule::StandaloneHelper
388
+
389
+ get '/' do
390
+ erb :index
391
+ end
392
+ end
393
+ ```
394
+
395
+ ```erb
396
+ <!-- views/index.erb -->
397
+ <%= style_capsule do %>
398
+ <style>
399
+ .section { color: red; }
400
+ </style>
401
+ <div class="section">Content</div>
402
+ <% end %>
403
+ ```
404
+
405
+ ### Stylesheet Registry Without Rails
406
+
407
+ The stylesheet registry automatically uses thread-local storage when ActiveSupport is not available:
408
+
409
+ ```ruby
410
+ require 'style_capsule'
411
+
412
+ # Works without Rails
413
+ StyleCapsule::StylesheetRegistry.register_inline(".test { color: red; }", namespace: :test)
414
+ stylesheets = StyleCapsule::StylesheetRegistry.request_inline_stylesheets
415
+ ```
416
+
417
+ For more details, see [docs/non_rails_support.md](docs/non_rails_support.md).
418
+
335
419
  ## How It Works
336
420
 
337
421
  1. **Scope ID Generation**: Each component class gets a unique scope ID based on its class name (shared across all instances)
@@ -339,12 +423,6 @@ end
339
423
  3. **HTML Wrapping**: Component content is automatically wrapped in a scoped element
340
424
  4. **No Class Renaming**: Class names remain unchanged (unlike Shadow DOM)
341
425
 
342
- ## Requirements
343
-
344
- - Ruby >= 3.0
345
- - Rails >= 7.0, < 9.0
346
- - ActiveSupport >= 7.0, < 9.0
347
-
348
426
  ## Development
349
427
 
350
428
  ```bash
@@ -395,4 +473,4 @@ For detailed security information, see [SECURITY.md](SECURITY.md).
395
473
 
396
474
  ## License
397
475
 
398
- The gem is available as open source under the terms of the [MIT License](LICENSE.md).
476
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "digest/sha1"
4
- require "active_support/core_ext/string"
4
+ # ActiveSupport string extensions are conditionally required in lib/style_capsule.rb
5
5
 
6
6
  module StyleCapsule
7
7
  # Phlex component concern for encapsulated CSS
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StyleCapsule
4
+ # Builds CSS files from StyleCapsule components
5
+ #
6
+ # This class extracts the logic from the rake task so it can be tested independently.
7
+ # The rake task delegates to this class.
8
+ class ComponentBuilder
9
+ class << self
10
+ # Check if Phlex is available
11
+ # This method can be stubbed in tests to test fallback paths
12
+ def phlex_available?
13
+ !!defined?(Phlex::HTML)
14
+ end
15
+
16
+ # Check if ViewComponent is available
17
+ # This method can be stubbed in tests to test fallback paths
18
+ def view_component_available?
19
+ !!defined?(ViewComponent::Base)
20
+ end
21
+
22
+ # Find all Phlex components that use StyleCapsule
23
+ #
24
+ # @return [Array<Class>] Array of component classes
25
+ def find_phlex_components
26
+ return [] unless phlex_available?
27
+
28
+ components = []
29
+ ObjectSpace.each_object(Class) do |klass|
30
+ if klass < Phlex::HTML && klass.included_modules.include?(StyleCapsule::Component)
31
+ components << klass
32
+ end
33
+ end
34
+ components
35
+ end
36
+
37
+ # Find all ViewComponent components that use StyleCapsule
38
+ #
39
+ # @return [Array<Class>] Array of component classes
40
+ def find_view_components
41
+ return [] unless view_component_available?
42
+
43
+ components = []
44
+ begin
45
+ ObjectSpace.each_object(Class) do |klass|
46
+ if klass < ViewComponent::Base && klass.included_modules.include?(StyleCapsule::ViewComponent)
47
+ components << klass
48
+ end
49
+ rescue
50
+ # Skip this class if checking inheritance triggers ViewComponent loading errors
51
+ # (e.g., ViewComponent 2.83.0 has a bug with Gem::Version#to_f)
52
+ next
53
+ end
54
+ rescue
55
+ # ViewComponent may have loading issues (e.g., version compatibility)
56
+ # Silently skip ViewComponent components if there's an error
57
+ # This allows the rake task to continue with Phlex components
58
+ end
59
+ components
60
+ end
61
+
62
+ # Collect all component classes that use StyleCapsule
63
+ #
64
+ # @return [Array<Class>] Array of component classes
65
+ def collect_components
66
+ find_phlex_components + find_view_components
67
+ end
68
+
69
+ # Build CSS file for a single component
70
+ #
71
+ # @param component_class [Class] Component class to build
72
+ # @param output_proc [Proc, nil] Optional proc to call with output messages
73
+ # @return [String, nil] Generated file path or nil if skipped
74
+ def build_component(component_class, output_proc: nil)
75
+ return nil unless component_class.inline_cache_strategy == :file
76
+ # Check for class method component_styles (required for file caching)
77
+ return nil unless component_class.respond_to?(:component_styles, false)
78
+
79
+ begin
80
+ # Use class method component_styles for file caching
81
+ css_content = component_class.component_styles
82
+ return nil if css_content.nil? || css_content.to_s.strip.empty?
83
+
84
+ # Create a temporary instance to get capsule
85
+ # Some components might require arguments, so we catch errors
86
+ instance = component_class.new
87
+ capsule_id = instance.component_capsule
88
+ scoped_css = instance.send(:scope_css, css_content)
89
+
90
+ # Write CSS file
91
+ file_path = CssFileWriter.write_css(
92
+ css_content: scoped_css,
93
+ component_class: component_class,
94
+ capsule_id: capsule_id
95
+ )
96
+
97
+ output_proc&.call("Generated: #{file_path}") if file_path
98
+ file_path
99
+ rescue ArgumentError, NoMethodError => e
100
+ # Component requires arguments or has dependencies - skip it
101
+ output_proc&.call("Skipped #{component_class.name}: #{e.message}")
102
+ nil
103
+ end
104
+ end
105
+
106
+ # Build CSS files for all components
107
+ #
108
+ # @param output_proc [Proc, nil] Optional proc to call with output messages
109
+ # @return [Integer] Number of files generated
110
+ def build_all(output_proc: nil)
111
+ require "style_capsule/css_file_writer"
112
+
113
+ # Ensure output directory exists
114
+ CssFileWriter.ensure_output_directory
115
+
116
+ # Collect all component classes that use StyleCapsule
117
+ component_classes = collect_components
118
+
119
+ # Generate CSS files for each component
120
+ generated_count = 0
121
+ component_classes.each do |component_class|
122
+ file_path = build_component(component_class, output_proc: output_proc)
123
+ generated_count += 1 if file_path
124
+ end
125
+
126
+ output_proc&.call("StyleCapsule CSS files built successfully")
127
+ generated_count
128
+ end
129
+ end
130
+ end
131
+ end
@@ -52,7 +52,7 @@ module StyleCapsule
52
52
 
53
53
  @output_dir = if output_dir
54
54
  output_dir.is_a?(Pathname) ? output_dir : Pathname.new(output_dir.to_s)
55
- elsif defined?(Rails) && Rails.root
55
+ elsif rails_available?
56
56
  Rails.root.join(DEFAULT_OUTPUT_DIR)
57
57
  else
58
58
  Pathname.new(DEFAULT_OUTPUT_DIR)
@@ -154,11 +154,17 @@ module StyleCapsule
154
154
  @enabled != false
155
155
  end
156
156
 
157
+ # Check if Rails is available
158
+ # This method can be stubbed in tests to test fallback paths
159
+ def rails_available?
160
+ defined?(Rails) && Rails.respond_to?(:root) && Rails.root
161
+ end
162
+
157
163
  private
158
164
 
159
165
  # Get output directory (with default)
160
166
  def output_directory
161
- @output_dir ||= if defined?(Rails) && Rails.root
167
+ @output_dir ||= if rails_available?
162
168
  Rails.root.join(DEFAULT_OUTPUT_DIR)
163
169
  else
164
170
  Pathname.new(DEFAULT_OUTPUT_DIR)
@@ -206,7 +212,7 @@ module StyleCapsule
206
212
 
207
213
  # Get Rails assets root (app/assets)
208
214
  def rails_assets_root
209
- if defined?(Rails) && Rails.root
215
+ if rails_available?
210
216
  Rails.root.join("app/assets")
211
217
  else
212
218
  Pathname.new("app/assets")
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "digest/sha1"
4
- require "active_support/core_ext/string"
4
+ # ActiveSupport string extensions are conditionally required in lib/style_capsule.rb
5
5
 
6
6
  module StyleCapsule
7
7
  # ERB Helper module for use in Rails views
@@ -148,16 +148,20 @@ module StyleCapsule
148
148
  StyleCapsule::StylesheetRegistry.register(file_path, namespace: namespace, **options)
149
149
  end
150
150
 
151
- # Render StyleCapsule registered stylesheets (similar to javascript_importmap_tags)
151
+ # Render StyleCapsule registered stylesheets
152
152
  #
153
153
  # Usage in ERB:
154
- # <%= stylesheet_registrymap_tags %>
155
- # <%= stylesheet_registrymap_tags(namespace: :admin) %>
154
+ # <%= stylesheet_registry_tags %>
155
+ # <%= stylesheet_registry_tags(namespace: :admin) %>
156
156
  #
157
157
  # @param namespace [Symbol, String, nil] Optional namespace to render (nil/blank renders all)
158
158
  # @return [String] HTML-safe string with stylesheet tags
159
- def stylesheet_registrymap_tags(namespace: nil)
159
+ def stylesheet_registry_tags(namespace: nil)
160
160
  StyleCapsule::StylesheetRegistry.render_head_stylesheets(self, namespace: namespace)
161
161
  end
162
+
163
+ # @deprecated Use {#stylesheet_registry_tags} instead.
164
+ # This method name will be removed in a future version.
165
+ alias_method :stylesheet_registrymap_tags, :stylesheet_registry_tags
162
166
  end
163
167
  end
@@ -36,17 +36,17 @@ module StyleCapsule
36
36
  StyleCapsule::StylesheetRegistry.register(file_path, namespace: namespace, **options)
37
37
  end
38
38
 
39
- # Render StyleCapsule registered stylesheets (similar to javascript_importmap_tags)
39
+ # Render StyleCapsule registered stylesheets
40
40
  #
41
41
  # Usage in Phlex layouts:
42
42
  # head do
43
- # stylesheet_registrymap_tags
44
- # stylesheet_registrymap_tags(namespace: :admin)
43
+ # stylesheet_registry_tags
44
+ # stylesheet_registry_tags(namespace: :admin)
45
45
  # end
46
46
  #
47
47
  # @param namespace [Symbol, String, nil] Optional namespace to render (nil/blank renders all)
48
48
  # @return [void] Renders stylesheet tags via raw
49
- def stylesheet_registrymap_tags(namespace: nil)
49
+ def stylesheet_registry_tags(namespace: nil)
50
50
  output = StyleCapsule::StylesheetRegistry.render_head_stylesheets(view_context, namespace: namespace)
51
51
  # Phlex's raw() requires the object to be marked as safe
52
52
  # Use Phlex's safe() if available, otherwise fall back to html_safe for test doubles
@@ -62,5 +62,9 @@ module StyleCapsule
62
62
  # Always return the output string for testing/compatibility
63
63
  output_string
64
64
  end
65
+
66
+ # @deprecated Use {#stylesheet_registry_tags} instead.
67
+ # This method name will be removed in a future version.
68
+ alias_method :stylesheet_registrymap_tags, :stylesheet_registry_tags
65
69
  end
66
70
  end
@@ -36,7 +36,9 @@ module StyleCapsule
36
36
  ObjectSpace.each_object(Class) do |klass|
37
37
  # Skip singleton classes and classes without names (they can cause errors)
38
38
  # Singleton classes don't have proper names and can't be safely checked
39
- next if klass.name.blank?
39
+ # Use fallback for blank? if ActiveSupport not available
40
+ name = klass.name
41
+ next if name.nil? || (name.respond_to?(:blank?) ? name.blank? : name.to_s.strip.empty?)
40
42
  next if klass.singleton_class?
41
43
 
42
44
  # Use method_defined? instead of respond_to? to avoid triggering delegation
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha1"
4
+ require "cgi"
5
+
6
+ module StyleCapsule
7
+ # Standalone helper module for use without Rails
8
+ #
9
+ # This module provides basic HTML generation and CSS scoping functionality
10
+ # without requiring Rails ActionView helpers. It can be included in any
11
+ # framework's view context or used directly.
12
+ #
13
+ # @example Usage in Sinatra
14
+ # class MyApp < Sinatra::Base
15
+ # helpers StyleCapsule::StandaloneHelper
16
+ # end
17
+ #
18
+ # @example Usage in plain Ruby
19
+ # class MyView
20
+ # include StyleCapsule::StandaloneHelper
21
+ # end
22
+ #
23
+ # @example Usage in ERB (non-Rails)
24
+ # # In your ERB template context
25
+ # include StyleCapsule::StandaloneHelper
26
+ # style_capsule do
27
+ # "<style>.section { color: red; }</style><div class='section'>Content</div>"
28
+ # end
29
+ module StandaloneHelper
30
+ # Maximum HTML content size (10MB) to prevent DoS attacks
31
+ MAX_HTML_SIZE = 10_000_000
32
+
33
+ # Generate capsule ID based on caller location for uniqueness
34
+ def generate_capsule_id(css_content)
35
+ # Use caller location + CSS content for uniqueness
36
+ caller_info = caller_locations(1, 1).first
37
+ capsule_key = "#{caller_info.path}:#{caller_info.lineno}:#{css_content}"
38
+ "a#{Digest::SHA1.hexdigest(capsule_key)}"[0, 8]
39
+ end
40
+
41
+ # Scope CSS content and return scoped CSS
42
+ def scope_css(css_content, capsule_id)
43
+ # Use thread-local cache to avoid reprocessing
44
+ cache_key = "style_capsule_#{capsule_id}"
45
+
46
+ if Thread.current[cache_key]
47
+ return Thread.current[cache_key]
48
+ end
49
+
50
+ scoped_css = CssProcessor.scope_selectors(css_content, capsule_id)
51
+ Thread.current[cache_key] = scoped_css
52
+ scoped_css
53
+ end
54
+
55
+ # Generate HTML tag without Rails helpers
56
+ #
57
+ # @param tag [String, Symbol] HTML tag name
58
+ # @param content [String, nil] Tag content (or use block)
59
+ # @param options [Hash] HTML attributes
60
+ # @param block [Proc] Block for tag content
61
+ # @return [String] HTML string
62
+ def content_tag(tag, content = nil, **options, &block)
63
+ tag_name = tag.to_s
64
+ content = capture(&block) if block_given? && content.nil?
65
+ content ||= ""
66
+
67
+ attrs = options.map do |k, v|
68
+ if v.is_a?(Hash)
69
+ # Handle nested attributes like data: { capsule: "abc" }
70
+ v.map { |nk, nv| %(#{k}-#{nk}="#{escape_html_attr(nv)}") }.join(" ")
71
+ else
72
+ %(#{k}="#{escape_html_attr(v)}")
73
+ end
74
+ end.join(" ")
75
+
76
+ attrs = " #{attrs}" unless attrs.empty?
77
+ "<#{tag_name}#{attrs}>#{content}</#{tag_name}>"
78
+ end
79
+
80
+ # Capture block content (simplified version without Rails)
81
+ #
82
+ # @param block [Proc] Block to capture
83
+ # @return [String] Captured content
84
+ def capture(&block)
85
+ return "" unless block_given?
86
+ block.call.to_s
87
+ end
88
+
89
+ # Mark string as HTML-safe (for compatibility)
90
+ #
91
+ # @param string [String] String to mark as safe
92
+ # @return [String] HTML-safe string
93
+ def html_safe(string)
94
+ # In non-Rails context, just return the string
95
+ # If ActiveSupport is available, use its html_safe
96
+ if string.respond_to?(:html_safe)
97
+ string.html_safe
98
+ else
99
+ string
100
+ end
101
+ end
102
+
103
+ # Raw string (no HTML escaping)
104
+ #
105
+ # @param string [String] String to return as-is
106
+ # @return [String] Raw string
107
+ def raw(string)
108
+ html_safe(string.to_s)
109
+ end
110
+
111
+ # ERB helper: automatically wraps content in scoped div and processes CSS
112
+ #
113
+ # @param css_content [String, nil] CSS content (or extract from block)
114
+ # @param capsule_id [String, nil] Optional capsule ID
115
+ # @param content_block [Proc] Block containing HTML content
116
+ # @return [String] HTML with scoped CSS and wrapped content
117
+ def style_capsule(css_content = nil, capsule_id: nil, &content_block)
118
+ html_content = nil
119
+
120
+ # If CSS content is provided as argument, use it
121
+ # Otherwise, extract from content block
122
+ if css_content.nil? && block_given?
123
+ full_content = capture(&content_block)
124
+
125
+ # Validate HTML content size to prevent DoS attacks
126
+ if full_content.bytesize > MAX_HTML_SIZE
127
+ raise ArgumentError, "HTML content exceeds maximum size of #{MAX_HTML_SIZE} bytes (got #{full_content.bytesize} bytes)"
128
+ end
129
+
130
+ # Extract <style> tags from content
131
+ style_match = full_content.match(/<style[^>]*>(.*?)<\/style>/m)
132
+ if style_match
133
+ css_content = style_match[1]
134
+ html_content = full_content.sub(/<style[^>]*>.*?<\/style>/m, "").strip
135
+ else
136
+ css_content = nil
137
+ html_content = full_content
138
+ end
139
+ elsif css_content && block_given?
140
+ html_content = capture(&content_block)
141
+ elsif css_content && !block_given?
142
+ # CSS provided but no content block - just return scoped CSS
143
+ capsule_id ||= generate_capsule_id(css_content)
144
+ scoped_css = scope_css(css_content, capsule_id)
145
+ return content_tag(:style, raw(scoped_css), type: "text/css")
146
+ else
147
+ return ""
148
+ end
149
+
150
+ # If no CSS, just return content
151
+ return html_safe(html_content) if css_content.nil? || css_content.to_s.strip.empty?
152
+
153
+ # Use provided capsule_id or generate one
154
+ capsule_id ||= generate_capsule_id(css_content)
155
+ scoped_css = scope_css(css_content, capsule_id)
156
+
157
+ # Render style tag and wrapped content
158
+ style_tag = content_tag(:style, raw(scoped_css), type: "text/css")
159
+ wrapped_content = content_tag(:div, raw(html_content), data: {capsule: capsule_id})
160
+
161
+ html_safe(style_tag + wrapped_content)
162
+ end
163
+
164
+ # Register a stylesheet file for head rendering
165
+ #
166
+ # @param file_path [String] Path to stylesheet
167
+ # @param namespace [Symbol, String, nil] Optional namespace
168
+ # @param options [Hash] Options for stylesheet link tag
169
+ # @return [void]
170
+ def register_stylesheet(file_path, namespace: nil, **options)
171
+ StyleCapsule::StylesheetRegistry.register(file_path, namespace: namespace, **options)
172
+ end
173
+
174
+ # Render StyleCapsule registered stylesheets
175
+ #
176
+ # @param namespace [Symbol, String, nil] Optional namespace to render
177
+ # @return [String] HTML-safe string with stylesheet tags
178
+ def stylesheet_registry_tags(namespace: nil)
179
+ StyleCapsule::StylesheetRegistry.render_head_stylesheets(self, namespace: namespace)
180
+ end
181
+
182
+ # @deprecated Use {#stylesheet_registry_tags} instead.
183
+ # This method name will be removed in a future version.
184
+ alias_method :stylesheet_registrymap_tags, :stylesheet_registry_tags
185
+
186
+ private
187
+
188
+ # Escape HTML attribute value
189
+ #
190
+ # @param value [String] Value to escape
191
+ # @return [String] Escaped value
192
+ def escape_html_attr(value)
193
+ CGI.escapeHTML(value.to_s)
194
+ end
195
+ end
196
+ end
@@ -1,8 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/current_attributes"
4
-
5
3
  module StyleCapsule
4
+ # Helper to determine the parent class for StylesheetRegistry
5
+ # ActiveSupport::CurrentAttributes is optional - if ActiveSupport is loaded,
6
+ # it will be available. Otherwise, we fall back to Object.
7
+ #
8
+ # This is evaluated at class definition time, so it can't be stubbed.
9
+ # For testing fallback paths, use the instance methods that check availability.
10
+ def self.stylesheet_registry_parent_class
11
+ defined?(ActiveSupport::CurrentAttributes) ? ActiveSupport::CurrentAttributes : Object
12
+ end
13
+
6
14
  # Hybrid registry for stylesheet files that need to be injected into <head>
7
15
  #
8
16
  # Uses a process-wide manifest for static file paths (like Propshaft) and request-scoped
@@ -52,7 +60,7 @@ module StyleCapsule
52
60
  # body(&block)
53
61
  # end
54
62
  # end
55
- class StylesheetRegistry < ActiveSupport::CurrentAttributes
63
+ class StylesheetRegistry < StyleCapsule.stylesheet_registry_parent_class
56
64
  # Default namespace for backward compatibility
57
65
  DEFAULT_NAMESPACE = :default
58
66
 
@@ -68,10 +76,74 @@ module StyleCapsule
68
76
  @last_cleanup_time = nil # rubocop:disable Style/ClassVars
69
77
 
70
78
  # Request-scoped storage for inline CSS only
71
- attribute :inline_stylesheets
79
+ # Only define attribute if we're inheriting from CurrentAttributes
80
+ if defined?(ActiveSupport::CurrentAttributes) && self < ActiveSupport::CurrentAttributes
81
+ attribute :inline_stylesheets
82
+ end
72
83
 
73
84
  class << self
74
85
  attr_reader :manifest, :inline_cache
86
+
87
+ # Get current time (ActiveSupport::Time.current or Time.now fallback)
88
+ def current_time
89
+ if defined?(Time) && Time.respond_to?(:current)
90
+ Time.current
91
+ else
92
+ # rubocop:disable Rails/TimeZone
93
+ # Time.now is intentional fallback for non-Rails usage when Time.current is unavailable
94
+ Time.now
95
+ # rubocop:enable Rails/TimeZone
96
+ end
97
+ end
98
+
99
+ # Check if we're using ActiveSupport::CurrentAttributes
100
+ # This method can be stubbed in tests to test fallback paths
101
+ def using_current_attributes?
102
+ defined?(ActiveSupport::CurrentAttributes) && self < ActiveSupport::CurrentAttributes
103
+ end
104
+
105
+ # Get inline stylesheets (thread-local fallback if not using CurrentAttributes)
106
+ def inline_stylesheets
107
+ if using_current_attributes?
108
+ # When using CurrentAttributes, access the instance attribute
109
+ # CurrentAttributes automatically provides access to instance attributes
110
+ inst = instance
111
+ inst&.inline_stylesheets || {}
112
+ else
113
+ Thread.current[:style_capsule_inline_stylesheets] ||= {}
114
+ end
115
+ end
116
+
117
+ # Set inline stylesheets (thread-local fallback if not using CurrentAttributes)
118
+ def inline_stylesheets=(value)
119
+ if using_current_attributes?
120
+ # When using CurrentAttributes, set via the instance
121
+ inst = instance
122
+ inst.inline_stylesheets = value if inst
123
+ else
124
+ Thread.current[:style_capsule_inline_stylesheets] = value
125
+ end
126
+ end
127
+
128
+ # Get instance (for CurrentAttributes compatibility)
129
+ def instance
130
+ if using_current_attributes?
131
+ # Call the CurrentAttributes instance method from parent class
132
+ super
133
+ else
134
+ # Return a simple object that responds to inline_stylesheets
135
+ # This is mainly for compatibility with code that might call instance.inline_stylesheets
136
+ registry_class = self
137
+ @_standalone_instance ||= begin
138
+ obj = Object.new
139
+ obj.define_singleton_method(:inline_stylesheets) { registry_class.inline_stylesheets }
140
+ obj.define_singleton_method(:inline_stylesheets=) { |v| registry_class.inline_stylesheets = v }
141
+ obj
142
+ end
143
+ end
144
+ end
145
+
146
+ private
75
147
  end
76
148
 
77
149
  # Normalize namespace (nil/blank becomes DEFAULT_NAMESPACE)
@@ -177,14 +249,14 @@ module StyleCapsule
177
249
  final_css = cached_css || css_content
178
250
 
179
251
  # Store in request-scoped registry
180
- registry = instance.inline_stylesheets || {}
252
+ registry = inline_stylesheets
181
253
  registry[ns] ||= []
182
254
  registry[ns] << {
183
255
  type: :inline,
184
256
  css_content: final_css,
185
257
  capsule_id: capsule_id
186
258
  }
187
- instance.inline_stylesheets = registry
259
+ self.inline_stylesheets = registry
188
260
 
189
261
  # Cache the CSS if strategy is enabled and not already cached
190
262
  if cache_strategy != :none && cache_key && !cached_css && cache_strategy != :file
@@ -216,7 +288,7 @@ module StyleCapsule
216
288
  # Check expiration based on strategy
217
289
  case cache_strategy
218
290
  when :time
219
- return nil if cache_ttl && cached_entry[:expires_at] && Time.current > cached_entry[:expires_at]
291
+ return nil if cache_ttl && cached_entry[:expires_at] && current_time > cached_entry[:expires_at]
220
292
  when :proc
221
293
  return nil unless cache_proc
222
294
  # Proc should validate cache entry
@@ -242,7 +314,13 @@ module StyleCapsule
242
314
 
243
315
  case cache_strategy
244
316
  when :time
245
- expires_at = cache_ttl ? Time.current + cache_ttl : nil
317
+ # Handle ActiveSupport::Duration (e.g., 1.hour) or integer seconds
318
+ ttl_seconds = if cache_ttl.respond_to?(:to_i)
319
+ cache_ttl.to_i
320
+ else
321
+ cache_ttl
322
+ end
323
+ expires_at = ttl_seconds ? current_time + ttl_seconds : nil
246
324
  when :proc
247
325
  if cache_proc
248
326
  _key, _should_cache, proc_expires = cache_proc.call(css_content, capsule_id, namespace)
@@ -252,7 +330,7 @@ module StyleCapsule
252
330
 
253
331
  @inline_cache[cache_key] = {
254
332
  css_content: css_content,
255
- cached_at: Time.current,
333
+ cached_at: current_time,
256
334
  expires_at: expires_at
257
335
  }
258
336
  end
@@ -287,18 +365,18 @@ module StyleCapsule
287
365
  def self.cleanup_expired_cache
288
366
  return 0 if @inline_cache.empty?
289
367
 
290
- current_time = Time.current
368
+ now = current_time
291
369
  expired_keys = []
292
370
 
293
371
  @inline_cache.each do |cache_key, entry|
294
372
  # Remove entries that have an expires_at time and it's in the past
295
- if entry[:expires_at] && current_time > entry[:expires_at]
373
+ if entry[:expires_at] && now > entry[:expires_at]
296
374
  expired_keys << cache_key
297
375
  end
298
376
  end
299
377
 
300
378
  expired_keys.each { |key| @inline_cache.delete(key) }
301
- @last_cleanup_time = current_time
379
+ @last_cleanup_time = now
302
380
 
303
381
  expired_keys.size
304
382
  end
@@ -314,7 +392,7 @@ module StyleCapsule
314
392
  # Cleanup every 5 minutes (300 seconds) to balance memory usage and performance
315
393
  cleanup_interval = 300
316
394
 
317
- if @last_cleanup_time.nil? || (Time.current - @last_cleanup_time) > cleanup_interval
395
+ if @last_cleanup_time.nil? || (current_time - @last_cleanup_time) > cleanup_interval
318
396
  cleanup_expired_cache
319
397
  end
320
398
  end
@@ -332,7 +410,7 @@ module StyleCapsule
332
410
  #
333
411
  # @return [Hash<Symbol, Array<Hash>>] Hash of namespace => array of inline stylesheet registrations
334
412
  def self.request_inline_stylesheets
335
- instance.inline_stylesheets || {}
413
+ inline_stylesheets
336
414
  end
337
415
 
338
416
  # Get all stylesheets (files + inline) for a specific namespace
@@ -361,12 +439,12 @@ module StyleCapsule
361
439
  # @return [void]
362
440
  def self.clear(namespace: nil)
363
441
  if namespace.nil?
364
- instance.inline_stylesheets = {}
442
+ self.inline_stylesheets = {}
365
443
  else
366
444
  ns = normalize_namespace(namespace)
367
- registry = instance.inline_stylesheets || {}
445
+ registry = inline_stylesheets
368
446
  registry.delete(ns)
369
- instance.inline_stylesheets = registry
447
+ self.inline_stylesheets = registry
370
448
  end
371
449
  end
372
450
 
@@ -411,7 +489,7 @@ module StyleCapsule
411
489
  end
412
490
 
413
491
  clear # Clear request-scoped inline CSS only
414
- return "".html_safe if all_stylesheets.empty?
492
+ return safe_string("") if all_stylesheets.empty?
415
493
 
416
494
  all_stylesheets.map do |stylesheet|
417
495
  if stylesheet[:type] == :inline
@@ -419,7 +497,7 @@ module StyleCapsule
419
497
  else
420
498
  render_file_stylesheet(stylesheet, view_context)
421
499
  end
422
- end.join("\n").html_safe
500
+ end.join("\n").then { |s| safe_string(s) }
423
501
 
424
502
  else
425
503
  # Render specific namespace
@@ -427,7 +505,7 @@ module StyleCapsule
427
505
  stylesheets = stylesheets_for(namespace: ns).dup
428
506
  clear(namespace: ns) # Clear request-scoped inline CSS only
429
507
 
430
- return "".html_safe if stylesheets.empty?
508
+ return safe_string("") if stylesheets.empty?
431
509
 
432
510
  stylesheets.map do |stylesheet|
433
511
  if stylesheet[:type] == :inline
@@ -435,7 +513,7 @@ module StyleCapsule
435
513
  else
436
514
  render_file_stylesheet(stylesheet, view_context)
437
515
  end
438
- end.join("\n").html_safe
516
+ end.join("\n").then { |s| safe_string(s) }
439
517
 
440
518
  end
441
519
  end
@@ -473,7 +551,7 @@ module StyleCapsule
473
551
  # Fallback if no view context
474
552
  href = "/assets/#{file_path}.css"
475
553
  tag_options = options.map { |k, v| %(#{k}="#{v}") }.join(" ")
476
- %(<link rel="stylesheet" href="#{href}"#{" #{tag_options}" unless tag_options.empty?}>).html_safe
554
+ safe_string(%(<link rel="stylesheet" href="#{href}"#{" #{tag_options}" unless tag_options.empty?}>))
477
555
  end
478
556
  end
479
557
 
@@ -486,9 +564,21 @@ module StyleCapsule
486
564
  # Construct HTML manually to avoid any HTML escaping issues
487
565
  # CSS content should not be HTML-escaped as it's inside a <style> tag
488
566
  # Using string interpolation with html_safe ensures CSS is not escaped
489
- %(<style type="text/css">#{css_content}</style>).html_safe
567
+ safe_string(%(<style type="text/css">#{css_content}</style>))
568
+ end
569
+
570
+ # Make string HTML-safe (compatible with Rails and non-Rails)
571
+ #
572
+ # @param string [String] String to mark as safe
573
+ # @return [String] HTML-safe string
574
+ def self.safe_string(string)
575
+ if string.respond_to?(:html_safe)
576
+ string.html_safe
577
+ else
578
+ string
579
+ end
490
580
  end
491
581
 
492
- private_class_method :render_file_stylesheet, :render_inline_stylesheet
582
+ private_class_method :render_file_stylesheet, :render_inline_stylesheet, :safe_string
493
583
  end
494
584
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StyleCapsule
4
- VERSION = "1.0.2"
4
+ VERSION = "1.1.0"
5
5
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "digest/sha1"
4
- require "active_support/core_ext/string"
4
+ # ActiveSupport string extensions are conditionally required in lib/style_capsule.rb
5
5
 
6
6
  module StyleCapsule
7
7
  # ViewComponent component concern for encapsulated CSS
@@ -36,18 +36,22 @@ module StyleCapsule
36
36
  StyleCapsule::StylesheetRegistry.register(file_path, namespace: namespace, **options)
37
37
  end
38
38
 
39
- # Render StyleCapsule registered stylesheets (similar to javascript_importmap_tags)
39
+ # Render StyleCapsule registered stylesheets
40
40
  #
41
41
  # Usage in ViewComponent layouts:
42
42
  # def call
43
- # helpers.stylesheet_registrymap_tags
44
- # helpers.stylesheet_registrymap_tags(namespace: :admin)
43
+ # helpers.stylesheet_registry_tags
44
+ # helpers.stylesheet_registry_tags(namespace: :admin)
45
45
  # end
46
46
  #
47
47
  # @param namespace [Symbol, String, nil] Optional namespace to render (nil/blank renders all)
48
48
  # @return [String] HTML-safe string with stylesheet tags
49
- def stylesheet_registrymap_tags(namespace: nil)
49
+ def stylesheet_registry_tags(namespace: nil)
50
50
  StyleCapsule::StylesheetRegistry.render_head_stylesheets(helpers, namespace: namespace)
51
51
  end
52
+
53
+ # @deprecated Use {#stylesheet_registry_tags} instead.
54
+ # This method name will be removed in a future version.
55
+ alias_method :stylesheet_registrymap_tags, :stylesheet_registry_tags
52
56
  end
53
57
  end
data/lib/style_capsule.rb CHANGED
@@ -1,7 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "digest/sha1"
4
- require "active_support/core_ext/string"
4
+ # Conditionally require ActiveSupport string extensions if available
5
+ # For non-Rails usage, these are optional
6
+ # Check first to avoid exception handling overhead in common case (Rails apps)
7
+ unless defined?(ActiveSupport) || String.method_defined?(:html_safe)
8
+ begin
9
+ require "active_support/core_ext/string"
10
+ rescue LoadError
11
+ # ActiveSupport not available - core functionality still works
12
+ end
13
+ end
5
14
 
6
15
  # StyleCapsule provides attribute-based CSS scoping for component encapsulation
7
16
  # in Phlex components, ViewComponent components, and ERB templates.
@@ -55,8 +64,8 @@ require "active_support/core_ext/string"
55
64
  # <div class="section">Content</div>
56
65
  # <% end %>
57
66
  #
58
- # <%= stylesheet_registrymap_tags %>
59
- # <%= stylesheet_registrymap_tags(namespace: :admin) %>
67
+ # <%= stylesheet_registry_tags %>
68
+ # <%= stylesheet_registry_tags(namespace: :admin) %>
60
69
  #
61
70
  # @example Namespace Support
62
71
  # # Register stylesheets with namespaces
@@ -64,10 +73,10 @@ require "active_support/core_ext/string"
64
73
  # StyleCapsule::StylesheetRegistry.register('stylesheets/user/profile', namespace: :user)
65
74
  #
66
75
  # # Render all namespaces (default)
67
- # <%= stylesheet_registrymap_tags %>
76
+ # <%= stylesheet_registry_tags %>
68
77
  #
69
78
  # # Render specific namespace
70
- # <%= stylesheet_registrymap_tags(namespace: :admin) %>
79
+ # <%= stylesheet_registry_tags(namespace: :admin) %>
71
80
  #
72
81
  # @example File-Based Caching (HTTP Caching)
73
82
  # class MyComponent < ApplicationComponent
@@ -85,9 +94,11 @@ module StyleCapsule
85
94
  require_relative "style_capsule/stylesheet_registry"
86
95
  require_relative "style_capsule/component_styles_support"
87
96
  require_relative "style_capsule/component"
97
+ require_relative "style_capsule/standalone_helper"
88
98
  require_relative "style_capsule/helper"
89
99
  require_relative "style_capsule/phlex_helper"
90
100
  require_relative "style_capsule/view_component"
91
101
  require_relative "style_capsule/view_component_helper"
102
+ require_relative "style_capsule/component_builder"
92
103
  require_relative "style_capsule/railtie" if defined?(Rails) && defined?(Rails::Railtie)
93
104
  end
@@ -3,76 +3,9 @@
3
3
  namespace :style_capsule do
4
4
  desc "Build StyleCapsule CSS files from components (similar to Tailwind CSS build)"
5
5
  task build: :environment do
6
- require "style_capsule/css_file_writer"
7
-
8
- # Ensure output directory exists
9
- StyleCapsule::CssFileWriter.ensure_output_directory
10
-
11
- # Collect all component classes that use StyleCapsule
12
- component_classes = []
13
-
14
- # Find Phlex components
15
- if defined?(Phlex::HTML)
16
- ObjectSpace.each_object(Class) do |klass|
17
- if klass < Phlex::HTML && klass.included_modules.include?(StyleCapsule::Component)
18
- component_classes << klass
19
- end
20
- end
21
- end
22
-
23
- # Find ViewComponent components
24
- # Wrap in begin/rescue to handle ViewComponent loading errors (e.g., version compatibility issues)
25
- begin
26
- if defined?(ViewComponent::Base)
27
- ObjectSpace.each_object(Class) do |klass|
28
- if klass < ViewComponent::Base && klass.included_modules.include?(StyleCapsule::ViewComponent)
29
- component_classes << klass
30
- end
31
- rescue
32
- # Skip this class if checking inheritance triggers ViewComponent loading errors
33
- # (e.g., ViewComponent 2.83.0 has a bug with Gem::Version#to_f)
34
- next
35
- end
36
- end
37
- rescue
38
- # ViewComponent may have loading issues (e.g., version compatibility)
39
- # Silently skip ViewComponent components if there's an error
40
- # This allows the rake task to continue with Phlex components
41
- end
42
-
43
- # Generate CSS files for each component
44
- component_classes.each do |component_class|
45
- next unless component_class.inline_cache_strategy == :file
46
- # Check for class method component_styles (required for file caching)
47
- next unless component_class.respond_to?(:component_styles, false)
48
-
49
- begin
50
- # Use class method component_styles for file caching
51
- css_content = component_class.component_styles
52
- next if css_content.nil? || css_content.to_s.strip.empty?
53
-
54
- # Create a temporary instance to get capsule
55
- # Some components might require arguments, so we catch errors
56
- instance = component_class.new
57
- capsule_id = instance.component_capsule
58
- scoped_css = instance.send(:scope_css, css_content)
59
-
60
- # Write CSS file
61
- file_path = StyleCapsule::CssFileWriter.write_css(
62
- css_content: scoped_css,
63
- component_class: component_class,
64
- capsule_id: capsule_id
65
- )
66
-
67
- puts "Generated: #{file_path}" if file_path
68
- rescue ArgumentError, NoMethodError => e
69
- # Component requires arguments or has dependencies - skip it
70
- puts "Skipped #{component_class.name}: #{e.message}"
71
- next
72
- end
73
- end
6
+ require "style_capsule/component_builder"
74
7
 
75
- puts "StyleCapsule CSS files built successfully"
8
+ StyleCapsule::ComponentBuilder.build_all(output_proc: ->(msg) { puts msg })
76
9
  end
77
10
 
78
11
  desc "Clear StyleCapsule generated CSS files"
@@ -88,7 +88,8 @@ module StyleCapsule
88
88
 
89
89
  module ViewComponentHelper
90
90
  def register_stylesheet: (String file_path, ?namespace: Symbol | String | nil, **Hash[untyped, untyped] options) -> void
91
- def stylesheet_registrymap_tags: (?namespace: Symbol | String | nil) -> String
91
+ def stylesheet_registry_tags: (?namespace: Symbol | String | nil) -> String
92
+ def stylesheet_registrymap_tags: (?namespace: Symbol | String | nil) -> String # deprecated
92
93
  end
93
94
 
94
95
  module Helper
@@ -96,12 +97,14 @@ module StyleCapsule
96
97
  def scope_css: (String css_content, String capsule_id) -> String
97
98
  def style_capsule: (?String css_content, ?capsule_id: String | nil) { () -> String } -> String
98
99
  def register_stylesheet: (String file_path, ?namespace: Symbol | String | nil, **Hash[untyped, untyped] options) -> void
99
- def stylesheet_registrymap_tags: (?namespace: Symbol | String | nil) -> String
100
+ def stylesheet_registry_tags: (?namespace: Symbol | String | nil) -> String
101
+ def stylesheet_registrymap_tags: (?namespace: Symbol | String | nil) -> String # deprecated
100
102
  end
101
103
 
102
104
  module PhlexHelper
103
105
  def register_stylesheet: (String file_path, ?namespace: Symbol | String | nil, **Hash[untyped, untyped] options) -> void
104
- def stylesheet_registrymap_tags: (?namespace: Symbol | String | nil) -> void
106
+ def stylesheet_registry_tags: (?namespace: Symbol | String | nil) -> void
107
+ def stylesheet_registrymap_tags: (?namespace: Symbol | String | nil) -> void # deprecated
105
108
  end
106
109
 
107
110
  class Railtie < Rails::Railtie
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: style_capsule
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-11-21 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activesupport
@@ -20,7 +19,7 @@ dependencies:
20
19
  - - "<"
21
20
  - !ruby/object:Gem::Version
22
21
  version: '9.0'
23
- type: :runtime
22
+ type: :development
24
23
  prerelease: false
25
24
  version_requirements: !ruby/object:Gem::Requirement
26
25
  requirements:
@@ -40,7 +39,7 @@ dependencies:
40
39
  - - "<"
41
40
  - !ruby/object:Gem::Version
42
41
  version: '9.0'
43
- type: :runtime
42
+ type: :development
44
43
  prerelease: false
45
44
  version_requirements: !ruby/object:Gem::Requirement
46
45
  requirements:
@@ -249,7 +248,8 @@ dependencies:
249
248
  description: Provides component-scoped CSS encapsulation using [data-capsule] attributes
250
249
  for Phlex components, ViewComponent components, and ERB templates. Styles are automatically
251
250
  scoped to prevent leakage between components. Inspired by component-based CSS approaches
252
- like Angular's view encapsulation and CSS modules.
251
+ like Angular's view encapsulation and CSS modules. Works with Rails and can be used
252
+ standalone in other Ruby frameworks (Sinatra, Hanami, etc.) or plain Ruby scripts.
253
253
  email:
254
254
  - contact@kiskolabs.com
255
255
  executables: []
@@ -262,12 +262,14 @@ files:
262
262
  - SECURITY.md
263
263
  - lib/style_capsule.rb
264
264
  - lib/style_capsule/component.rb
265
+ - lib/style_capsule/component_builder.rb
265
266
  - lib/style_capsule/component_styles_support.rb
266
267
  - lib/style_capsule/css_file_writer.rb
267
268
  - lib/style_capsule/css_processor.rb
268
269
  - lib/style_capsule/helper.rb
269
270
  - lib/style_capsule/phlex_helper.rb
270
271
  - lib/style_capsule/railtie.rb
272
+ - lib/style_capsule/standalone_helper.rb
271
273
  - lib/style_capsule/stylesheet_registry.rb
272
274
  - lib/style_capsule/version.rb
273
275
  - lib/style_capsule/view_component.rb
@@ -283,7 +285,6 @@ metadata:
283
285
  bug_tracker_uri: https://github.com/amkisko/style_capsule.rb/issues
284
286
  documentation_uri: https://github.com/amkisko/style_capsule.rb#readme
285
287
  rubygems_mfa_required: 'true'
286
- post_install_message:
287
288
  rdoc_options: []
288
289
  require_paths:
289
290
  - lib
@@ -298,8 +299,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
298
299
  - !ruby/object:Gem::Version
299
300
  version: '0'
300
301
  requirements: []
301
- rubygems_version: 3.5.9
302
- signing_key:
302
+ rubygems_version: 3.6.9
303
303
  specification_version: 4
304
304
  summary: Attribute-based CSS scoping for Phlex, ViewComponent, and ERB templates.
305
305
  test_files: []