style_capsule 1.3.0 → 1.4.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: 0dc57ce596c8aec7767199f8c57b82db16a8f96b33ac06cba599febd89afa86a
4
- data.tar.gz: b4767a7eed2a864357027386452ea6a2da24577be3376388569c8ceb61bff937
3
+ metadata.gz: 74203df88bc23dc0b1509f0e7adc5978fe327c982026916e990644c92f65a0db
4
+ data.tar.gz: 7f7783ef50b66e14859e1f7207262279f20e5f28eb29d3373a85fef9ae2f3a7c
5
5
  SHA512:
6
- metadata.gz: a666c80dc9ddc70179e9992bc4a1c0c9b58a295e21e0fc64441227529c4b401fdc7c7a703af5e87403ca407d0e482a5b97c73b1cc81d385a021cea2e1186c8d5
7
- data.tar.gz: 0d8523c1659c5467b6e0a1aa6fc1268f999f669772a2ff22b66b386ec5c6ed19d85a9b9dc2b915f744d691ad2d9ccaa2dd7bc33f5c132a3c7d1fe1df073dcd41
6
+ metadata.gz: bed787978d271158ca409ffe8445df1f6203e954000d153beae40368861e0077b566557c3f50456373d04998f85ab60ee86b3cd93ed558d31f3d482247fb0b7e
7
+ data.tar.gz: 22b900ebbf5ce4a219d487f7915b7dcd31ed4552d134af44f056ba79eff8551a4ef62e8070f03887fc4962f2073d06bd3b2d65d35e00f09183d97a730ad24ee2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.4.0 (2025-11-26)
4
+
5
+ - Added unified `style_capsule` class method for configuring all StyleCapsule settings (namespace, cache strategy, CSS scoping, head rendering) in a single call
6
+ - Added automatic namespace fallback in `register_stylesheet` helper methods - when namespace is not specified, uses the component's configured namespace from `style_capsule`
7
+ - Removed deprecated `head_rendering!` method (use `style_capsule` instead)
8
+ - Removed deprecated `stylesheet_registrymap_tags` alias (use `stylesheet_registry_tags` instead)
9
+ - Refactored namespace configuration to use instance variables instead of constants for better inheritance behavior
10
+
3
11
  ## 1.3.0 (2025-11-26)
4
12
 
5
13
  - Added comprehensive instrumentation via `StyleCapsule::Instrumentation` using ActiveSupport::Notifications
@@ -29,7 +37,7 @@
29
37
  - Rails integration remains fully supported via Railtie
30
38
  - Added `StyleCapsule::StandaloneHelper` for non-Rails frameworks
31
39
  - `StylesheetRegistry` now works without `ActiveSupport::CurrentAttributes` using thread-local storage fallback
32
- - Renamed `stylesheet_registrymap_tags` to `stylesheet_registry_tags` (old name kept as deprecated alias)
40
+ - Renamed `stylesheet_registrymap_tags` to `stylesheet_registry_tags` (deprecated alias removed in 1.4.0)
33
41
  - Extracted CSS building logic from Rake tasks into `StyleCapsule::ComponentBuilder`
34
42
  - Fixed XSS vulnerability in `escape_html_attr` by using `CGI.escapeHTML` for proper HTML entity escaping
35
43
  - Optimized ActiveSupport require to avoid exception handling overhead in Rails apps
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # style_capsule
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/style_capsule.svg?v=1.3.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)
3
+ [![Gem Version](https://badge.fury.io/rb/style_capsule.svg?v=1.4.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
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
 
@@ -103,12 +103,12 @@ StyleCapsule supports two CSS scoping strategies:
103
103
 
104
104
  ### Configuration
105
105
 
106
- **Per-component:**
106
+ **Per-component (using `style_capsule` - recommended):**
107
107
 
108
108
  ```ruby
109
109
  class MyComponent < ApplicationComponent
110
110
  include StyleCapsule::Component
111
- css_scoping_strategy :nesting # Use CSS nesting
111
+ style_capsule scoping_strategy: :nesting # Use CSS nesting
112
112
  end
113
113
  ```
114
114
 
@@ -117,7 +117,7 @@ end
117
117
  ```ruby
118
118
  class ApplicationComponent < Phlex::HTML
119
119
  include StyleCapsule::Component
120
- css_scoping_strategy :nesting # Enable for all components
120
+ style_capsule scoping_strategy: :nesting # Enable for all components
121
121
  end
122
122
  ```
123
123
 
@@ -129,12 +129,12 @@ MyComponent.clear_css_cache
129
129
 
130
130
  ## Stylesheet Registry
131
131
 
132
- For better performance, register styles for head rendering instead of rendering `<style>` tags in the body:
132
+ For better performance, register styles for head rendering instead of rendering `<style>` tags in the body. Use the unified `style_capsule` method to configure all settings:
133
133
 
134
134
  ```ruby
135
135
  class MyComponent < ApplicationComponent
136
136
  include StyleCapsule::Component
137
- stylesheet_registry namespace: :admin # Optional namespace
137
+ style_capsule namespace: :admin # Configure namespace and enable head rendering
138
138
 
139
139
  def component_styles
140
140
  <<~CSS
@@ -144,12 +144,17 @@ class MyComponent < ApplicationComponent
144
144
  end
145
145
  ```
146
146
 
147
- With cache strategy:
147
+ With cache strategy and CSS scoping:
148
148
 
149
149
  ```ruby
150
150
  class MyComponent < ApplicationComponent
151
151
  include StyleCapsule::Component
152
- stylesheet_registry namespace: :admin, cache_strategy: :time, cache_ttl: 1.hour
152
+ style_capsule(
153
+ namespace: :admin,
154
+ cache_strategy: :time,
155
+ cache_ttl: 1.hour,
156
+ scoping_strategy: :nesting
157
+ )
153
158
 
154
159
  def component_styles
155
160
  <<~CSS
@@ -159,11 +164,10 @@ class MyComponent < ApplicationComponent
159
164
  end
160
165
  ```
161
166
 
162
- Then in your layout:
167
+ Then in your layout (render only the namespace you need):
163
168
 
164
169
  ```erb
165
170
  <head>
166
- <%= stylesheet_registry_tags %>
167
171
  <%= stylesheet_registry_tags(namespace: :admin) %>
168
172
  </head>
169
173
  ```
@@ -172,42 +176,58 @@ Or in Phlex (requires including `StyleCapsule::PhlexHelper`):
172
176
 
173
177
  ```ruby
174
178
  head do
175
- stylesheet_registry_tags
179
+ stylesheet_registry_tags(namespace: :admin)
176
180
  end
177
181
  ```
178
182
 
183
+ **Namespace Isolation:** Using namespaces prevents stylesheet leakage between different application contexts. For example, login pages can use `namespace: :login`, ActiveAdmin can use `namespace: :active_admin`, and user components can use `namespace: :user`. Each namespace is rendered separately, improving caching efficiency and preventing style conflicts.
184
+
179
185
  ### Registering Stylesheet Files
180
186
 
181
- You can also register external stylesheet files (not inline CSS) for head rendering:
187
+ You can also register external stylesheet files (not inline CSS) for head rendering. When a component has a configured namespace via `style_capsule`, you don't need to specify it every time:
182
188
 
183
189
  **In ERB:**
184
190
 
185
191
  ```erb
186
- <% register_stylesheet("stylesheets/user/order_select_component", "data-turbo-track": "reload") %>
192
+ <% register_stylesheet("stylesheets/user/my_component", "data-turbo-track": "reload") %>
187
193
  <% register_stylesheet("stylesheets/admin/dashboard", namespace: :admin) %>
188
194
  ```
189
195
 
190
196
  **In Phlex (requires including `StyleCapsule::PhlexHelper`):**
191
197
 
192
198
  ```ruby
193
- def view_template
194
- register_stylesheet("stylesheets/user/order_select_component", "data-turbo-track": "reload")
195
- register_stylesheet("stylesheets/admin/dashboard", namespace: :admin)
196
- div { "Content" }
199
+ class UserComponent < ApplicationComponent
200
+ include StyleCapsule::Component
201
+ include StyleCapsule::PhlexHelper
202
+ style_capsule namespace: :user # Set default namespace
203
+
204
+ def view_template
205
+ # Namespace automatically uses :user from style_capsule
206
+ register_stylesheet("stylesheets/user/my_component", "data-turbo-track": "reload")
207
+ # Can still override namespace if needed
208
+ register_stylesheet("stylesheets/shared/common", namespace: :shared)
209
+ div { "Content" }
210
+ end
197
211
  end
198
212
  ```
199
213
 
200
214
  **In ViewComponent (requires including `StyleCapsule::ViewComponentHelper`):**
201
215
 
202
216
  ```ruby
203
- def call
204
- register_stylesheet("stylesheets/user/order_select_component", "data-turbo-track": "reload")
205
- register_stylesheet("stylesheets/admin/dashboard", namespace: :admin)
206
- content_tag(:div, "Content")
217
+ class UserComponent < ApplicationComponent
218
+ include StyleCapsule::ViewComponent
219
+ include StyleCapsule::ViewComponentHelper
220
+ style_capsule namespace: :user # Set default namespace
221
+
222
+ def call
223
+ # Namespace automatically uses :user from style_capsule
224
+ register_stylesheet("stylesheets/user/my_component", "data-turbo-track": "reload")
225
+ content_tag(:div, "Content")
226
+ end
207
227
  end
208
228
  ```
209
229
 
210
- Registered files are rendered via `stylesheet_registry_tags` in your layout, just like inline CSS.
230
+ Registered files are rendered via `stylesheet_registry_tags` in your layout, just like inline CSS. The namespace is automatically used from the component's `style_capsule` configuration when not explicitly specified.
211
231
 
212
232
  ## Caching Strategies
213
233
 
@@ -216,22 +236,22 @@ Registered files are rendered via `stylesheet_registry_tags` in your layout, jus
216
236
  ```ruby
217
237
  class MyComponent < ApplicationComponent
218
238
  include StyleCapsule::Component
219
- stylesheet_registry # No cache strategy set (default: :none)
239
+ style_capsule # No cache strategy set (default: :none)
220
240
  end
221
241
  ```
222
242
 
223
243
  ### Time-Based Caching
224
244
 
225
245
  ```ruby
226
- stylesheet_registry cache_strategy: :time, cache_ttl: 1.hour # Using ActiveSupport::Duration
246
+ style_capsule cache_strategy: :time, cache_ttl: 1.hour # Using ActiveSupport::Duration
227
247
  # Or using integer seconds:
228
- stylesheet_registry cache_strategy: :time, cache_ttl: 3600 # Cache for 1 hour
248
+ style_capsule cache_strategy: :time, cache_ttl: 3600 # Cache for 1 hour
229
249
  ```
230
250
 
231
251
  ### Custom Proc Caching
232
252
 
233
253
  ```ruby
234
- stylesheet_registry cache_strategy: ->(css, capsule_id, namespace) {
254
+ style_capsule cache_strategy: ->(css, capsule_id, namespace) {
235
255
  cache_key = "css_#{capsule_id}_#{namespace}"
236
256
  should_cache = css.length > 100
237
257
  expires_at = Time.now + 1800
@@ -248,7 +268,7 @@ Writes CSS to files for HTTP caching. **Requires class method `def self.componen
248
268
  ```ruby
249
269
  class MyComponent < ApplicationComponent
250
270
  include StyleCapsule::Component
251
- stylesheet_registry cache_strategy: :file
271
+ style_capsule cache_strategy: :file
252
272
 
253
273
  # Must use class method for file caching
254
274
  def self.component_styles
@@ -227,23 +227,104 @@ module StyleCapsule
227
227
  end
228
228
  end
229
229
 
230
- # Deprecated: Use stylesheet_registry instead
231
- # @deprecated Use {#stylesheet_registry} instead
232
- def head_rendering!
233
- stylesheet_registry
234
- end
235
-
236
- # Check if component uses head rendering
230
+ # Check if component uses head rendering (checks instance variable, then parent class, defaults to false)
231
+ #
232
+ # @return [Boolean] Whether head rendering is enabled (default: false)
237
233
  def head_rendering?
238
- return false unless defined?(@head_rendering)
239
- @head_rendering
234
+ if defined?(@head_rendering)
235
+ @head_rendering
236
+ elsif superclass.respond_to?(:head_rendering?, true)
237
+ superclass.head_rendering?
238
+ else
239
+ false
240
+ end
240
241
  end
241
242
 
242
243
  public :head_rendering?
243
244
 
244
- # Get the namespace for stylesheet registry
245
+ # Get the namespace for stylesheet registry (checks instance variable, then parent class, defaults to nil)
246
+ #
247
+ # @return [Symbol, String, nil] The namespace identifier (default: nil)
245
248
  def stylesheet_namespace
246
- @stylesheet_namespace if defined?(@stylesheet_namespace)
249
+ if defined?(@stylesheet_namespace) && @stylesheet_namespace
250
+ @stylesheet_namespace
251
+ elsif superclass.respond_to?(:stylesheet_namespace, true)
252
+ superclass.stylesheet_namespace
253
+ end
254
+ end
255
+
256
+ # Configure StyleCapsule settings
257
+ #
258
+ # All settings support class inheritance - child classes inherit settings from parent classes
259
+ # and can override them by calling style_capsule again with different values.
260
+ #
261
+ # @param namespace [Symbol, String, nil] Default namespace for stylesheets
262
+ # @param cache_strategy [Symbol, String, Proc, nil] Cache strategy: :none (default), :time, :proc, :file
263
+ # @param cache_ttl [Integer, ActiveSupport::Duration, nil] Time-to-live in seconds (for :time strategy)
264
+ # @param cache_proc [Proc, nil] Custom cache proc (for :proc strategy)
265
+ # @param scoping_strategy [Symbol, nil] CSS scoping strategy: :selector_patching (default) or :nesting
266
+ # @param head_rendering [Boolean, nil] Enable head rendering (default: true if any option is set, false otherwise)
267
+ # @return [void]
268
+ # @example Basic usage with namespace
269
+ # class AdminComponent < ApplicationComponent
270
+ # include StyleCapsule::Component
271
+ # style_capsule namespace: :admin
272
+ #
273
+ # def view_template
274
+ # register_stylesheet("stylesheets/admin/dashboard") # Uses :admin namespace automatically
275
+ # div { "Content" }
276
+ # end
277
+ # end
278
+ # @example With all options
279
+ # class MyComponent < ApplicationComponent
280
+ # include StyleCapsule::Component
281
+ # style_capsule(
282
+ # namespace: :user,
283
+ # cache_strategy: :time,
284
+ # cache_ttl: 1.hour,
285
+ # scoping_strategy: :nesting
286
+ # )
287
+ # end
288
+ # @example Inheritance - child class inherits parent settings
289
+ # class BaseComponent < ApplicationComponent
290
+ # include StyleCapsule::Component
291
+ # style_capsule namespace: :admin, cache_strategy: :time, cache_ttl: 1.hour
292
+ # end
293
+ #
294
+ # class ChildComponent < BaseComponent
295
+ # # Inherits namespace: :admin, cache_strategy: :time, cache_ttl: 1.hour
296
+ # # Can override specific settings:
297
+ # style_capsule namespace: :user # Overrides namespace, keeps cache settings
298
+ # end
299
+ def style_capsule(namespace: nil, cache_strategy: nil, cache_ttl: nil, cache_proc: nil, scoping_strategy: nil, head_rendering: nil)
300
+ # Set namespace (stored in instance variable, but getter checks parent class for inheritance)
301
+ if namespace
302
+ @stylesheet_namespace = namespace
303
+ end
304
+
305
+ # Configure cache strategy if provided
306
+ if cache_strategy || cache_ttl || cache_proc
307
+ normalized_strategy, normalized_proc = normalize_cache_strategy(cache_strategy || :none, cache_proc)
308
+ @inline_cache_strategy = normalized_strategy
309
+ # Explicitly set cache_ttl (even if nil) to override parent's value when cache settings are changed
310
+ @inline_cache_ttl = cache_ttl
311
+ @inline_cache_proc = normalized_proc
312
+ end
313
+
314
+ # Configure CSS scoping strategy if provided
315
+ if scoping_strategy
316
+ unless [:selector_patching, :nesting].include?(scoping_strategy)
317
+ raise ArgumentError, "scoping_strategy must be :selector_patching or :nesting (got: #{scoping_strategy.inspect})"
318
+ end
319
+ @css_scoping_strategy = scoping_strategy
320
+ end
321
+
322
+ # Enable head rendering if explicitly set or if any option is provided (except scoping_strategy)
323
+ if head_rendering.nil?
324
+ @head_rendering = true if namespace || cache_strategy || cache_ttl || cache_proc
325
+ else
326
+ @head_rendering = head_rendering
327
+ end
247
328
  end
248
329
 
249
330
  # Get the custom scope ID if set (alias for capsule_id getter)
@@ -251,22 +332,55 @@ module StyleCapsule
251
332
  @custom_capsule_id if defined?(@custom_capsule_id)
252
333
  end
253
334
 
254
- # Get inline cache strategy
335
+ # Get inline cache strategy (checks instance variable, then parent class, defaults to nil)
336
+ #
337
+ # @return [Symbol, nil] The cache strategy (default: nil)
255
338
  def inline_cache_strategy
256
- @inline_cache_strategy if defined?(@inline_cache_strategy)
339
+ if defined?(@inline_cache_strategy) && @inline_cache_strategy
340
+ @inline_cache_strategy
341
+ elsif superclass.respond_to?(:inline_cache_strategy, true)
342
+ superclass.inline_cache_strategy
343
+ end
257
344
  end
258
345
 
259
- # Get inline cache TTL
346
+ # Get inline cache TTL (checks instance variable, then parent class, defaults to nil)
347
+ #
348
+ # @return [Integer, ActiveSupport::Duration, nil] The cache TTL (default: nil)
260
349
  def inline_cache_ttl
261
- @inline_cache_ttl if defined?(@inline_cache_ttl)
350
+ if defined?(@inline_cache_ttl)
351
+ @inline_cache_ttl
352
+ elsif superclass.respond_to?(:inline_cache_ttl, true)
353
+ superclass.inline_cache_ttl
354
+ end
262
355
  end
263
356
 
264
- # Get inline cache proc
357
+ # Get inline cache proc (checks instance variable, then parent class, defaults to nil)
358
+ #
359
+ # @return [Proc, nil] The cache proc (default: nil)
265
360
  def inline_cache_proc
266
- @inline_cache_proc if defined?(@inline_cache_proc)
361
+ if defined?(@inline_cache_proc)
362
+ @inline_cache_proc
363
+ elsif superclass.respond_to?(:inline_cache_proc, true)
364
+ superclass.inline_cache_proc
365
+ end
267
366
  end
268
367
 
269
- public :head_rendering?, :stylesheet_namespace, :custom_capsule_id, :inline_cache_strategy, :inline_cache_ttl, :inline_cache_proc
368
+ public :head_rendering?, :stylesheet_namespace, :style_capsule, :custom_capsule_id, :inline_cache_strategy, :inline_cache_ttl, :inline_cache_proc
369
+
370
+ # Get CSS scoping strategy (checks instance variable, then parent class, defaults to :selector_patching)
371
+ #
372
+ # @return [Symbol] The current scoping strategy (default: :selector_patching)
373
+ def css_scoping_strategy
374
+ if defined?(@css_scoping_strategy) && @css_scoping_strategy
375
+ @css_scoping_strategy
376
+ elsif superclass.respond_to?(:css_scoping_strategy, true)
377
+ superclass.css_scoping_strategy
378
+ else
379
+ :selector_patching
380
+ end
381
+ end
382
+
383
+ public :css_scoping_strategy
270
384
 
271
385
  # Set or get options for stylesheet_link_tag when using file-based caching
272
386
  #
@@ -289,58 +403,6 @@ module StyleCapsule
289
403
  end
290
404
 
291
405
  public :stylesheet_link_options
292
-
293
- # Set or get CSS scoping strategy
294
- #
295
- # @param strategy [Symbol, nil] Scoping strategy: :selector_patching (default) or :nesting (omit to get current value)
296
- # - :selector_patching: Adds [data-capsule="..."] prefix to each selector (better browser support)
297
- # - :nesting: Wraps entire CSS in [data-capsule="..."] { ... } (more performant, requires CSS nesting support)
298
- # @return [Symbol] The current scoping strategy (default: :selector_patching)
299
- # @example Using CSS nesting (requires Chrome 112+, Firefox 117+, Safari 16.5+)
300
- # class MyComponent < ApplicationComponent
301
- # include StyleCapsule::Component
302
- # css_scoping_strategy :nesting # More performant, no CSS parsing needed
303
- #
304
- # def component_styles
305
- # <<~CSS
306
- # .section { color: red; }
307
- # .heading:hover { opacity: 0.8; }
308
- # CSS
309
- # end
310
- # end
311
- # # Output: [data-capsule="abc123"] { .section { color: red; } .heading:hover { opacity: 0.8; } }
312
- # @example Using selector patching (default, better browser support)
313
- # class MyComponent < ApplicationComponent
314
- # include StyleCapsule::Component
315
- # css_scoping_strategy :selector_patching # Default
316
- #
317
- # def component_styles
318
- # <<~CSS
319
- # .section { color: red; }
320
- # CSS
321
- # end
322
- # end
323
- # # Output: [data-capsule="abc123"] .section { color: red; }
324
- def css_scoping_strategy(strategy = nil)
325
- if strategy.nil?
326
- # Check if this class has a strategy set
327
- if defined?(@css_scoping_strategy) && @css_scoping_strategy
328
- @css_scoping_strategy
329
- # Otherwise, check parent class (for inheritance)
330
- elsif superclass.respond_to?(:css_scoping_strategy, true)
331
- superclass.css_scoping_strategy
332
- else
333
- :selector_patching
334
- end
335
- else
336
- unless [:selector_patching, :nesting].include?(strategy)
337
- raise ArgumentError, "css_scoping_strategy must be :selector_patching or :nesting (got: #{strategy.inspect})"
338
- end
339
- @css_scoping_strategy = strategy
340
- end
341
- end
342
-
343
- public :css_scoping_strategy
344
406
  end
345
407
 
346
408
  # Module that wraps view_template to add scoped wrapper
@@ -159,9 +159,5 @@ module StyleCapsule
159
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
166
162
  end
167
163
  end
@@ -10,13 +10,18 @@ module StyleCapsule
10
10
  #
11
11
  # Usage in Phlex layouts:
12
12
  # head do
13
- # stylesheet_registrymap_tags
13
+ # stylesheet_registry_tags
14
14
  # end
15
15
  #
16
16
  # Usage in Phlex components:
17
- # def view_template
18
- # register_stylesheet("stylesheets/user/my_component")
19
- # div { "Content" }
17
+ # class MyComponent < ApplicationComponent
18
+ # include StyleCapsule::PhlexHelper
19
+ # styles_namespace :user # Set default namespace
20
+ #
21
+ # def view_template
22
+ # register_stylesheet("stylesheets/user/my_component") # Uses :user namespace automatically
23
+ # div { "Content" }
24
+ # end
20
25
  # end
21
26
  module PhlexHelper
22
27
  # Register a stylesheet file for head rendering
@@ -28,11 +33,18 @@ module StyleCapsule
28
33
  # div { "Content" }
29
34
  # end
30
35
  #
36
+ # If the component has a default namespace set via styles_namespace or stylesheet_registry,
37
+ # it will be used automatically when namespace is not explicitly provided.
38
+ #
31
39
  # @param file_path [String] Path to stylesheet (relative to app/assets/stylesheets)
32
- # @param namespace [Symbol, String, nil] Optional namespace for separation (nil/blank uses default)
40
+ # @param namespace [Symbol, String, nil] Optional namespace for separation (nil/blank uses component's default or global default)
33
41
  # @param options [Hash] Options for stylesheet_link_tag
34
42
  # @return [void]
35
43
  def register_stylesheet(file_path, namespace: nil, **options)
44
+ # Use component's default namespace if not explicitly provided
45
+ if namespace.nil? && respond_to?(:class) && self.class.respond_to?(:stylesheet_namespace)
46
+ namespace = self.class.stylesheet_namespace
47
+ end
36
48
  StyleCapsule::StylesheetRegistry.register(file_path, namespace: namespace, **options)
37
49
  end
38
50
 
@@ -62,9 +74,5 @@ module StyleCapsule
62
74
  # Always return the output string for testing/compatibility
63
75
  output_string
64
76
  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
69
77
  end
70
78
  end
@@ -179,10 +179,6 @@ module StyleCapsule
179
179
  StyleCapsule::StylesheetRegistry.render_head_stylesheets(self, namespace: namespace)
180
180
  end
181
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
182
  private
187
183
 
188
184
  # Escape HTML attribute value
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StyleCapsule
4
- VERSION = "1.3.0"
4
+ VERSION = "1.4.0"
5
5
  end
@@ -227,23 +227,104 @@ module StyleCapsule
227
227
  end
228
228
  end
229
229
 
230
- # Deprecated: Use stylesheet_registry instead
231
- # @deprecated Use {#stylesheet_registry} instead
232
- def head_rendering!
233
- stylesheet_registry
234
- end
235
-
236
- # Check if component uses head rendering
230
+ # Check if component uses head rendering (checks instance variable, then parent class, defaults to false)
231
+ #
232
+ # @return [Boolean] Whether head rendering is enabled (default: false)
237
233
  def head_rendering?
238
- return false unless defined?(@head_rendering)
239
- @head_rendering
234
+ if defined?(@head_rendering)
235
+ @head_rendering
236
+ elsif superclass.respond_to?(:head_rendering?, true)
237
+ superclass.head_rendering?
238
+ else
239
+ false
240
+ end
240
241
  end
241
242
 
242
243
  public :head_rendering?
243
244
 
244
- # Get the namespace for stylesheet registry
245
+ # Get the namespace for stylesheet registry (checks instance variable, then parent class, defaults to nil)
246
+ #
247
+ # @return [Symbol, String, nil] The namespace identifier (default: nil)
245
248
  def stylesheet_namespace
246
- @stylesheet_namespace if defined?(@stylesheet_namespace)
249
+ if defined?(@stylesheet_namespace) && @stylesheet_namespace
250
+ @stylesheet_namespace
251
+ elsif superclass.respond_to?(:stylesheet_namespace, true)
252
+ superclass.stylesheet_namespace
253
+ end
254
+ end
255
+
256
+ # Configure StyleCapsule settings
257
+ #
258
+ # All settings support class inheritance - child classes inherit settings from parent classes
259
+ # and can override them by calling style_capsule again with different values.
260
+ #
261
+ # @param namespace [Symbol, String, nil] Default namespace for stylesheets
262
+ # @param cache_strategy [Symbol, String, Proc, nil] Cache strategy: :none (default), :time, :proc, :file
263
+ # @param cache_ttl [Integer, ActiveSupport::Duration, nil] Time-to-live in seconds (for :time strategy)
264
+ # @param cache_proc [Proc, nil] Custom cache proc (for :proc strategy)
265
+ # @param scoping_strategy [Symbol, nil] CSS scoping strategy: :selector_patching (default) or :nesting
266
+ # @param head_rendering [Boolean, nil] Enable head rendering (default: true if any option is set, false otherwise)
267
+ # @return [void]
268
+ # @example Basic usage with namespace
269
+ # class AdminComponent < ApplicationComponent
270
+ # include StyleCapsule::ViewComponent
271
+ # style_capsule namespace: :admin
272
+ #
273
+ # def call
274
+ # register_stylesheet("stylesheets/admin/dashboard") # Uses :admin namespace automatically
275
+ # content_tag(:div, "Content")
276
+ # end
277
+ # end
278
+ # @example With all options
279
+ # class MyComponent < ApplicationComponent
280
+ # include StyleCapsule::ViewComponent
281
+ # style_capsule(
282
+ # namespace: :user,
283
+ # cache_strategy: :time,
284
+ # cache_ttl: 1.hour,
285
+ # scoping_strategy: :nesting
286
+ # )
287
+ # end
288
+ # @example Inheritance - child class inherits parent settings
289
+ # class BaseComponent < ApplicationComponent
290
+ # include StyleCapsule::ViewComponent
291
+ # style_capsule namespace: :admin, cache_strategy: :time, cache_ttl: 1.hour
292
+ # end
293
+ #
294
+ # class ChildComponent < BaseComponent
295
+ # # Inherits namespace: :admin, cache_strategy: :time, cache_ttl: 1.hour
296
+ # # Can override specific settings:
297
+ # style_capsule namespace: :user # Overrides namespace, keeps cache settings
298
+ # end
299
+ def style_capsule(namespace: nil, cache_strategy: nil, cache_ttl: nil, cache_proc: nil, scoping_strategy: nil, head_rendering: nil)
300
+ # Set namespace (stored in instance variable, but getter checks parent class for inheritance)
301
+ if namespace
302
+ @stylesheet_namespace = namespace
303
+ end
304
+
305
+ # Configure cache strategy if provided
306
+ if cache_strategy || cache_ttl || cache_proc
307
+ normalized_strategy, normalized_proc = normalize_cache_strategy(cache_strategy || :none, cache_proc)
308
+ @inline_cache_strategy = normalized_strategy
309
+ # Explicitly set cache_ttl (even if nil) to override parent's value when cache settings are changed
310
+ @inline_cache_ttl = cache_ttl
311
+ @inline_cache_proc = normalized_proc
312
+ end
313
+
314
+ # Configure CSS scoping strategy if provided
315
+ if scoping_strategy
316
+ unless [:selector_patching, :nesting].include?(scoping_strategy)
317
+ raise ArgumentError, "scoping_strategy must be :selector_patching or :nesting (got: #{scoping_strategy.inspect})"
318
+ end
319
+ @css_scoping_strategy = scoping_strategy
320
+ end
321
+
322
+ # Enable head rendering if explicitly set or if any option is provided (except scoping_strategy)
323
+ if head_rendering.nil?
324
+ @head_rendering = true if namespace || cache_strategy || cache_ttl || cache_proc
325
+ else
326
+ @head_rendering = head_rendering
327
+ end
247
328
  end
248
329
 
249
330
  # Get the custom scope ID if set (alias for capsule_id getter)
@@ -251,22 +332,55 @@ module StyleCapsule
251
332
  @custom_capsule_id if defined?(@custom_capsule_id)
252
333
  end
253
334
 
254
- # Get inline cache strategy
335
+ # Get inline cache strategy (checks instance variable, then parent class, defaults to nil)
336
+ #
337
+ # @return [Symbol, nil] The cache strategy (default: nil)
255
338
  def inline_cache_strategy
256
- @inline_cache_strategy if defined?(@inline_cache_strategy)
339
+ if defined?(@inline_cache_strategy) && @inline_cache_strategy
340
+ @inline_cache_strategy
341
+ elsif superclass.respond_to?(:inline_cache_strategy, true)
342
+ superclass.inline_cache_strategy
343
+ end
257
344
  end
258
345
 
259
- # Get inline cache TTL
346
+ # Get inline cache TTL (checks instance variable, then parent class, defaults to nil)
347
+ #
348
+ # @return [Integer, ActiveSupport::Duration, nil] The cache TTL (default: nil)
260
349
  def inline_cache_ttl
261
- @inline_cache_ttl if defined?(@inline_cache_ttl)
350
+ if defined?(@inline_cache_ttl)
351
+ @inline_cache_ttl
352
+ elsif superclass.respond_to?(:inline_cache_ttl, true)
353
+ superclass.inline_cache_ttl
354
+ end
262
355
  end
263
356
 
264
- # Get inline cache proc
357
+ # Get inline cache proc (checks instance variable, then parent class, defaults to nil)
358
+ #
359
+ # @return [Proc, nil] The cache proc (default: nil)
265
360
  def inline_cache_proc
266
- @inline_cache_proc if defined?(@inline_cache_proc)
361
+ if defined?(@inline_cache_proc)
362
+ @inline_cache_proc
363
+ elsif superclass.respond_to?(:inline_cache_proc, true)
364
+ superclass.inline_cache_proc
365
+ end
267
366
  end
268
367
 
269
- public :head_rendering?, :stylesheet_namespace, :custom_capsule_id, :inline_cache_strategy, :inline_cache_ttl, :inline_cache_proc
368
+ public :head_rendering?, :stylesheet_namespace, :style_capsule, :custom_capsule_id, :inline_cache_strategy, :inline_cache_ttl, :inline_cache_proc
369
+
370
+ # Get CSS scoping strategy (checks instance variable, then parent class, defaults to :selector_patching)
371
+ #
372
+ # @return [Symbol] The current scoping strategy (default: :selector_patching)
373
+ def css_scoping_strategy
374
+ if defined?(@css_scoping_strategy) && @css_scoping_strategy
375
+ @css_scoping_strategy
376
+ elsif superclass.respond_to?(:css_scoping_strategy, true)
377
+ superclass.css_scoping_strategy
378
+ else
379
+ :selector_patching
380
+ end
381
+ end
382
+
383
+ public :css_scoping_strategy
270
384
 
271
385
  # Set or get options for stylesheet_link_tag when using file-based caching
272
386
  #
@@ -289,58 +403,6 @@ module StyleCapsule
289
403
  end
290
404
 
291
405
  public :stylesheet_link_options
292
-
293
- # Set or get CSS scoping strategy
294
- #
295
- # @param strategy [Symbol, nil] Scoping strategy: :selector_patching (default) or :nesting (omit to get current value)
296
- # - :selector_patching: Adds [data-capsule="..."] prefix to each selector (better browser support)
297
- # - :nesting: Wraps entire CSS in [data-capsule="..."] { ... } (more performant, requires CSS nesting support)
298
- # @return [Symbol] The current scoping strategy (default: :selector_patching)
299
- # @example Using CSS nesting (requires Chrome 112+, Firefox 117+, Safari 16.5+)
300
- # class MyComponent < ApplicationComponent
301
- # include StyleCapsule::ViewComponent
302
- # css_scoping_strategy :nesting # More performant, no CSS parsing needed
303
- #
304
- # def component_styles
305
- # <<~CSS
306
- # .section { color: red; }
307
- # .heading:hover { opacity: 0.8; }
308
- # CSS
309
- # end
310
- # end
311
- # # Output: [data-capsule="abc123"] { .section { color: red; } .heading:hover { opacity: 0.8; } }
312
- # @example Using selector patching (default, better browser support)
313
- # class MyComponent < ApplicationComponent
314
- # include StyleCapsule::ViewComponent
315
- # css_scoping_strategy :selector_patching # Default
316
- #
317
- # def component_styles
318
- # <<~CSS
319
- # .section { color: red; }
320
- # CSS
321
- # end
322
- # end
323
- # # Output: [data-capsule="abc123"] .section { color: red; }
324
- def css_scoping_strategy(strategy = nil)
325
- if strategy.nil?
326
- # Check if this class has a strategy set
327
- if defined?(@css_scoping_strategy) && @css_scoping_strategy
328
- @css_scoping_strategy
329
- # Otherwise, check parent class (for inheritance)
330
- elsif superclass.respond_to?(:css_scoping_strategy, true)
331
- superclass.css_scoping_strategy
332
- else
333
- :selector_patching
334
- end
335
- else
336
- unless [:selector_patching, :nesting].include?(strategy)
337
- raise ArgumentError, "css_scoping_strategy must be :selector_patching or :nesting (got: #{strategy.inspect})"
338
- end
339
- @css_scoping_strategy = strategy
340
- end
341
- end
342
-
343
- public :css_scoping_strategy
344
406
  end
345
407
 
346
408
  # Module that wraps call to add scoped wrapper
@@ -10,13 +10,17 @@ module StyleCapsule
10
10
  #
11
11
  # Usage in ViewComponent layouts:
12
12
  # def call
13
- # helpers.stylesheet_registrymap_tags
13
+ # helpers.stylesheet_registry_tags
14
14
  # end
15
15
  #
16
16
  # Usage in ViewComponent components:
17
- # def call
18
- # register_stylesheet("stylesheets/user/my_component")
19
- # content_tag(:div, "Content")
17
+ # class MyComponent < ApplicationComponent
18
+ # styles_namespace :user # Set default namespace
19
+ #
20
+ # def call
21
+ # register_stylesheet("stylesheets/user/my_component") # Uses :user namespace automatically
22
+ # content_tag(:div, "Content")
23
+ # end
20
24
  # end
21
25
  module ViewComponentHelper
22
26
  # Register a stylesheet file for head rendering
@@ -28,11 +32,18 @@ module StyleCapsule
28
32
  # content_tag(:div, "Content")
29
33
  # end
30
34
  #
35
+ # If the component has a default namespace set via styles_namespace or stylesheet_registry,
36
+ # it will be used automatically when namespace is not explicitly provided.
37
+ #
31
38
  # @param file_path [String] Path to stylesheet (relative to app/assets/stylesheets)
32
- # @param namespace [Symbol, String, nil] Optional namespace for separation (nil/blank uses default)
39
+ # @param namespace [Symbol, String, nil] Optional namespace for separation (nil/blank uses component's default or global default)
33
40
  # @param options [Hash] Options for stylesheet_link_tag
34
41
  # @return [void]
35
42
  def register_stylesheet(file_path, namespace: nil, **options)
43
+ # Use component's default namespace if not explicitly provided
44
+ if namespace.nil? && respond_to?(:class) && self.class.respond_to?(:stylesheet_namespace)
45
+ namespace = self.class.stylesheet_namespace
46
+ end
36
47
  StyleCapsule::StylesheetRegistry.register(file_path, namespace: namespace, **options)
37
48
  end
38
49
 
@@ -49,9 +60,5 @@ module StyleCapsule
49
60
  def stylesheet_registry_tags(namespace: nil)
50
61
  StyleCapsule::StylesheetRegistry.render_head_stylesheets(helpers, namespace: namespace)
51
62
  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
56
63
  end
57
64
  end
data/lib/style_capsule.rb CHANGED
@@ -18,6 +18,7 @@ end
18
18
  # @example Phlex Component Usage
19
19
  # class MyComponent < ApplicationComponent
20
20
  # include StyleCapsule::Component
21
+ # style_capsule namespace: :user # Configure namespace and other settings
21
22
  #
22
23
  # def component_styles
23
24
  # <<~CSS
@@ -29,6 +30,7 @@ end
29
30
  # @example ViewComponent Encapsulation Usage
30
31
  # class MyComponent < ApplicationComponent
31
32
  # include StyleCapsule::ViewComponent
33
+ # style_capsule namespace: :admin # Configure namespace and other settings
32
34
  #
33
35
  # def component_styles
34
36
  # <<~CSS
@@ -51,8 +53,10 @@ end
51
53
  # end
52
54
  #
53
55
  # class MyComponent < ApplicationComponent
56
+ # style_capsule namespace: :user # Configure namespace
57
+ #
54
58
  # def call
55
- # register_stylesheet("stylesheets/user/my_component")
59
+ # register_stylesheet("stylesheets/user/my_component") # Uses :user namespace automatically
56
60
  # content_tag(:div, "Content", class: "section")
57
61
  # end
58
62
  # end
@@ -43,7 +43,7 @@ module StyleCapsule
43
43
  def capsule_id: () -> String | nil
44
44
  def capsule_id: (String capsule_id) -> String
45
45
  def stylesheet_registry: (?namespace: Symbol | String | nil, ?cache_strategy: Symbol, ?cache_ttl: Integer | ActiveSupport::Duration | nil, ?cache_proc: Proc | nil) -> void
46
- def head_rendering!: () -> void # deprecated
46
+ def style_capsule: (?namespace: Symbol | String | nil, ?cache_strategy: Symbol | String | Proc | nil, ?cache_ttl: Integer | ActiveSupport::Duration | nil, ?cache_proc: Proc | nil, ?scoping_strategy: Symbol | nil, ?head_rendering: bool | nil) -> void
47
47
  def head_rendering?: () -> bool
48
48
  def stylesheet_namespace: () -> Symbol | String | nil
49
49
  def custom_capsule_id: () -> String | nil
@@ -54,7 +54,6 @@ module StyleCapsule
54
54
  def stylesheet_link_options: () -> Hash[untyped, untyped] | nil
55
55
  def stylesheet_link_options: (Hash[untyped, untyped] options) -> Hash[untyped, untyped]
56
56
  def css_scoping_strategy: () -> Symbol
57
- def css_scoping_strategy: (Symbol strategy) -> Symbol
58
57
  end
59
58
 
60
59
  def component_capsule: () -> String
@@ -68,7 +67,7 @@ module StyleCapsule
68
67
  def capsule_id: () -> String | nil
69
68
  def capsule_id: (String capsule_id) -> String
70
69
  def stylesheet_registry: (?namespace: Symbol | String | nil, ?cache_strategy: Symbol, ?cache_ttl: Integer | ActiveSupport::Duration | nil, ?cache_proc: Proc | nil) -> void
71
- def head_rendering!: () -> void # deprecated
70
+ def style_capsule: (?namespace: Symbol | String | nil, ?cache_strategy: Symbol | String | Proc | nil, ?cache_ttl: Integer | ActiveSupport::Duration | nil, ?cache_proc: Proc | nil, ?scoping_strategy: Symbol | nil, ?head_rendering: bool | nil) -> void
72
71
  def head_rendering?: () -> bool
73
72
  def stylesheet_namespace: () -> Symbol | String | nil
74
73
  def custom_capsule_id: () -> String | nil
@@ -79,7 +78,6 @@ module StyleCapsule
79
78
  def stylesheet_link_options: () -> Hash[untyped, untyped] | nil
80
79
  def stylesheet_link_options: (Hash[untyped, untyped] options) -> Hash[untyped, untyped]
81
80
  def css_scoping_strategy: () -> Symbol
82
- def css_scoping_strategy: (Symbol strategy) -> Symbol
83
81
  end
84
82
 
85
83
  def component_capsule: () -> String
@@ -89,7 +87,6 @@ module StyleCapsule
89
87
  module ViewComponentHelper
90
88
  def register_stylesheet: (String file_path, ?namespace: Symbol | String | nil, **Hash[untyped, untyped] options) -> void
91
89
  def stylesheet_registry_tags: (?namespace: Symbol | String | nil) -> String
92
- def stylesheet_registrymap_tags: (?namespace: Symbol | String | nil) -> String # deprecated
93
90
  end
94
91
 
95
92
  module Helper
@@ -98,13 +95,11 @@ module StyleCapsule
98
95
  def style_capsule: (?String css_content, ?capsule_id: String | nil) { () -> String } -> String
99
96
  def register_stylesheet: (String file_path, ?namespace: Symbol | String | nil, **Hash[untyped, untyped] options) -> void
100
97
  def stylesheet_registry_tags: (?namespace: Symbol | String | nil) -> String
101
- def stylesheet_registrymap_tags: (?namespace: Symbol | String | nil) -> String # deprecated
102
98
  end
103
99
 
104
100
  module PhlexHelper
105
101
  def register_stylesheet: (String file_path, ?namespace: Symbol | String | nil, **Hash[untyped, untyped] options) -> void
106
102
  def stylesheet_registry_tags: (?namespace: Symbol | String | nil) -> void
107
- def stylesheet_registrymap_tags: (?namespace: Symbol | String | nil) -> void # deprecated
108
103
  end
109
104
 
110
105
  class Railtie < Rails::Railtie
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: style_capsule
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov