vident 1.0.1 → 2.0.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 +4 -4
- data/CHANGELOG.md +54 -0
- data/README.md +49 -18
- data/lib/vident/caching.rb +4 -110
- data/lib/vident/capabilities/caching.rb +98 -0
- data/lib/vident/capabilities/child_element_rendering.rb +92 -0
- data/lib/vident/capabilities/class_list_building.rb +23 -0
- data/lib/vident/capabilities/declarable.rb +39 -0
- data/lib/vident/capabilities/identifiable.rb +54 -0
- data/lib/vident/capabilities/inspectable.rb +17 -0
- data/lib/vident/capabilities/root_element_rendering.rb +31 -0
- data/lib/vident/capabilities/stimulus_data_emitting.rb +98 -0
- data/lib/vident/capabilities/stimulus_declaring.rb +79 -0
- data/lib/vident/capabilities/stimulus_draft.rb +51 -0
- data/lib/vident/capabilities/stimulus_mutation.rb +60 -0
- data/lib/vident/capabilities/stimulus_parsing.rb +144 -0
- data/lib/vident/capabilities/tailwind.rb +18 -0
- data/lib/vident/component.rb +14 -76
- data/lib/vident/engine.rb +6 -5
- data/lib/vident/error.rb +16 -0
- data/lib/vident/internals/action_builder.rb +97 -0
- data/lib/vident/internals/attribute_writer.rb +17 -0
- data/lib/vident/internals/class_list_builder.rb +62 -0
- data/lib/vident/internals/declaration.rb +13 -0
- data/lib/vident/internals/declarations.rb +64 -0
- data/lib/vident/internals/draft.rb +47 -0
- data/lib/vident/internals/dsl.rb +172 -0
- data/lib/vident/internals/plan.rb +9 -0
- data/lib/vident/internals/registry.rb +37 -0
- data/lib/vident/internals/resolver.rb +316 -0
- data/lib/vident/internals/target_builder.rb +23 -0
- data/lib/vident/stable_id.rb +3 -3
- data/lib/vident/stimulus/action.rb +127 -0
- data/lib/vident/stimulus/base.rb +26 -0
- data/lib/vident/stimulus/class_map.rb +57 -0
- data/lib/vident/stimulus/collection.rb +40 -0
- data/lib/vident/stimulus/combinable.rb +30 -0
- data/lib/vident/stimulus/controller.rb +45 -0
- data/lib/vident/stimulus/naming.rb +9 -9
- data/lib/vident/stimulus/null.rb +7 -0
- data/lib/vident/stimulus/outlet.rb +93 -0
- data/lib/vident/stimulus/param.rb +56 -0
- data/lib/vident/stimulus/target.rb +48 -0
- data/lib/vident/stimulus/value.rb +57 -0
- data/lib/vident/stimulus_null.rb +4 -8
- data/lib/vident/tailwind.rb +4 -17
- data/lib/vident/types.rb +28 -0
- data/lib/vident/version.rb +1 -6
- data/lib/vident.rb +44 -36
- data/skills/vident/SKILL.md +133 -21
- data/skills/vident/api-reference.md +662 -0
- data/skills/vident/examples.md +505 -0
- metadata +40 -28
- data/lib/vident/child_element_helper.rb +0 -64
- data/lib/vident/class_list_builder.rb +0 -112
- data/lib/vident/component_attribute_resolver.rb +0 -87
- data/lib/vident/component_class_lists.rb +0 -34
- data/lib/vident/stimulus/primitive.rb +0 -38
- data/lib/vident/stimulus.rb +0 -31
- data/lib/vident/stimulus_action.rb +0 -133
- data/lib/vident/stimulus_action_collection.rb +0 -11
- data/lib/vident/stimulus_attribute_base.rb +0 -67
- data/lib/vident/stimulus_attributes.rb +0 -129
- data/lib/vident/stimulus_builder.rb +0 -119
- data/lib/vident/stimulus_class.rb +0 -59
- data/lib/vident/stimulus_class_collection.rb +0 -11
- data/lib/vident/stimulus_collection_base.rb +0 -51
- data/lib/vident/stimulus_component.rb +0 -75
- data/lib/vident/stimulus_controller.rb +0 -41
- data/lib/vident/stimulus_controller_collection.rb +0 -14
- data/lib/vident/stimulus_data_attribute_builder.rb +0 -32
- data/lib/vident/stimulus_helper.rb +0 -66
- data/lib/vident/stimulus_outlet.rb +0 -90
- data/lib/vident/stimulus_outlet_collection.rb +0 -11
- data/lib/vident/stimulus_param.rb +0 -42
- data/lib/vident/stimulus_param_collection.rb +0 -11
- data/lib/vident/stimulus_target.rb +0 -47
- data/lib/vident/stimulus_target_collection.rb +0 -18
- data/lib/vident/stimulus_value.rb +0 -39
- data/lib/vident/stimulus_value_collection.rb +0 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1c85914dca2e7df8a1ff9711be7cf0f1528409ff7d44dbb72501474c2c1359c2
|
|
4
|
+
data.tar.gz: 43e7d176b19a36b2c7d8640cd230429ae59a46e0b77ad9e6c6d24c9c40e38973
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4e07543b9851d9b4d7a9ef98eadec0a53ad8c8e409934fd0f92e942386235fa7f964d36fa04bb84bde91d036ce4cf7231d848e13274f3190f91573b00339a999
|
|
7
|
+
data.tar.gz: b89835399b32ff4e98efd24f1fb75328ed0633a73c935eb6a7c844f5f1dc7f909ade0805741445db81a0d704ffcc066aa416c17ee6a65100b9d8fb9738d86b41
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,60 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
## [2.0.0] - 2026-04-24
|
|
10
|
+
|
|
11
|
+
Vident 2.0 is a ground-up rearchitecture of the DSL, attribute resolution, and composition model. The public shape of `stimulus do ... end`, `root_element`, `child_element`, outlets, props, and the `stimulus_*:` prop/kwarg API is preserved for common cases, but several internals and a handful of edge-case behaviours changed. See `doc/reviews/v1-gotchas.md` for the full list of fixed gotchas; the highlights are below.
|
|
12
|
+
|
|
13
|
+
### Breaking
|
|
14
|
+
|
|
15
|
+
- **Namespace consolidation.** The `Vident2::*` namespace used during side-by-side development has been removed. Everything now lives under `Vident::*` directly: `Vident::Component`, `Vident::Capabilities::*`, `Vident::Phlex::HTML`, `Vident::ViewComponent::Base`, `Vident::Stimulus::*` (value classes), `Vident::Internals::*` (DSL/Resolver/Plan). Upgrade path is a `s/Vident2::/Vident::/g` on project code.
|
|
16
|
+
- **Legacy collection classes removed.** `Vident::StimulusAction`, `StimulusTarget`, `StimulusController`, `StimulusValue`, `StimulusParam`, `StimulusOutlet`, `StimulusClass`, and each of their `*Collection` companions (plus `StimulusBuilder`, `StimulusDataAttributeBuilder`, `StimulusAttributeBase`, `StimulusAttributes`, `StimulusAction::Descriptor`) are gone. The 2.0 equivalents live under `Vident::Stimulus::*` (`Action`, `Target`, `Controller`, `Value`, `Param`, `Outlet`, `ClassMap`, `Collection`). `#to_h` now uses **Symbol keys** uniformly; V1 returned String keys for some primitives.
|
|
17
|
+
- **`child_element` strictness.** Passing both `stimulus_<singular>:` and `stimulus_<plural>:` kwargs now raises `ArgumentError` (`"mutually exclusive — pass one or the other."`). V1 silently dropped the singular when the plural was an empty array (latent since 1.0.0, see "Fixed" below). Workaround at call sites that relied on the V1 shape: `stimulus_targets: [:input, *control_targets]`.
|
|
18
|
+
- **`stimulus_controllers:` prop appends instead of replacing.** V1 replaced the implied controller when you passed `stimulus_controllers:` at render time; V2 appends, so the root element carries `data-controller="implied extra"`. Migration: if you wanted to *replace*, add `no_stimulus_controller` at the class level.
|
|
19
|
+
- **Outlet DSL procs now resolve.** V1 silently passed a proc through as the outlet selector string, emitting literal `#<Proc:0x...>`. V2 evaluates the proc in the instance binding and emits the returned string. This turns previously broken code into working code, but if you relied on the proc-as-identity string for some reason, you'll now see a different data attribute.
|
|
20
|
+
- **`nil` vs `false` drop rule.** V1 dropped both `nil` and `false` proc returns silently via a `blank?` filter. V2 drops only `nil`; `false` reaches the parser and raises `Vident::ParseError`. Fix: return `nil` (not `false`) to mean "don't emit this entry."
|
|
21
|
+
- **`no_stimulus_controller` + DSL body now raises loudly.** V1 raised a bare `StandardError` at instance init. V2 raises `Vident::DeclarationError` at `stimulus do ... end` time with the offending class name and caller location in the message.
|
|
22
|
+
- **StableId strategy.** Unchanged from 1.0, but reiterated here because it's the biggest gotcha during upgrade. `Vident::StableId` requires an explicit `strategy` and per-request seed — `bin/rails generate vident:install` sets both up correctly.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **Fluent action DSL.** Inside `stimulus do ... end`, the singular `action(...)` call returns a chainable builder: `action(:escape).on(:keydown).keyboard("esc").window`, `action(:save).modifier(:prevent, :stop)`, `action(:delete).when { admin? }`. Chain methods: `.on`, `.call_method`, `.modifier`, `.keyboard`, `.window`, `.on_controller`, `.when`.
|
|
27
|
+
- **Kwargs shorthand for actions.** `action :save, on: :click, modifier: [:prevent, :stop], keyboard: "ctrl+s", window: true, on_controller: :admin, call_method: :handle_save, when: -> { ... }` — equivalent to the fluent chain, pick whichever reads better. Unknown kwargs raise `ArgumentError`.
|
|
28
|
+
- **Controller aliases.** `controller "admin/users", as: :admin` inside `stimulus do` declares a short alias; `action(...).on_controller(:admin)` or `action(..., on_controller: :admin)` then routes through it. Unknown alias refs raise `Vident::DeclarationError`. Runtime inputs (`stimulus_actions: [{method: :save, controller: :admin}]`) resolve the same map.
|
|
29
|
+
- **Capability mixin composition.** `Vident::Component` is a composition root that includes twelve focused capability mixins (`Tailwind`, `Declarable`, `Identifiable`, `StimulusDeclaring`, `StimulusParsing`, `StimulusMutation`, `StimulusDraft`, `StimulusDataEmitting`, `ClassListBuilding`, `RootElementRendering`, `ChildElementRendering`, `Inspectable`), plus an opt-in `Caching` mixin (thirteen in total, including Caching). Mixin order mirrors capability dependencies.
|
|
30
|
+
- **Singular DSL primitives.** `value(:name, *, **)`, `param(:name, *, **)`, `outlet(:name, *, **)`, `class_map(:name, *, **)`, `controller(path, as: nil)`, `controllers(*paths)`, `target(:name).when { ... }` — full set of singular entry points alongside the plural forms.
|
|
31
|
+
- **Draft → Plan seal pattern.** Internal: `after_component_initialize` mutators (e.g. `add_stimulus_actions`) still work against a mutable Draft; the Draft seals into an immutable Plan once rendering begins. Prevents the V1 "mutator silently ignored post-render" class of bug.
|
|
32
|
+
- **Phase-split proc resolution.** DSL procs are evaluated in two phases: static (after_initialize, no `view_context`) and procs (`before_template` / `before_render`, `view_context`/`helpers` wired). Procs can now call Rails helpers (`number_with_precision`, url helpers, `t`, `l`, etc.) safely — already backported to 1.0.2.
|
|
33
|
+
- `has_stimulus_controller` class method re-enables the implied controller in a subclass after a parent declared `no_stimulus_controller` (#27).
|
|
34
|
+
- `root_element_class_list(extra = nil)` and `root_element_data_attributes` for components rendering through third-party tag helpers instead of `root_element(...)` (#25; V1 names `render_classes` / `stimulus_data_attributes` — see UPGRADING.md §8).
|
|
35
|
+
- Class-level Stimulus builders — `MyComponent.stimulus_target(:x)` etc. — return value objects without a component instance (#18).
|
|
36
|
+
- `Vident::Types::*` — seven named Literal unions for the built-in `stimulus_*:` prop types, reusable from user components (#16).
|
|
37
|
+
- `cache_component(*keys, **options, &block)` on both adapter base classes — fragment-caches the block using the Vident-computed `cache_key` (#13).
|
|
38
|
+
- Shared `Vident::Stimulus::Base` + `Combinable` for the value classes: `with(**overrides)` combinator, canonical `deconstruct_keys` (restores pattern matching — previously shadowed by the `to_h` data-attribute override), and `Action.parse([event, existing_action])` merge form (#28).
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- `child_element` no longer silently drops a `stimulus_<singular>:` kwarg when `stimulus_<plural>:` is also passed with an empty array. The V1 helper returned early on an empty-but-truthy plural, losing the singular and emitting no data attribute — latent since 1.0.0, surfaced by call sites like `child_element(:input, stimulus_target: :input, stimulus_targets: control_targets)` where `control_targets` defaulted to `[]`. V2 raises `ArgumentError` on both-set (see Breaking); workaround on 1.x is `stimulus_targets: [:input, *control_targets]` (also works on 2.x).
|
|
43
|
+
- Outlet DSL procs now resolve instead of being treated as identity strings (see Breaking).
|
|
44
|
+
- `add_stimulus_actions([:click, :handle])` now treats the Array as *one* action descriptor (event + method pair) rather than splatting it into two separate `:click` and `:handle` actions. Matches the DSL's `actions [:click, :handle]` semantics — the V1 asymmetry is gone.
|
|
45
|
+
|
|
46
|
+
### Removed
|
|
47
|
+
|
|
48
|
+
- `Vident::StimulusAction::Descriptor` typed data class. The V2 equivalent is `Vident::Stimulus::Action` itself — the Hash shape accepted by the DSL (`{event:, method:, controller:, options:, keyboard:, window:}`) parses directly into the value class now. Callers that instantiated `Descriptor` explicitly should swap to `Vident::Stimulus::Action.parse(hash, implied: ...)` or just pass the Hash.
|
|
49
|
+
- Legacy top-level requires like `require "vident/stimulus_action"`. The main `require "vident"` loads the whole surface; deep requires for individual value classes still work (`require "vident/stimulus/action"`) but are no longer the documented path.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## [1.0.2] - 2026-04-21
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- Stimulus DSL procs (`values foo: -> { ... }`, `actions -> { ... }`, etc.) now resolve at **render time** — Phlex's `before_template` for `Vident::Phlex::HTML`, ViewComponent's `before_render` for `Vident::ViewComponent::Base` — instead of in `after_initialize`. Procs can now reach `helpers` / `view_context`, so they can call Rails helpers (`number_with_precision`, `t`, `l`, url helpers, etc.). Non-proc DSL entries still land in the collections at init time, so `after_component_initialize` mutators and external readers see them in the same order as before.
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
|
|
60
|
+
- `phlex_helpers :name1, :name2, ...` class macro on `Vident::Phlex::HTML` — opts the component into Phlex's per-helper Rails adapters (`Phlex::Rails::Helpers::<CamelCase>`) so DSL procs can call helpers bare (`number_with_precision(@amount, precision: 2)`) instead of via the deprecated `helpers.<method>`. Unknown helper names raise `ArgumentError` at class definition.
|
|
61
|
+
|
|
62
|
+
|
|
9
63
|
## [1.0.0] - 2026-04-19
|
|
10
64
|
|
|
11
65
|
### Breaking
|
data/README.md
CHANGED
|
@@ -79,9 +79,13 @@ class ButtonComponent < Vident::ViewComponent::Base
|
|
|
79
79
|
|
|
80
80
|
# Configure Stimulus integration
|
|
81
81
|
stimulus do
|
|
82
|
-
#
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
# Fluent action DSL: reads left-to-right as "the handle_click method fires on the click event".
|
|
83
|
+
action(:handle_click).on(:click)
|
|
84
|
+
# Kwargs shorthand — same result, pick whichever reads better:
|
|
85
|
+
action :handle_submit, on: :submit, modifier: [:prevent, :stop]
|
|
86
|
+
# Proc for conditional / cross-component wiring, evaluated in the instance at render time.
|
|
87
|
+
action(-> { [stimulus_scoped_event(:my_custom_event), :handle_this] if should_handle_this? })
|
|
88
|
+
|
|
85
89
|
# Map the clicked_count prop as a Stimulus value
|
|
86
90
|
values_from_props :clicked_count
|
|
87
91
|
# Dynamic values using procs (evaluated in component context)
|
|
@@ -178,7 +182,7 @@ Use the component in your views:
|
|
|
178
182
|
<%= render ButtonComponent.new(text: "Cancel", url: "/home", style: :secondary) %>
|
|
179
183
|
|
|
180
184
|
<!-- Override things -->
|
|
181
|
-
<%= render ButtonComponent.new(text: "Cancel", url: "/home" classes: "bg-red-900", html_options: {role: "button"}) %>
|
|
185
|
+
<%= render ButtonComponent.new(text: "Cancel", url: "/home", classes: "bg-red-900", html_options: {role: "button"}) %>
|
|
182
186
|
```
|
|
183
187
|
|
|
184
188
|
The rendered HTML includes all Stimulus data attributes:
|
|
@@ -227,7 +231,7 @@ class CardComponent < Vident::ViewComponent::Base
|
|
|
227
231
|
# Property with validation
|
|
228
232
|
prop :size, _Union(:small, :medium, :large), default: :medium
|
|
229
233
|
|
|
230
|
-
# Boolean property (
|
|
234
|
+
# Boolean property (pass `predicate: :public` to also generate a `?` method)
|
|
231
235
|
prop :featured, _Boolean, default: false
|
|
232
236
|
end
|
|
233
237
|
```
|
|
@@ -271,7 +275,7 @@ The `root_element` helper method renders your component's root element with all
|
|
|
271
275
|
```ruby
|
|
272
276
|
# In your component class
|
|
273
277
|
def root_element_classes
|
|
274
|
-
["card", featured
|
|
278
|
+
["card", @featured ? "card-featured" : nil]
|
|
275
279
|
end
|
|
276
280
|
|
|
277
281
|
private
|
|
@@ -362,17 +366,41 @@ class ToggleComponent < Vident::ViewComponent::Base
|
|
|
362
366
|
end
|
|
363
367
|
```
|
|
364
368
|
|
|
365
|
-
**Action modifiers
|
|
369
|
+
**Action modifiers — fluent DSL.** Singular `action(...)` returns a builder you chain with event, modifier, keyboard, and window setters. Kwargs shorthand is equivalent:
|
|
366
370
|
|
|
367
371
|
```ruby
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
#
|
|
372
|
-
|
|
372
|
+
stimulus do
|
|
373
|
+
action(:submit).on(:click).modifier(:once, :prevent) # click:once:prevent->implied#submit
|
|
374
|
+
action(:on_key).on(:keydown).keyboard("ctrl+a") # keydown.ctrl+a->implied#onKey
|
|
375
|
+
action(:on_resize).on(:resize).window # resize@window->implied#onResize
|
|
376
|
+
|
|
377
|
+
# kwargs shorthand — same result:
|
|
378
|
+
action :submit, on: :click, modifier: [:once, :prevent]
|
|
379
|
+
action :on_key, on: :keydown, keyboard: "ctrl+a"
|
|
380
|
+
action :on_resize, on: :resize, window: true
|
|
381
|
+
action :save, on: :click, call_method: :handle_save
|
|
382
|
+
|
|
383
|
+
# conditional inclusion via `.when` / `when:`:
|
|
384
|
+
action(:delete).when { admin? }
|
|
385
|
+
end
|
|
373
386
|
```
|
|
374
387
|
|
|
375
|
-
|
|
388
|
+
Chain methods: `.on`, `.call_method`, `.modifier`, `.keyboard`, `.window`, `.on_controller`, `.when`. Recognised kwargs: `on:`, `call_method:`, `modifier:` (Symbol or Array), `keyboard:`, `window:`, `on_controller:`, `when:`. Unknown kwargs or modifier symbols raise `ArgumentError`.
|
|
389
|
+
|
|
390
|
+
**Controller aliases.** Declare a short alias with `controller "path", as: :sym`, then reference it from action entries via the fluent `.on_controller(:sym)` or the `on_controller: :sym` kwarg:
|
|
391
|
+
|
|
392
|
+
```ruby
|
|
393
|
+
stimulus do
|
|
394
|
+
controller "admin/users", as: :admin
|
|
395
|
+
|
|
396
|
+
action(:save).on(:click).on_controller(:admin) # click->admin--users#save
|
|
397
|
+
action :save, on: :click, on_controller: :admin # same, kwargs form
|
|
398
|
+
end
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Unknown aliases raise `Vident::DeclarationError` at render time.
|
|
402
|
+
|
|
403
|
+
**Legacy Hash form.** Still accepted for compat — `actions({event: :click, method: :submit, options: [:once, :prevent]})` parses the same way. The Hash descriptor is folded directly into `Vident::Stimulus::Action` — pass a Hash and it is parsed in place. Accepted keys: `method:`, `event:`, `controller:`, `options:`, `keyboard:`, `window:`.
|
|
376
404
|
|
|
377
405
|
### Dynamic Values and Classes with Procs
|
|
378
406
|
|
|
@@ -415,7 +443,10 @@ class DynamicComponent < Vident::ViewComponent::Base
|
|
|
415
443
|
end
|
|
416
444
|
```
|
|
417
445
|
|
|
418
|
-
Procs have access to instance variables
|
|
446
|
+
Procs have access to instance variables and component methods. They run at render time (Phlex `before_template` / ViewComponent `before_render`), so they can reach the view context:
|
|
447
|
+
|
|
448
|
+
- **Phlex**: `helpers` is deprecated in phlex-rails. Opt in per Rails helper by including the matching adapter — e.g. `include Phlex::Rails::Helpers::NumberWithPrecision` — and call the helper bare (`number_with_precision(@amount, precision: 2)`) inside the proc. Vident ships a `phlex_helpers :number_with_precision, :t, :l` class macro on `Vident::Phlex::HTML` that does the right include for each name. See [phlex.fun/rails/helpers](https://www.phlex.fun/rails/helpers) for the full list of adapters.
|
|
449
|
+
- **ViewComponent**: call `helpers.<method>` or `view_context.<method>` directly.
|
|
419
450
|
|
|
420
451
|
**Important**: Each proc returns a single value for its corresponding stimulus attribute. If a proc returns an array, that entire array is treated as a single value, not multiple separate values. To provide multiple values for an attribute, use multiple procs or mix procs with static values:
|
|
421
452
|
|
|
@@ -486,14 +517,14 @@ class MyComponent < Vident::ViewComponent::Base
|
|
|
486
517
|
# This would generate: "my-component:dataLoaded"
|
|
487
518
|
puts stimulus_scoped_event(:data_loaded)
|
|
488
519
|
|
|
489
|
-
# For window events, this generates: "my-component:dataLoaded@window"
|
|
520
|
+
# For window events, this generates: :"my-component:dataLoaded@window"
|
|
490
521
|
puts stimulus_scoped_event_on_window(:data_loaded)
|
|
491
522
|
end
|
|
492
523
|
end
|
|
493
524
|
|
|
494
525
|
# Available as both class and instance methods:
|
|
495
|
-
MyComponent.stimulus_scoped_event(:data_loaded) # => "my-component:dataLoaded"
|
|
496
|
-
MyComponent.new.stimulus_scoped_event(:data_loaded) # => "my-component:dataLoaded"
|
|
526
|
+
MyComponent.stimulus_scoped_event(:data_loaded) # => :"my-component:dataLoaded"
|
|
527
|
+
MyComponent.new.stimulus_scoped_event(:data_loaded) # => :"my-component:dataLoaded"
|
|
497
528
|
```
|
|
498
529
|
|
|
499
530
|
This is useful for:
|
|
@@ -529,7 +560,7 @@ class CustomComponent < Vident::ViewComponent::Base
|
|
|
529
560
|
end
|
|
530
561
|
```
|
|
531
562
|
|
|
532
|
-
All stimulus props accept Symbol paths as well as Strings (e.g. `stimulus_controllers: [:custom, :"admin/users"]`). `stimulus_values:` and `stimulus_classes:` additionally accept Array entries (for cross-controller: `[["admin/users", :name, "value"]]`) and pre-built `
|
|
563
|
+
All stimulus props accept Symbol paths as well as Strings (e.g. `stimulus_controllers: [:custom, :"admin/users"]`). `stimulus_values:` and `stimulus_classes:` additionally accept Array entries (for cross-controller: `[["admin/users", :name, "value"]]`) and pre-built `Vident::Stimulus::Value` / `Vident::Stimulus::ClassMap` instances, so you can compose attribute sets outside the component and pass them in.
|
|
533
564
|
|
|
534
565
|
or you can use tag helpers to generate HTML with Stimulus attributes:
|
|
535
566
|
|
data/lib/vident/caching.rb
CHANGED
|
@@ -1,114 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
# Rails fragment caching works by either expecting the cached key object to respond to `cache_key` or for that object
|
|
5
|
-
# to be an array or hash.
|
|
6
|
-
module Caching
|
|
7
|
-
extend ActiveSupport::Concern
|
|
8
|
-
|
|
9
|
-
class_methods do
|
|
10
|
-
def inherited(subclass)
|
|
11
|
-
subclass.instance_variable_set(:@named_cache_key_attributes, @named_cache_key_attributes.clone)
|
|
12
|
-
super
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def with_cache_key(*attrs, name: :_collection)
|
|
16
|
-
# Add view file to cache key
|
|
17
|
-
attrs << :component_modified_time
|
|
18
|
-
attrs << :to_h if respond_to?(:to_h)
|
|
19
|
-
named_cache_key_includes(name, *attrs.uniq)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
attr_reader :named_cache_key_attributes
|
|
23
|
-
|
|
24
|
-
# Components can be used with fragment caching, but you need to be careful! Read on...
|
|
25
|
-
#
|
|
26
|
-
# <% cache component do %>
|
|
27
|
-
# <%= render component %>
|
|
28
|
-
# <% end %>
|
|
29
|
-
#
|
|
30
|
-
# The most important point is that Rails cannot track dependencies on the component itself, so you need to
|
|
31
|
-
# be careful to be explicit on the attributes, and manually specify any sub Viewcomponent dependencies that the
|
|
32
|
-
# component has. The assumption is that the subcomponent takes any attributes from the parent, so the cache key
|
|
33
|
-
# depends on the parent component attributes. Otherwise changes to the parent or sub component views/Ruby class
|
|
34
|
-
# will result in different cache keys too. Of course if you invalidate all cache keys with a modifier on deploy
|
|
35
|
-
# then no need to worry about changing the cache key on component changes, only on attribute/data changes.
|
|
36
|
-
#
|
|
37
|
-
# A big caveat is that the cache key cannot depend on anything related to the view_context of the component (such
|
|
38
|
-
# as `helpers` as the key is created before the rending pipline is invoked (which is when the view_context is set).
|
|
39
|
-
def depends_on(*klasses)
|
|
40
|
-
@component_dependencies ||= []
|
|
41
|
-
@component_dependencies += klasses
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
attr_reader :component_dependencies
|
|
45
|
-
|
|
46
|
-
def component_modified_time
|
|
47
|
-
return @component_modified_time if Rails.env.production? && @component_modified_time
|
|
48
|
-
|
|
49
|
-
raise StandardError, "Must implement cache_component_modified_time" unless respond_to?(:cache_component_modified_time)
|
|
50
|
-
|
|
51
|
-
# FIXME: This could stack overflow if there are circular dependencies
|
|
52
|
-
deps = component_dependencies&.map(&:component_modified_time)&.join("-") || ""
|
|
53
|
-
@component_modified_time = deps + cache_component_modified_time
|
|
54
|
-
end
|
|
3
|
+
require_relative "capabilities/caching"
|
|
55
4
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
define_cache_key_method unless @named_cache_key_attributes
|
|
60
|
-
@named_cache_key_attributes ||= {}
|
|
61
|
-
@named_cache_key_attributes[name] = attrs
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def define_cache_key_method
|
|
65
|
-
# If the presenter defines cache key setup then define the method. Otherwise Rails assumes this
|
|
66
|
-
# will return a valid key if the class will respond to this
|
|
67
|
-
define_method :cache_key do |n = :_collection|
|
|
68
|
-
if defined?(@cache_key)
|
|
69
|
-
return @cache_key[n] if @cache_key.key?(n)
|
|
70
|
-
else
|
|
71
|
-
@cache_key ||= {}
|
|
72
|
-
end
|
|
73
|
-
generate_cache_key(n)
|
|
74
|
-
@cache_key[n]
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Component modified time which is combined with other cache key attributes to generate cache key for an instance
|
|
80
|
-
def component_modified_time = self.class.component_modified_time
|
|
81
|
-
|
|
82
|
-
def cacheable? = respond_to?(:cache_key)
|
|
83
|
-
|
|
84
|
-
def cache_key_modifier = ENV["RAILS_CACHE_ID"]
|
|
85
|
-
|
|
86
|
-
def cache_keys_for_sources(key_attributes)
|
|
87
|
-
sources = key_attributes.flat_map { |n| n.is_a?(Proc) ? instance_eval(&n) : send(n) }
|
|
88
|
-
sources.compact.map do |item|
|
|
89
|
-
next if item == self
|
|
90
|
-
generate_item_cache_key_from(item)
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def generate_item_cache_key_from(item)
|
|
95
|
-
if item.respond_to? :cache_key_with_version
|
|
96
|
-
item.cache_key_with_version
|
|
97
|
-
elsif item.respond_to? :cache_key
|
|
98
|
-
item.cache_key
|
|
99
|
-
elsif item.is_a?(String)
|
|
100
|
-
Digest::SHA1.hexdigest(item)
|
|
101
|
-
else
|
|
102
|
-
Digest::SHA1.hexdigest(Marshal.dump(item))
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def generate_cache_key(index)
|
|
107
|
-
key_attributes = self.class.named_cache_key_attributes[index]
|
|
108
|
-
return nil unless key_attributes
|
|
109
|
-
key = "#{self.class.name}/#{cache_keys_for_sources(key_attributes).join("/")}"
|
|
110
|
-
raise StandardError, "Cache key for key #{key} is blank!" if key.blank?
|
|
111
|
-
@cache_key[index] = cache_key_modifier.present? ? "#{key}/#{cache_key_modifier}" : key
|
|
112
|
-
end
|
|
113
|
-
end
|
|
5
|
+
module Vident
|
|
6
|
+
# Top-level alias for the mixin at `Vident::Capabilities::Caching`.
|
|
7
|
+
Caching = Capabilities::Caching
|
|
114
8
|
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest/sha1"
|
|
4
|
+
|
|
5
|
+
module Vident
|
|
6
|
+
module Capabilities
|
|
7
|
+
# Fragment-caching opt-in. Include into a component to get `cacheable?`,
|
|
8
|
+
# `cache_key`, and the `with_cache_key` / `depends_on` class helpers.
|
|
9
|
+
# `cache_component_modified_time` must be implemented by the adapter base class.
|
|
10
|
+
module Caching
|
|
11
|
+
extend ActiveSupport::Concern
|
|
12
|
+
|
|
13
|
+
class_methods do
|
|
14
|
+
def inherited(subclass)
|
|
15
|
+
subclass.instance_variable_set(:@named_cache_key_attributes, @named_cache_key_attributes&.dup)
|
|
16
|
+
subclass.instance_variable_set(:@component_dependencies, @component_dependencies&.dup)
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def with_cache_key(*attrs, name: :_collection)
|
|
21
|
+
attrs << :component_modified_time
|
|
22
|
+
attrs << :to_h if respond_to?(:to_h)
|
|
23
|
+
named_cache_key_includes(name, *attrs.uniq)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
attr_reader :named_cache_key_attributes
|
|
27
|
+
|
|
28
|
+
def depends_on(*klasses)
|
|
29
|
+
@component_dependencies ||= []
|
|
30
|
+
@component_dependencies += klasses
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
attr_reader :component_dependencies
|
|
34
|
+
|
|
35
|
+
def component_modified_time
|
|
36
|
+
return @component_modified_time if defined?(::Rails) && ::Rails.env.production? && @component_modified_time
|
|
37
|
+
|
|
38
|
+
raise ::Vident::ConfigurationError, "Must implement cache_component_modified_time" unless respond_to?(:cache_component_modified_time)
|
|
39
|
+
|
|
40
|
+
deps = component_dependencies&.map(&:component_modified_time)&.join("-") || ""
|
|
41
|
+
@component_modified_time = deps + cache_component_modified_time
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def named_cache_key_includes(name, *attrs)
|
|
47
|
+
define_cache_key_method unless @named_cache_key_attributes
|
|
48
|
+
@named_cache_key_attributes ||= {}
|
|
49
|
+
@named_cache_key_attributes[name] = attrs
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def define_cache_key_method
|
|
53
|
+
define_method :cache_key do |n = :_collection|
|
|
54
|
+
@cache_key ||= {}
|
|
55
|
+
return @cache_key[n] if @cache_key.key?(n)
|
|
56
|
+
generate_cache_key(n)
|
|
57
|
+
@cache_key[n]
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def component_modified_time = self.class.component_modified_time
|
|
63
|
+
|
|
64
|
+
def cacheable? = respond_to?(:cache_key)
|
|
65
|
+
|
|
66
|
+
def cache_key_modifier = ENV["RAILS_CACHE_ID"]
|
|
67
|
+
|
|
68
|
+
def cache_keys_for_sources(key_attributes)
|
|
69
|
+
sources = key_attributes.flat_map { |n| n.is_a?(Proc) ? instance_eval(&n) : send(n) }
|
|
70
|
+
sources.compact.filter_map { |item| generate_item_cache_key_from(item) unless item == self }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def generate_item_cache_key_from(item)
|
|
74
|
+
if item.respond_to?(:cache_key_with_version)
|
|
75
|
+
item.cache_key_with_version
|
|
76
|
+
elsif item.respond_to?(:cache_key)
|
|
77
|
+
item.cache_key
|
|
78
|
+
elsif item.is_a?(String)
|
|
79
|
+
Digest::SHA1.hexdigest(item)
|
|
80
|
+
else
|
|
81
|
+
Digest::SHA1.hexdigest(Marshal.dump(item))
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def generate_cache_key(index)
|
|
86
|
+
key_attributes = self.class.named_cache_key_attributes[index]
|
|
87
|
+
return nil unless key_attributes
|
|
88
|
+
sources = cache_keys_for_sources(key_attributes)
|
|
89
|
+
if sources.empty?
|
|
90
|
+
raise ::Vident::ConfigurationError,
|
|
91
|
+
"no cache key sources resolved for #{self.class.name} — ensure `with_cache_key` attributes return non-nil values"
|
|
92
|
+
end
|
|
93
|
+
key = "#{self.class.name}/#{sources.join("/")}"
|
|
94
|
+
@cache_key[index] = cache_key_modifier.present? ? "#{key}/#{cache_key_modifier}" : key
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../internals/registry"
|
|
4
|
+
require_relative "../stimulus/collection"
|
|
5
|
+
|
|
6
|
+
module Vident
|
|
7
|
+
module Capabilities
|
|
8
|
+
module ChildElementRendering
|
|
9
|
+
def child_element(
|
|
10
|
+
tag_name,
|
|
11
|
+
stimulus_controllers: nil,
|
|
12
|
+
stimulus_targets: nil,
|
|
13
|
+
stimulus_actions: nil,
|
|
14
|
+
stimulus_outlets: nil,
|
|
15
|
+
stimulus_values: nil,
|
|
16
|
+
stimulus_params: nil,
|
|
17
|
+
stimulus_classes: nil,
|
|
18
|
+
stimulus_controller: nil,
|
|
19
|
+
stimulus_target: nil,
|
|
20
|
+
stimulus_action: nil,
|
|
21
|
+
stimulus_outlet: nil,
|
|
22
|
+
stimulus_value: nil,
|
|
23
|
+
stimulus_param: nil,
|
|
24
|
+
stimulus_class: nil,
|
|
25
|
+
**options,
|
|
26
|
+
&block
|
|
27
|
+
)
|
|
28
|
+
inputs = {
|
|
29
|
+
controllers: [stimulus_controllers, stimulus_controller],
|
|
30
|
+
actions: [stimulus_actions, stimulus_action],
|
|
31
|
+
targets: [stimulus_targets, stimulus_target],
|
|
32
|
+
outlets: [stimulus_outlets, stimulus_outlet],
|
|
33
|
+
values: [stimulus_values, stimulus_value],
|
|
34
|
+
params: [stimulus_params, stimulus_param],
|
|
35
|
+
class_maps: [stimulus_classes, stimulus_class]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
data_attrs = {}
|
|
39
|
+
::Vident::Internals::Registry.each do |kind|
|
|
40
|
+
plural, singular = inputs.fetch(kind.name)
|
|
41
|
+
child_element_check_plural!(plural, singular, kind)
|
|
42
|
+
coll = child_element_build_collection(kind, plural, singular)
|
|
43
|
+
data_attrs.merge!(coll.to_h) unless coll.empty?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
generate_child_element(tag_name, data_attrs, options, &block)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def generate_child_element(tag_name, stimulus_data_attributes, options, &block)
|
|
50
|
+
raise NoMethodError, "adapter must implement generate_child_element"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def child_element_check_plural!(plural, singular, kind)
|
|
56
|
+
if plural && singular
|
|
57
|
+
raise ArgumentError,
|
|
58
|
+
"'stimulus_#{kind.plural_name}:' and 'stimulus_#{kind.singular_name}:' " \
|
|
59
|
+
"are mutually exclusive — pass one or the other."
|
|
60
|
+
end
|
|
61
|
+
return if plural.nil?
|
|
62
|
+
return if plural.is_a?(Enumerable) && !plural.is_a?(Hash)
|
|
63
|
+
return if plural.is_a?(Hash) && kind.keyed?
|
|
64
|
+
raise ArgumentError,
|
|
65
|
+
"'stimulus_#{kind.plural_name}:' must be an enumerable. " \
|
|
66
|
+
"Did you mean 'stimulus_#{kind.singular_name}:'?"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Exactly one of `plural` / `singular` is non-nil; guard above
|
|
70
|
+
# rejects both-set.
|
|
71
|
+
def child_element_build_collection(kind, plural, singular)
|
|
72
|
+
plural_method = :"stimulus_#{kind.plural_name}"
|
|
73
|
+
singular_method = :"stimulus_#{kind.singular_name}"
|
|
74
|
+
|
|
75
|
+
if plural
|
|
76
|
+
if kind.keyed? && plural.is_a?(Hash)
|
|
77
|
+
send(plural_method, plural)
|
|
78
|
+
elsif plural.is_a?(Array)
|
|
79
|
+
send(plural_method, *plural)
|
|
80
|
+
else
|
|
81
|
+
send(plural_method, *Array.wrap(plural))
|
|
82
|
+
end
|
|
83
|
+
elsif singular
|
|
84
|
+
coll_items = [send(singular_method, *Array.wrap(singular))]
|
|
85
|
+
::Vident::Stimulus::Collection.new(kind: kind, items: coll_items)
|
|
86
|
+
else
|
|
87
|
+
::Vident::Stimulus::Collection.new(kind: kind, items: [])
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../internals/class_list_builder"
|
|
4
|
+
|
|
5
|
+
module Vident
|
|
6
|
+
module Capabilities
|
|
7
|
+
module ClassListBuilding
|
|
8
|
+
def class_list_for_stimulus_classes(*names)
|
|
9
|
+
resolve_stimulus_attributes_at_render_time
|
|
10
|
+
plan = seal_draft
|
|
11
|
+
maps = plan.class_maps
|
|
12
|
+
return "" if maps.empty? || names.empty?
|
|
13
|
+
|
|
14
|
+
result = ::Vident::Internals::ClassListBuilder.call(
|
|
15
|
+
stimulus_classes: maps,
|
|
16
|
+
stimulus_class_names: names,
|
|
17
|
+
tailwind_merger: tailwind_merger
|
|
18
|
+
)
|
|
19
|
+
result || ""
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../types"
|
|
4
|
+
|
|
5
|
+
module Vident
|
|
6
|
+
module Capabilities
|
|
7
|
+
module Declarable
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
extend Literal::Properties
|
|
12
|
+
|
|
13
|
+
prop :element_tag, Symbol, default: :div
|
|
14
|
+
prop :id, _Nilable(String)
|
|
15
|
+
prop :classes, _Union(String, _Array(String)), default: -> { [] }
|
|
16
|
+
prop :html_options, Hash, default: -> { {} }
|
|
17
|
+
|
|
18
|
+
# `stimulus_controllers:` APPENDS to the implied controller (which
|
|
19
|
+
# seeds first unless `no_stimulus_controller`).
|
|
20
|
+
prop :stimulus_controllers, ::Vident::Types::StimulusControllers, default: -> { [] }
|
|
21
|
+
prop :stimulus_actions, ::Vident::Types::StimulusActions, default: -> { [] }
|
|
22
|
+
prop :stimulus_targets, ::Vident::Types::StimulusTargets, default: -> { [] }
|
|
23
|
+
prop :stimulus_outlets, ::Vident::Types::StimulusOutlets, default: -> { [] }
|
|
24
|
+
prop :stimulus_outlet_host, _Nilable(::Vident::Component)
|
|
25
|
+
prop :stimulus_values, ::Vident::Types::StimulusValues, default: -> { {} }
|
|
26
|
+
prop :stimulus_params, ::Vident::Types::StimulusParams, default: -> { {} }
|
|
27
|
+
prop :stimulus_classes, ::Vident::Types::StimulusClasses, default: -> { {} }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class_methods do
|
|
31
|
+
def prop_names
|
|
32
|
+
literal_properties.properties_index.keys.map(&:to_sym)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def prop_names = self.class.prop_names
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../stimulus/controller"
|
|
4
|
+
require_relative "../stable_id"
|
|
5
|
+
|
|
6
|
+
module Vident
|
|
7
|
+
module Capabilities
|
|
8
|
+
module Identifiable
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
class_methods do
|
|
12
|
+
def stimulus_identifier_path
|
|
13
|
+
name&.underscore || "anonymous_component"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def stimulus_identifier
|
|
17
|
+
stimulus_identifier_path.split("/").map(&:dasherize).join("--")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def component_name
|
|
21
|
+
@component_name ||= stimulus_identifier
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def component_name = self.class.component_name
|
|
26
|
+
|
|
27
|
+
def stimulus_identifier = self.class.stimulus_identifier
|
|
28
|
+
|
|
29
|
+
private def default_controller_path = self.class.stimulus_identifier_path
|
|
30
|
+
|
|
31
|
+
# `.presence` is intentional — blank string falls through to auto-generation.
|
|
32
|
+
def id
|
|
33
|
+
@id.presence || random_id
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def random_id
|
|
37
|
+
@__vident_auto_id ||= "#{component_name}-#{::Vident::StableId.next_id_in_sequence}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def outlet_id
|
|
41
|
+
@outlet_id ||= [stimulus_identifier, "##{id}"]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def implied_controller
|
|
47
|
+
@__vident_implied_controller ||= ::Vident::Stimulus::Controller.new(
|
|
48
|
+
path: self.class.stimulus_identifier_path,
|
|
49
|
+
name: self.class.stimulus_identifier
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Vident
|
|
4
|
+
module Capabilities
|
|
5
|
+
module Inspectable
|
|
6
|
+
# Matches the Data.define#with convention introduced in Ruby 3.2.
|
|
7
|
+
def with(overrides = {})
|
|
8
|
+
self.class.new(**to_h.merge(overrides))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def inspect(klass_name = "Component")
|
|
12
|
+
attr_text = to_h.map { |k, v| "#{k}=#{v.inspect}" }.join(", ")
|
|
13
|
+
"#<#{self.class.name}<Vident::#{klass_name}> #{attr_text}>"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|