style_capsule 1.0.2

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.
@@ -0,0 +1,479 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha1"
4
+ require "active_support/core_ext/string"
5
+
6
+ module StyleCapsule
7
+ # ViewComponent component concern for encapsulated CSS
8
+ #
9
+ # This implements attribute-based CSS scoping for component encapsulation:
10
+ # - CSS selectors are rewritten to include [data-capsule="..."] attribute selectors
11
+ # - Class names remain unchanged (no renaming)
12
+ # - Scope ID is per-component-type (shared across all instances)
13
+ # - Styles are rendered as <style> tag in body before component HTML
14
+ # - Automatically wraps component content in a scoped wrapper element
15
+ #
16
+ # Usage in a ViewComponent component:
17
+ #
18
+ # Instance method (dynamic rendering, all cache strategies except :file):
19
+ #
20
+ # class MyComponent < ApplicationComponent
21
+ # include StyleCapsule::ViewComponent
22
+ #
23
+ # def component_styles
24
+ # <<~CSS
25
+ # .section {
26
+ # color: red;
27
+ # }
28
+ # .heading:hover {
29
+ # opacity: 0.8;
30
+ # }
31
+ # CSS
32
+ # end
33
+ # end
34
+ #
35
+ # Class method (static rendering, supports all cache strategies including :file):
36
+ #
37
+ # class MyComponent < ApplicationComponent
38
+ # include StyleCapsule::ViewComponent
39
+ # stylesheet_registry cache_strategy: :file # File caching requires class method
40
+ #
41
+ # def self.component_styles
42
+ # <<~CSS
43
+ # .section {
44
+ # color: red;
45
+ # }
46
+ # CSS
47
+ # end
48
+ #
49
+ # def call
50
+ # # Content is automatically wrapped in a scoped element
51
+ # # No need to manually add data-capsule attribute!
52
+ # content_tag(:div, class: "section") do
53
+ # content_tag(:h2, "Hello", class: "heading")
54
+ # end
55
+ # end
56
+ # end
57
+ #
58
+ # For testing with a custom scope ID:
59
+ #
60
+ # class MyComponent < ApplicationComponent
61
+ # include StyleCapsule::ViewComponent
62
+ # capsule_id "test-capsule-123" # Use exact capsule ID for testing
63
+ #
64
+ # def component_styles
65
+ # <<~CSS
66
+ # .section { color: red; }
67
+ # CSS
68
+ # end
69
+ # end
70
+ #
71
+ # The CSS will be automatically rewritten from:
72
+ # .section { color: red; }
73
+ # .heading:hover { opacity: 0.8; }
74
+ #
75
+ # To:
76
+ # [data-capsule="a1b2c3d4"] .section { color: red; }
77
+ # [data-capsule="a1b2c3d4"] .heading:hover { opacity: 0.8; }
78
+ #
79
+ # And the HTML will be automatically wrapped:
80
+ # <div data-capsule="a1b2c3d4">
81
+ # <div class="section">...</div>
82
+ # </div>
83
+ #
84
+ # This ensures styles only apply to elements within the scoped component.
85
+ module ViewComponent
86
+ def self.included(base)
87
+ base.extend(ClassMethods)
88
+ base.include(ComponentStylesSupport)
89
+
90
+ # Use prepend to wrap call method
91
+ base.prepend(CallWrapper)
92
+ end
93
+
94
+ module ClassMethods
95
+ # Class-level cache for scoped CSS per component class
96
+ def css_cache
97
+ @css_cache ||= {}
98
+ end
99
+
100
+ # Clear the CSS cache for this component class
101
+ #
102
+ # Useful for testing or when you want to force CSS reprocessing.
103
+ # In development, this is automatically called when classes are reloaded.
104
+ #
105
+ # @example
106
+ # MyComponent.clear_css_cache
107
+ def clear_css_cache
108
+ @css_cache = {}
109
+ end
110
+
111
+ # Set or get a custom capsule ID for this component class (useful for testing)
112
+ #
113
+ # @param capsule_id [String, nil] The custom capsule ID to use (nil to get current value)
114
+ # @return [String, nil] The current capsule ID if no argument provided
115
+ # @example Setting a custom capsule ID
116
+ # class MyComponent < ApplicationComponent
117
+ # include StyleCapsule::ViewComponent
118
+ # capsule_id "test-capsule-123"
119
+ # end
120
+ # @example Getting the current capsule ID
121
+ # MyComponent.capsule_id # => "test-capsule-123" or nil
122
+ def capsule_id(capsule_id = nil)
123
+ if capsule_id.nil?
124
+ @custom_capsule_id if defined?(@custom_capsule_id)
125
+ else
126
+ @custom_capsule_id = capsule_id.to_s
127
+ end
128
+ end
129
+
130
+ # Configure stylesheet registry for head rendering
131
+ #
132
+ # Enables head rendering and configures namespace and cache strategy in a single call.
133
+ # All parameters are optional - calling without arguments enables head rendering with defaults.
134
+ #
135
+ # @param namespace [Symbol, String, nil] Namespace identifier (nil/blank uses default)
136
+ # @param cache_strategy [Symbol, String, Proc, nil] Cache strategy: :none (default), :time, :proc, :file
137
+ # - Symbol or String: :none, :time, :proc, :file (or "none", "time", "proc", "file")
138
+ # - Proc: Custom cache proc (automatically uses :proc strategy)
139
+ # Proc receives: (css_content, capsule_id, namespace) and should return [cache_key, should_cache, expires_at]
140
+ # @param cache_ttl [Integer, ActiveSupport::Duration, nil] Time-to-live in seconds (for :time strategy). Supports ActiveSupport::Duration (e.g., 1.hour, 30.minutes)
141
+ # @param cache_proc [Proc, nil] Custom cache proc (for :proc strategy, ignored if cache_strategy is a Proc)
142
+ # Proc receives: (css_content, capsule_id, namespace) and should return [cache_key, should_cache, expires_at]
143
+ # @return [void]
144
+ # @example Basic usage (enables head rendering with defaults)
145
+ # class MyComponent < ApplicationComponent
146
+ # include StyleCapsule::ViewComponent
147
+ # stylesheet_registry
148
+ # end
149
+ # @example With namespace
150
+ # class AdminComponent < ApplicationComponent
151
+ # include StyleCapsule::ViewComponent
152
+ # stylesheet_registry namespace: :admin
153
+ # end
154
+ # @example With time-based caching
155
+ # class MyComponent < ApplicationComponent
156
+ # include StyleCapsule::ViewComponent
157
+ # stylesheet_registry cache_strategy: :time, cache_ttl: 1.hour
158
+ # end
159
+ # @example With custom proc caching
160
+ # class MyComponent < ApplicationComponent
161
+ # include StyleCapsule::ViewComponent
162
+ # stylesheet_registry cache_strategy: :proc, cache_proc: ->(css, capsule_id, ns) {
163
+ # cache_key = "css_#{capsule_id}_#{ns}"
164
+ # should_cache = css.length > 100
165
+ # expires_at = Time.now + 1800
166
+ # [cache_key, should_cache, expires_at]
167
+ # }
168
+ # end
169
+ # @example File-based caching (requires class method component_styles)
170
+ # class MyComponent < ApplicationComponent
171
+ # include StyleCapsule::ViewComponent
172
+ # stylesheet_registry cache_strategy: :file
173
+ #
174
+ # def self.component_styles # Must be class method for file caching
175
+ # <<~CSS
176
+ # .section { color: red; }
177
+ # CSS
178
+ # end
179
+ # end
180
+ # @example All options combined
181
+ # class MyComponent < ApplicationComponent
182
+ # include StyleCapsule::ViewComponent
183
+ # stylesheet_registry namespace: :admin, cache_strategy: :time, cache_ttl: 1.hour
184
+ # end
185
+ def stylesheet_registry(namespace: nil, cache_strategy: :none, cache_ttl: nil, cache_proc: nil)
186
+ @head_rendering = true
187
+ @stylesheet_namespace = namespace unless namespace.nil?
188
+
189
+ # Normalize cache_strategy: convert strings to symbols, handle Proc
190
+ normalized_strategy, normalized_proc = normalize_cache_strategy(cache_strategy, cache_proc)
191
+ @inline_cache_strategy = normalized_strategy
192
+ @inline_cache_ttl = cache_ttl
193
+ @inline_cache_proc = normalized_proc
194
+ end
195
+
196
+ private
197
+
198
+ # Normalize cache_strategy to handle Symbol, String, and Proc
199
+ #
200
+ # @param cache_strategy [Symbol, String, Proc, nil] Cache strategy
201
+ # @param cache_proc [Proc, nil] Optional cache proc (ignored if cache_strategy is a Proc)
202
+ # @return [Array<Symbol, Proc|nil>] Normalized strategy and proc
203
+ def normalize_cache_strategy(cache_strategy, cache_proc)
204
+ case cache_strategy
205
+ when Proc
206
+ # If cache_strategy is a Proc, use it as the proc and set strategy to :proc
207
+ [:proc, cache_strategy]
208
+ when String
209
+ # Convert string to symbol
210
+ normalized = cache_strategy.to_sym
211
+ unless [:none, :time, :proc, :file].include?(normalized)
212
+ raise ArgumentError, "cache_strategy must be :none, :time, :proc, or :file (got: #{cache_strategy.inspect})"
213
+ end
214
+ [normalized, cache_proc]
215
+ when Symbol
216
+ unless [:none, :time, :proc, :file].include?(cache_strategy)
217
+ raise ArgumentError, "cache_strategy must be :none, :time, :proc, or :file (got: #{cache_strategy.inspect})"
218
+ end
219
+ [cache_strategy, cache_proc]
220
+ when nil
221
+ [:none, nil]
222
+ else
223
+ raise ArgumentError, "cache_strategy must be a Symbol, String, or Proc (got: #{cache_strategy.class})"
224
+ end
225
+ end
226
+
227
+ # Deprecated: Use stylesheet_registry instead
228
+ # @deprecated Use {#stylesheet_registry} instead
229
+ def head_rendering!
230
+ stylesheet_registry
231
+ end
232
+
233
+ # Check if component uses head rendering
234
+ def head_rendering?
235
+ return false unless defined?(@head_rendering)
236
+ @head_rendering
237
+ end
238
+
239
+ public :head_rendering?
240
+
241
+ # Get the namespace for stylesheet registry
242
+ def stylesheet_namespace
243
+ @stylesheet_namespace if defined?(@stylesheet_namespace)
244
+ end
245
+
246
+ # Get the custom scope ID if set (alias for capsule_id getter)
247
+ def custom_capsule_id
248
+ @custom_capsule_id if defined?(@custom_capsule_id)
249
+ end
250
+
251
+ # Get inline cache strategy
252
+ def inline_cache_strategy
253
+ @inline_cache_strategy if defined?(@inline_cache_strategy)
254
+ end
255
+
256
+ # Get inline cache TTL
257
+ def inline_cache_ttl
258
+ @inline_cache_ttl if defined?(@inline_cache_ttl)
259
+ end
260
+
261
+ # Get inline cache proc
262
+ def inline_cache_proc
263
+ @inline_cache_proc if defined?(@inline_cache_proc)
264
+ end
265
+
266
+ public :head_rendering?, :stylesheet_namespace, :custom_capsule_id, :inline_cache_strategy, :inline_cache_ttl, :inline_cache_proc
267
+
268
+ # Set or get options for stylesheet_link_tag when using file-based caching
269
+ #
270
+ # @param options [Hash, nil] Options to pass to stylesheet_link_tag (e.g., "data-turbo-track": "reload", omit to get current value)
271
+ # @return [Hash, nil] The current stylesheet link options if no argument provided
272
+ # @example Setting stylesheet link options
273
+ # class MyComponent < ApplicationComponent
274
+ # include StyleCapsule::ViewComponent
275
+ # stylesheet_registry cache_strategy: :file
276
+ # stylesheet_link_options "data-turbo-track": "reload"
277
+ # end
278
+ # @example Getting the current options
279
+ # MyComponent.stylesheet_link_options # => {"data-turbo-track" => "reload"} or nil
280
+ def stylesheet_link_options(options = nil)
281
+ if options.nil?
282
+ @stylesheet_link_options if defined?(@stylesheet_link_options)
283
+ else
284
+ @stylesheet_link_options = options
285
+ end
286
+ end
287
+
288
+ public :stylesheet_link_options
289
+
290
+ # Set or get CSS scoping strategy
291
+ #
292
+ # @param strategy [Symbol, nil] Scoping strategy: :selector_patching (default) or :nesting (omit to get current value)
293
+ # - :selector_patching: Adds [data-capsule="..."] prefix to each selector (better browser support)
294
+ # - :nesting: Wraps entire CSS in [data-capsule="..."] { ... } (more performant, requires CSS nesting support)
295
+ # @return [Symbol] The current scoping strategy (default: :selector_patching)
296
+ # @example Using CSS nesting (requires Chrome 112+, Firefox 117+, Safari 16.5+)
297
+ # class MyComponent < ApplicationComponent
298
+ # include StyleCapsule::ViewComponent
299
+ # css_scoping_strategy :nesting # More performant, no CSS parsing needed
300
+ #
301
+ # def component_styles
302
+ # <<~CSS
303
+ # .section { color: red; }
304
+ # .heading:hover { opacity: 0.8; }
305
+ # CSS
306
+ # end
307
+ # end
308
+ # # Output: [data-capsule="abc123"] { .section { color: red; } .heading:hover { opacity: 0.8; } }
309
+ # @example Using selector patching (default, better browser support)
310
+ # class MyComponent < ApplicationComponent
311
+ # include StyleCapsule::ViewComponent
312
+ # css_scoping_strategy :selector_patching # Default
313
+ #
314
+ # def component_styles
315
+ # <<~CSS
316
+ # .section { color: red; }
317
+ # CSS
318
+ # end
319
+ # end
320
+ # # Output: [data-capsule="abc123"] .section { color: red; }
321
+ def css_scoping_strategy(strategy = nil)
322
+ if strategy.nil?
323
+ # Check if this class has a strategy set
324
+ if defined?(@css_scoping_strategy) && @css_scoping_strategy
325
+ @css_scoping_strategy
326
+ # Otherwise, check parent class (for inheritance)
327
+ elsif superclass.respond_to?(:css_scoping_strategy, true)
328
+ superclass.css_scoping_strategy
329
+ else
330
+ :selector_patching
331
+ end
332
+ else
333
+ unless [:selector_patching, :nesting].include?(strategy)
334
+ raise ArgumentError, "css_scoping_strategy must be :selector_patching or :nesting (got: #{strategy.inspect})"
335
+ end
336
+ @css_scoping_strategy = strategy
337
+ end
338
+ end
339
+
340
+ public :css_scoping_strategy
341
+ end
342
+
343
+ # Module that wraps call to add scoped wrapper
344
+ module CallWrapper
345
+ def call
346
+ if component_styles?
347
+ # Render styles first
348
+ styles_html = render_capsule_styles
349
+
350
+ # Get content from original call method
351
+ content_html = super
352
+
353
+ # Wrap content in scoped element
354
+ scoped_wrapper = helpers.content_tag(:div, content_html.html_safe, data: {capsule: component_capsule})
355
+
356
+ # Combine styles and wrapped content
357
+ (styles_html + scoped_wrapper).html_safe
358
+ else
359
+ # No styles, render normally
360
+ super
361
+ end
362
+ end
363
+ end
364
+
365
+ # Get the component capsule ID (per-component-type, shared across instances)
366
+ #
367
+ # All instances of the same component class share the same capsule ID.
368
+ # Can be overridden with capsule_id class method for testing.
369
+ #
370
+ # @return [String] The capsule ID (e.g., "a1b2c3d4")
371
+ def component_capsule
372
+ return @component_capsule if defined?(@component_capsule)
373
+
374
+ # Check for custom capsule ID set via class method
375
+ @component_capsule = self.class.custom_capsule_id || generate_capsule_id
376
+ end
377
+
378
+ private
379
+
380
+ # Render the style capsule <style> tag
381
+ #
382
+ # Can render in body (default) or register for head rendering via StylesheetRegistry
383
+ #
384
+ # Supports both instance method (def component_styles) and class method (def self.component_styles).
385
+ # File caching is only allowed for class method component_styles.
386
+ #
387
+ # @return [String] HTML string with style tag or empty string
388
+ def render_capsule_styles
389
+ css_content = component_styles_content
390
+ return "".html_safe if css_content.nil? || css_content.to_s.strip.empty?
391
+
392
+ scoped_css = scope_css(css_content)
393
+ capsule_id = component_capsule
394
+
395
+ # Check if component uses head rendering
396
+ if head_rendering?
397
+ # Register for head rendering instead of rendering in body
398
+ namespace = self.class.stylesheet_namespace
399
+
400
+ # Get cache configuration from class
401
+ cache_strategy = self.class.inline_cache_strategy || :none
402
+ cache_ttl = self.class.inline_cache_ttl
403
+ cache_proc = self.class.inline_cache_proc
404
+
405
+ # File caching is only allowed for class method component_styles
406
+ if cache_strategy == :file && !file_caching_allowed?
407
+ # Fall back to :none strategy if file caching requested but not allowed
408
+ cache_strategy = :none
409
+ cache_ttl = nil
410
+ cache_proc = nil
411
+ end
412
+
413
+ # Generate cache key based on component class and capsule
414
+ cache_key = (cache_strategy != :none) ? "#{self.class.name}:#{capsule_id}" : nil
415
+
416
+ StylesheetRegistry.register_inline(
417
+ scoped_css,
418
+ namespace: namespace,
419
+ capsule_id: capsule_id,
420
+ cache_key: cache_key,
421
+ cache_strategy: cache_strategy,
422
+ cache_ttl: cache_ttl,
423
+ cache_proc: cache_proc,
424
+ component_class: self.class,
425
+ stylesheet_link_options: self.class.stylesheet_link_options
426
+ )
427
+ "".html_safe
428
+ else
429
+ # Render <style> tag in body (HTML5 allows this)
430
+ helpers.content_tag(:style, scoped_css.html_safe, type: "text/css")
431
+ end
432
+ end
433
+
434
+ # Check if component should use head rendering
435
+ #
436
+ # Checks class-level configuration first, then allows instance override.
437
+ def head_rendering?
438
+ return true if self.class.head_rendering?
439
+ false
440
+ end
441
+
442
+ # Scope CSS and return scoped CSS with attribute selectors
443
+ def scope_css(css_content)
444
+ # Use class-level cache to avoid reprocessing same CSS
445
+ # Include capsule_id and scoping strategy in cache key
446
+ capsule_id = component_capsule
447
+ scoping_strategy = self.class.css_scoping_strategy
448
+ cache_key = "#{self.class.name}:#{capsule_id}:#{scoping_strategy}"
449
+
450
+ if self.class.css_cache.key?(cache_key)
451
+ return self.class.css_cache[cache_key]
452
+ end
453
+
454
+ # Use the configured scoping strategy
455
+ scoped_css = case scoping_strategy
456
+ when :nesting
457
+ CssProcessor.scope_with_nesting(css_content, capsule_id)
458
+ else # :selector_patching (default)
459
+ CssProcessor.scope_selectors(css_content, capsule_id)
460
+ end
461
+
462
+ # Cache at class level (one style block per component type/scope/strategy combination)
463
+ self.class.css_cache[cache_key] = scoped_css
464
+
465
+ scoped_css
466
+ end
467
+
468
+ # Generate a unique capsule ID based on component class name (per-component-type)
469
+ #
470
+ # This ensures all instances of the same component class share the same capsule ID,
471
+ # similar to how component-based frameworks scope styles per component type.
472
+ #
473
+ # @return [String] The capsule ID (e.g., "a1b2c3d4")
474
+ def generate_capsule_id
475
+ class_name = self.class.name || self.class.object_id.to_s
476
+ "a#{Digest::SHA1.hexdigest(class_name)}"[0, 8]
477
+ end
478
+ end
479
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StyleCapsule
4
+ # ViewComponent helper module for StyleCapsule stylesheet registry
5
+ #
6
+ # Include this in your base ViewComponent class (e.g., ApplicationComponent):
7
+ # class ApplicationComponent < ViewComponent::Base
8
+ # include StyleCapsule::ViewComponentHelper
9
+ # end
10
+ #
11
+ # Usage in ViewComponent layouts:
12
+ # def call
13
+ # helpers.stylesheet_registrymap_tags
14
+ # end
15
+ #
16
+ # Usage in ViewComponent components:
17
+ # def call
18
+ # register_stylesheet("stylesheets/user/my_component")
19
+ # content_tag(:div, "Content")
20
+ # end
21
+ module ViewComponentHelper
22
+ # Register a stylesheet file for head rendering
23
+ #
24
+ # Usage in ViewComponent components:
25
+ # def call
26
+ # register_stylesheet("stylesheets/user/my_component", "data-turbo-track": "reload")
27
+ # register_stylesheet("stylesheets/admin/dashboard", namespace: :admin)
28
+ # content_tag(:div, "Content")
29
+ # end
30
+ #
31
+ # @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)
33
+ # @param options [Hash] Options for stylesheet_link_tag
34
+ # @return [void]
35
+ def register_stylesheet(file_path, namespace: nil, **options)
36
+ StyleCapsule::StylesheetRegistry.register(file_path, namespace: namespace, **options)
37
+ end
38
+
39
+ # Render StyleCapsule registered stylesheets (similar to javascript_importmap_tags)
40
+ #
41
+ # Usage in ViewComponent layouts:
42
+ # def call
43
+ # helpers.stylesheet_registrymap_tags
44
+ # helpers.stylesheet_registrymap_tags(namespace: :admin)
45
+ # end
46
+ #
47
+ # @param namespace [Symbol, String, nil] Optional namespace to render (nil/blank renders all)
48
+ # @return [String] HTML-safe string with stylesheet tags
49
+ def stylesheet_registrymap_tags(namespace: nil)
50
+ StyleCapsule::StylesheetRegistry.render_head_stylesheets(helpers, namespace: namespace)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha1"
4
+ require "active_support/core_ext/string"
5
+
6
+ # StyleCapsule provides attribute-based CSS scoping for component encapsulation
7
+ # in Phlex components, ViewComponent components, and ERB templates.
8
+ #
9
+ # @example Phlex Component Usage
10
+ # class MyComponent < ApplicationComponent
11
+ # include StyleCapsule::Component
12
+ #
13
+ # def component_styles
14
+ # <<~CSS
15
+ # .section { color: red; }
16
+ # CSS
17
+ # end
18
+ # end
19
+ #
20
+ # @example ViewComponent Encapsulation Usage
21
+ # class MyComponent < ApplicationComponent
22
+ # include StyleCapsule::ViewComponent
23
+ #
24
+ # def component_styles
25
+ # <<~CSS
26
+ # .section { color: red; }
27
+ # .heading:hover { opacity: 0.8; }
28
+ # CSS
29
+ # end
30
+ #
31
+ # def call
32
+ # # Content is automatically wrapped in a scoped element
33
+ # content_tag(:div, class: "section") do
34
+ # content_tag(:h2, "Hello", class: "heading")
35
+ # end
36
+ # end
37
+ # end
38
+ #
39
+ # @example ViewComponent Helper Usage (for stylesheet registry)
40
+ # class ApplicationComponent < ViewComponent::Base
41
+ # include StyleCapsule::ViewComponentHelper
42
+ # end
43
+ #
44
+ # class MyComponent < ApplicationComponent
45
+ # def call
46
+ # register_stylesheet("stylesheets/user/my_component")
47
+ # content_tag(:div, "Content", class: "section")
48
+ # end
49
+ # end
50
+ #
51
+ # @example ERB Helper Usage
52
+ # # Helpers are automatically included - no setup required
53
+ # <%= style_capsule do %>
54
+ # <style>.section { color: red; }</style>
55
+ # <div class="section">Content</div>
56
+ # <% end %>
57
+ #
58
+ # <%= stylesheet_registrymap_tags %>
59
+ # <%= stylesheet_registrymap_tags(namespace: :admin) %>
60
+ #
61
+ # @example Namespace Support
62
+ # # Register stylesheets with namespaces
63
+ # StyleCapsule::StylesheetRegistry.register('stylesheets/admin/dashboard', namespace: :admin)
64
+ # StyleCapsule::StylesheetRegistry.register('stylesheets/user/profile', namespace: :user)
65
+ #
66
+ # # Render all namespaces (default)
67
+ # <%= stylesheet_registrymap_tags %>
68
+ #
69
+ # # Render specific namespace
70
+ # <%= stylesheet_registrymap_tags(namespace: :admin) %>
71
+ #
72
+ # @example File-Based Caching (HTTP Caching)
73
+ # class MyComponent < ApplicationComponent
74
+ # include StyleCapsule::Component
75
+ # stylesheet_registry cache_strategy: :file # Writes CSS to files for HTTP caching
76
+ # end
77
+ #
78
+ # # CSS files are written to app/assets/builds/capsules/
79
+ # # Files are automatically precompiled via: bin/rails assets:precompile
80
+ # # Or manually: bin/rails style_capsule:build
81
+ module StyleCapsule
82
+ require_relative "style_capsule/version"
83
+ require_relative "style_capsule/css_processor"
84
+ require_relative "style_capsule/css_file_writer"
85
+ require_relative "style_capsule/stylesheet_registry"
86
+ require_relative "style_capsule/component_styles_support"
87
+ require_relative "style_capsule/component"
88
+ require_relative "style_capsule/helper"
89
+ require_relative "style_capsule/phlex_helper"
90
+ require_relative "style_capsule/view_component"
91
+ require_relative "style_capsule/view_component_helper"
92
+ require_relative "style_capsule/railtie" if defined?(Rails) && defined?(Rails::Railtie)
93
+ end