vident 1.0.2 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +45 -17
  4. data/lib/vident/caching.rb +4 -110
  5. data/lib/vident/capabilities/caching.rb +98 -0
  6. data/lib/vident/capabilities/child_element_rendering.rb +92 -0
  7. data/lib/vident/capabilities/class_list_building.rb +23 -0
  8. data/lib/vident/capabilities/declarable.rb +39 -0
  9. data/lib/vident/capabilities/identifiable.rb +54 -0
  10. data/lib/vident/capabilities/inspectable.rb +17 -0
  11. data/lib/vident/capabilities/root_element_rendering.rb +31 -0
  12. data/lib/vident/capabilities/stimulus_data_emitting.rb +98 -0
  13. data/lib/vident/capabilities/stimulus_declaring.rb +79 -0
  14. data/lib/vident/capabilities/stimulus_draft.rb +51 -0
  15. data/lib/vident/capabilities/stimulus_mutation.rb +60 -0
  16. data/lib/vident/capabilities/stimulus_parsing.rb +144 -0
  17. data/lib/vident/capabilities/tailwind.rb +18 -0
  18. data/lib/vident/component.rb +14 -76
  19. data/lib/vident/engine.rb +6 -5
  20. data/lib/vident/error.rb +16 -0
  21. data/lib/{vident2 → vident}/internals/action_builder.rb +18 -22
  22. data/lib/vident/internals/attribute_writer.rb +17 -0
  23. data/lib/{vident2 → vident}/internals/class_list_builder.rb +5 -22
  24. data/lib/vident/internals/declaration.rb +13 -0
  25. data/lib/{vident2 → vident}/internals/declarations.rb +6 -18
  26. data/lib/{vident2 → vident}/internals/draft.rb +3 -16
  27. data/lib/{vident2 → vident}/internals/dsl.rb +6 -32
  28. data/lib/vident/internals/plan.rb +9 -0
  29. data/lib/vident/internals/registry.rb +37 -0
  30. data/lib/{vident2 → vident}/internals/resolver.rb +101 -91
  31. data/lib/{vident2 → vident}/internals/target_builder.rb +1 -7
  32. data/lib/vident/stable_id.rb +3 -3
  33. data/lib/{vident2 → vident}/stimulus/action.rb +11 -24
  34. data/lib/vident/stimulus/base.rb +26 -0
  35. data/lib/{vident2 → vident}/stimulus/class_map.rb +6 -18
  36. data/lib/{vident2 → vident}/stimulus/collection.rb +6 -8
  37. data/lib/vident/stimulus/combinable.rb +30 -0
  38. data/lib/vident/stimulus/controller.rb +45 -0
  39. data/lib/vident/stimulus/naming.rb +9 -9
  40. data/lib/vident/stimulus/null.rb +7 -0
  41. data/lib/{vident2 → vident}/stimulus/outlet.rb +12 -32
  42. data/lib/{vident2 → vident}/stimulus/param.rb +5 -11
  43. data/lib/{vident2 → vident}/stimulus/target.rb +5 -14
  44. data/lib/vident/stimulus/value.rb +57 -0
  45. data/lib/vident/stimulus_null.rb +4 -8
  46. data/lib/vident/tailwind.rb +4 -17
  47. data/lib/vident/types.rb +28 -0
  48. data/lib/vident/version.rb +1 -6
  49. data/lib/vident.rb +46 -36
  50. data/skills/vident/SKILL.md +122 -19
  51. data/skills/vident/api-reference.md +259 -115
  52. data/skills/vident/examples.md +23 -10
  53. metadata +38 -60
  54. data/lib/vident/child_element_helper.rb +0 -64
  55. data/lib/vident/class_list_builder.rb +0 -112
  56. data/lib/vident/component_attribute_resolver.rb +0 -106
  57. data/lib/vident/component_class_lists.rb +0 -37
  58. data/lib/vident/stimulus/primitive.rb +0 -38
  59. data/lib/vident/stimulus.rb +0 -31
  60. data/lib/vident/stimulus_action.rb +0 -133
  61. data/lib/vident/stimulus_action_collection.rb +0 -11
  62. data/lib/vident/stimulus_attribute_base.rb +0 -67
  63. data/lib/vident/stimulus_attributes.rb +0 -129
  64. data/lib/vident/stimulus_builder.rb +0 -136
  65. data/lib/vident/stimulus_class.rb +0 -59
  66. data/lib/vident/stimulus_class_collection.rb +0 -11
  67. data/lib/vident/stimulus_collection_base.rb +0 -51
  68. data/lib/vident/stimulus_component.rb +0 -75
  69. data/lib/vident/stimulus_controller.rb +0 -41
  70. data/lib/vident/stimulus_controller_collection.rb +0 -14
  71. data/lib/vident/stimulus_data_attribute_builder.rb +0 -32
  72. data/lib/vident/stimulus_helper.rb +0 -66
  73. data/lib/vident/stimulus_outlet.rb +0 -90
  74. data/lib/vident/stimulus_outlet_collection.rb +0 -11
  75. data/lib/vident/stimulus_param.rb +0 -42
  76. data/lib/vident/stimulus_param_collection.rb +0 -11
  77. data/lib/vident/stimulus_target.rb +0 -47
  78. data/lib/vident/stimulus_target_collection.rb +0 -18
  79. data/lib/vident/stimulus_value.rb +0 -39
  80. data/lib/vident/stimulus_value_collection.rb +0 -11
  81. data/lib/vident2/caching.rb +0 -93
  82. data/lib/vident2/component.rb +0 -538
  83. data/lib/vident2/engine.rb +0 -18
  84. data/lib/vident2/error.rb +0 -30
  85. data/lib/vident2/internals/attribute_writer.rb +0 -22
  86. data/lib/vident2/internals/declaration.rb +0 -17
  87. data/lib/vident2/internals/plan.rb +0 -12
  88. data/lib/vident2/internals/registry.rb +0 -41
  89. data/lib/vident2/phlex/html.rb +0 -84
  90. data/lib/vident2/phlex.rb +0 -9
  91. data/lib/vident2/stimulus/controller.rb +0 -59
  92. data/lib/vident2/stimulus/naming.rb +0 -26
  93. data/lib/vident2/stimulus/null.rb +0 -16
  94. data/lib/vident2/stimulus/value.rb +0 -77
  95. data/lib/vident2/tailwind.rb +0 -19
  96. data/lib/vident2/version.rb +0 -5
  97. data/lib/vident2/view_component/base.rb +0 -124
  98. data/lib/vident2/view_component.rb +0 -9
  99. data/lib/vident2.rb +0 -50
@@ -54,13 +54,64 @@ class Foo::BarComponent < Vident::ViewComponent::Base
54
54
  end
55
55
  ```
56
56
 
57
+ **Subclass re-enables the controller.** `no_stimulus_controller` is inherited by subclasses. A subclass that has its own paired JS controller calls `has_stimulus_controller` to flip the flag back on:
58
+
59
+ ```ruby
60
+ class ApplicationComponent < Vident::Phlex::HTML
61
+ no_stimulus_controller # shell — no paired JS
62
+ end
63
+
64
+ class DropdownComponent < ApplicationComponent
65
+ has_stimulus_controller # emits data-controller="dropdown-component"
66
+ stimulus { actions :toggle }
67
+ end
68
+ ```
69
+
57
70
  Cross-controller references elsewhere (actions/targets/values/classes/outlets) use the `"path/to/controller"` **string** form — Vident stimulizes it for you.
58
71
 
59
72
  ### 1.2 Actions
60
73
 
61
74
  Stimulus descriptor: `event->controller#method`, with optional modifiers (`:once`, `:prevent`, `keydown.ctrl+a`, `@window`, etc.). Stimulus encodes them as space-separated tokens in `data-action="..."`.
62
75
 
63
- Vident `actions` DSL entries (all of these work inside `stimulus do ... end`):
76
+ **Primary form the fluent `action(...)` builder.** Singular `action` returns a builder that reads left-to-right:
77
+
78
+ ```ruby
79
+ stimulus do
80
+ action :click # implied#click
81
+ action(:submit).on(:click) # click->implied#submit
82
+ action(:save).on(:click).modifier(:prevent, :stop) # click:prevent:stop->implied#save
83
+ action(:escape).on(:keydown).keyboard("esc").window # keydown.esc@window->implied#escape
84
+ action(:delete).when { admin? } # conditional — `.when` takes a predicate proc
85
+ end
86
+ ```
87
+
88
+ Chain methods: `.on(event)`, `.call_method(name)` (override), `.modifier(*opts)`, `.keyboard(str)`, `.window`, `.on_controller(alias_sym)`, `.when { predicate }`. Each returns `self`.
89
+
90
+ **Kwargs shorthand.** Equivalent to the fluent chain — pick whichever reads better:
91
+
92
+ ```ruby
93
+ action :save, on: :click, modifier: [:prevent, :stop]
94
+ action :escape, on: :keydown, keyboard: "esc", window: true
95
+ action :delete, when: -> { admin? }
96
+ action :save, on: :click, call_method: :handle_save
97
+ ```
98
+
99
+ Recognised keys: `on:`, `call_method:`, `modifier:` (Symbol or Array), `keyboard:`, `window:`, `on_controller:`, `when:`. Anything else raises `ArgumentError`.
100
+
101
+ **Controller aliases.** Declare a short name for a cross-controller path with `controller "path", as: :alias`, then reference it from action entries:
102
+
103
+ ```ruby
104
+ stimulus do
105
+ controller "admin/users", as: :admin
106
+
107
+ action(:save).on(:click).on_controller(:admin) # click->admin--users#save
108
+ action :save, on: :click, on_controller: :admin # same, kwargs form
109
+ end
110
+ ```
111
+
112
+ The alias resolves at render time — unknown aliases raise `Vident::DeclarationError`. Also works for runtime inputs: `stimulus_actions: [{method: :save, controller: :admin}]` resolves against the same declared map.
113
+
114
+ **Legacy plural form.** Still accepted for compat — `actions(*entries)` accepts:
64
115
 
65
116
  | Ruby | Emits |
66
117
  | ------------------------------------------- | -------------------------------------------------- |
@@ -69,10 +120,9 @@ Vident `actions` DSL entries (all of these work inside `stimulus do ... end`):
69
120
  | `[:click, "other/ctrl", :my_thing]` | `click->other--ctrl#myThing` |
70
121
  | `"click->other--ctrl#myThing"` | pass-through, parsed into its parts |
71
122
  | `{event: :click, method: :submit, options: [:once, :prevent]}` | `click:once:prevent->implied#submit` |
72
- | `Vident::StimulusAction::Descriptor.new(event: :click, method: :submit, options: [:once])` | same — typed data object, Hash is sugar |
73
123
  | `-> { [:click, :my_thing] if @editable }` | proc, evaluated in component instance; `nil`/`false` returns drop the entry |
74
124
 
75
- **Modifiers via the Hash / Descriptor form.** Accepted keys on the hash (and the `Descriptor` data class):
125
+ **Modifiers via the Hash form.** Accepted keys:
76
126
 
77
127
  | Key | Type | Emits |
78
128
  | ------------ | ----------------- | ---------------------------------------- |
@@ -83,7 +133,7 @@ Vident `actions` DSL entries (all of these work inside `stimulus do ... end`):
83
133
  | `keyboard:` | String like `"ctrl+a"` | `.ctrl+a` suffix on event filter |
84
134
  | `window:` | Boolean | `@window` suffix on event |
85
135
 
86
- Unknown option symbols raise `ArgumentError`. Use the Hash form for the common case; use `Vident::StimulusAction::Descriptor.new(...)` when you want a typed, passable value object (reusable across components, shared helpers).
136
+ Unknown option symbols raise `ArgumentError`. The Hash descriptor is parsed directly into `Vident::Stimulus::Action` there is no separate `Descriptor` class in V2.
87
137
 
88
138
  ```ruby
89
139
  actions({event: :keydown, method: :on_escape, keyboard: "esc", options: [:prevent]})
@@ -247,7 +297,7 @@ stimulus_outlets: [
247
297
  ]
248
298
  ```
249
299
 
250
- **(c) Child self-registers on a host via `stimulus_outlet_host:`.** Every Vident component inherits a `stimulus_outlet_host` prop. Passing a parent component at render time calls `host.add_stimulus_outlets(self)` in `prepare_stimulus_collections`, so the host's root gets the outlet attribute without enumerating children in its DSL:
300
+ **(c) Child self-registers on a host via `stimulus_outlet_host:`.** Every Vident component inherits a `stimulus_outlet_host` prop. Passing a parent component at render time calls `host.add_stimulus_outlets(self)` in `after_initialize` (via `Vident::Capabilities::StimulusDraft`), so the host's root gets the outlet attribute without enumerating children in its DSL:
251
301
 
252
302
  ```ruby
253
303
  render PageComponent.new do |page|
@@ -329,7 +379,7 @@ prop :count, Integer, default: 0 # with default
329
379
  prop :url, _Nilable(String) # optional / nilable
330
380
  prop :variant, _Union(:primary, :secondary), default: :primary
331
381
  prop :items, _Array(Hash), default: -> { [] } # callable defaults must be lambdas
332
- prop :open, _Boolean, default: false # generates an `open?` predicate
382
+ prop :open, _Boolean, default: false # pass `predicate: :public` to also get an `open?` method
333
383
  ```
334
384
 
335
385
  Props become `@ivar`s at init time. To also expose a reader method, declare the prop with `reader: :public`.
@@ -343,9 +393,9 @@ From `Vident::Component`:
343
393
  - `classes` — `String | Array(String)`. Appended to the root element's `class=`.
344
394
  - `html_options` — `Hash`. Merged onto the root element; highest precedence.
345
395
 
346
- From `Vident::StimulusComponent`:
396
+ From `Vident::Component`:
347
397
 
348
- - `stimulus_controllers` — `Array(String | Symbol | StimulusController | Collection)`. Defaults to `[default_controller_path]` unless `no_stimulus_controller` is declared.
398
+ - `stimulus_controllers` — `Array(String | Symbol | Vident::Stimulus::Controller)`. Defaults to `[default_controller_path]` unless `no_stimulus_controller` is declared.
349
399
  - `stimulus_actions`, `stimulus_targets`, `stimulus_values`, `stimulus_classes`, `stimulus_outlets` — Array / Hash props matching the shapes described in section 1.
350
400
  - `stimulus_outlet_host` — optional `Vident::Component`; activates child→host outlet self-registration.
351
401
 
@@ -413,13 +463,45 @@ When handwriting HTML inside ERB instead of using `child_element`, emit just the
413
463
  <div <%= component.as_stimulus_values(%i[count label]) %>></div>
414
464
  ```
415
465
 
416
- Plural (`as_stimulus_targets`, `as_stimulus_actions`, `as_stimulus_values`, `as_stimulus_classes`, `as_stimulus_outlets`, `as_stimulus_controllers`) and singular variants exist for every attribute kind. These helpers are defined on `Vident::ViewComponent::Base`; for Phlex, use `child_element` or compose directly.
466
+ Plural (`as_stimulus_targets`, `as_stimulus_actions`, `as_stimulus_values`, `as_stimulus_params`, `as_stimulus_classes`, `as_stimulus_outlets`, `as_stimulus_controllers`) and singular variants (`as_stimulus_target`, `as_stimulus_action`, `as_stimulus_value`, `as_stimulus_param`, `as_stimulus_class`, `as_stimulus_outlet`, `as_stimulus_controller`) exist for every attribute kind. These helpers are defined on `Vident::ViewComponent::Base`; for Phlex, use `child_element` or compose directly.
467
+
468
+ ### Class-level Stimulus builders (no instance needed)
469
+
470
+ When you need a Stimulus value without a component instance (Turbo-Stream partials, JSON endpoints, test selectors), call the builders on the class:
471
+
472
+ ```ruby
473
+ ButtonComponent.stimulus_target(:submit) # Vident::Stimulus::Target
474
+ ButtonComponent.stimulus_action(:click, :handle) # click->implied#handle
475
+ ButtonComponent.stimulus_value(:count, 0)
476
+ ButtonComponent.stimulus_param(:item_id, 42)
477
+ ButtonComponent.stimulus_class(:loading, "opacity-50")
478
+ ButtonComponent.stimulus_outlet(:modal, ".js-modal") # selector required
479
+ ButtonComponent.stimulus_controller # the implied controller
480
+ ```
481
+
482
+ Returns a `Vident::Stimulus::*` value object with the same `#to_h` / `#to_data_pair` as the instance equivalents — splat `.to_h` into a tag's HTML options. Two restrictions at class level: **outlets require an explicit selector** (no `component_id` to auto-scope), and **cross-controller forms are rejected** (call `Vident::Stimulus::Target.parse(...)` directly for those).
483
+
484
+ ### Rendering outside `root_element(...)`
485
+
486
+ For components that build their root tag via a third-party helper (e.g. `inline_svg_tag`), two instance methods return what `root_element(...)` would emit:
487
+
488
+ - `root_element_class_list(extra_classes = nil)` → `String` with the full class cascade (`component_name`, `root_element_classes`, `@classes` prop, `html_options[:class]`, extras) plus Tailwind-merging.
489
+ - `root_element_data_attributes` → `Hash` (Symbol keys) with the full `data-*` set (controller, action, target, value, param, class, outlet) from the sealed Plan.
490
+
491
+ ```ruby
492
+ def view_template
493
+ svg("data-src" => helpers.image_path(file_name),
494
+ id: @id,
495
+ class: root_element_class_list,
496
+ data: root_element_data_attributes) {}
497
+ end
498
+ ```
417
499
 
418
500
  ---
419
501
 
420
502
  ## 3. `stimulus do ... end` block
421
503
 
422
- Opens a `Vident::StimulusBuilder` instance scoped to the class. It supports `actions`, `targets`, `values`, `values_from_props`, `classes`, `outlets`. Multiple `stimulus do` blocks on the same class are merged; a subclass's block is merged with its superclass's (subclass entries appended, values/classes/outlets merged by key, subclass wins on conflicts).
504
+ Opens a `Vident::Internals::DSL` instance scoped to the class. It supports `actions`, `targets`, `values`, `values_from_props`, `classes`, `outlets`. Multiple `stimulus do` blocks on the same class are merged; a subclass's block is merged with its superclass's (subclass entries appended, values/classes/outlets merged by key, subclass wins on conflicts).
423
505
 
424
506
  Procs passed anywhere in the DSL are evaluated via `instance_exec` on the **component instance** at render time (Phlex `before_template` / ViewComponent `before_render`), so they see `@ivars`, public/private instance methods, and the view context.
425
507
 
@@ -495,8 +577,30 @@ Vident::StableId.with_sequence_generator(seed: "some-unique-key") { render ... }
495
577
 
496
578
  - **`after_component_initialize`** — override in your component; runs after props are assigned and Vident has prepared its stimulus collections. Don't override `after_initialize` unless you `super` — Literal calls it to wire everything up.
497
579
  - **`component_name` / `stimulus_identifier`** — class method and instance method; the kebab-case/`--`-separated identifier. Used for outlet auto-selectors, scoped event names, and the default class on the root.
498
- - **Caching** (`include Vident::Caching` + `with_cache_key :attr1, :attr2`) — declares attributes that feed `cache_key`. Combined with a template mtime so edits bust the cache. `depends_on(OtherComponent, …)` chains subcomponent mtimes into the key.
499
- - **`clone(overrides = {})`** returns a new instance with merged props.
580
+ - **Caching** (`include Vident::Caching` + `with_cache_key :attr1, :attr2`) — declares attributes that feed `cache_key`. Combined with a template mtime so edits bust the cache. `depends_on(OtherComponent, …)` chains subcomponent mtimes into the key. Two separable concerns:
581
+ - **Computing the key** — `component.cache_key` is always available once `with_cache_key` is declared. Use it for etags, conditional rendering, explicit `Rails.cache.fetch(key) { ... }` at the call site, or any other place you need a content-addressed identifier.
582
+ - **Fragment-caching the render** — `cache_component(*extra_keys, &block)` wraps the block with Rails.cache using the Vident-computed key. Works on both adapters. For Phlex, call it inside `view_template` (delegates to Phlex's `cache(...)`); for ViewComponent, call inside `call` (uses `Rails.cache.fetch` + `capture`). Sidecar ERB templates can just write `<% cache cache_key do %> ... <% end %>` directly.
583
+
584
+ ```ruby
585
+ # Phlex
586
+ def view_template
587
+ cache_component do
588
+ root_element { ... }
589
+ end
590
+ end
591
+
592
+ # ViewComponent (def call form)
593
+ def call
594
+ cache_component { root_element { ... } }
595
+ end
596
+
597
+ # ViewComponent (sidecar ERB) — use the Rails helper with component.cache_key
598
+ # <% cache cache_key do %>
599
+ # <%= root_element do %>...<% end %>
600
+ # <% end %>
601
+ ```
602
+ Calling `cache_component` on a non-cacheable component (no `with_cache_key`) raises `Vident::ConfigurationError`.
603
+ - **`with(overrides = {})`** — returns a new instance with merged props. (`clone` is a backward-compat alias.)
500
604
  - **Phlex tag safety** — `Vident::Phlex::HTML` validates every `child_element` tag name against a whitelist; passing an unknown tag raises.
501
605
 
502
606
  ---
@@ -625,13 +729,12 @@ end
625
729
 
626
730
  For the exhaustive public-API listing (every method signature, argument shape, and raise-condition, verified against current code), see [`api-reference.md`](api-reference.md). The files below are useful when you need to read the implementation itself.
627
731
 
628
- - `lib/vident/stimulus_builder.rb` — DSL evaluator.
629
- - `lib/vident/stimulus_attributes.rb` — parser for every `stimulus_*` input shape + `as_stimulus_*` helpers' backing.
630
- - `lib/vident/stimulus_{action,target,value,outlet,class,controller}.rb` — value objects; read `parse_arguments` to learn the argument shapes.
631
- - `lib/vident/child_element_helper.rb` — `child_element` kwargs and validation.
632
- - `lib/vident/component_attribute_resolver.rb` — how DSL, props, and `root_element_attributes` compose at render time.
633
- - `lib/vident/component_class_lists.rb` — `class_list_for_stimulus_classes` / `render_classes`.
732
+ - `lib/vident/component.rb` — composition root; includes all capabilities in dependency order.
634
733
  - `lib/vident/stable_id.rb` — the StableId strategy system.
635
734
  - `lib/vident/stimulus_null.rb` — the StimulusNull sentinel.
636
- - `lib/vident/view_component/base.rb` / `lib/vident/phlex/html.rb` framework-specific `root_element` / `child_element` backings and (ViewComponent only) `as_stimulus_*` helpers.
735
+ - `lib/vident/stimulus/` value classes: `Action`, `Target`, `Controller`, `Outlet`, `Value`, `Param`, `ClassMap`, `Collection`, `Null`, `Naming`.
736
+ - `lib/vident/capabilities/` — focused capability mixins: `Tailwind`, `Caching`, `Declarable`, `Identifiable`, `StimulusDeclaring`, `StimulusParsing`, `StimulusMutation`, `StimulusDraft`, `StimulusDataEmitting`, `ClassListBuilding`, `RootElementRendering`, `ChildElementRendering`, `Inspectable`.
737
+ - `lib/vident/internals/` — internal DSL/resolver plumbing: `Registry`, `Declaration`, `Declarations`, `DSL`, `Draft`, `Plan`, `Resolver`, `AttributeWriter`, `ClassListBuilder`, `ActionBuilder`, `TargetBuilder`.
738
+ - `lib/vident/phlex/html.rb` — Phlex adapter (`root_element`, `child_element`, tag whitelist).
739
+ - `lib/vident/view_component/base.rb` — ViewComponent adapter (`root_element`, `child_element`, `as_stimulus_*` helpers).
637
740
  - `test/dummy/app/components/dashboard/` — canonical multi-component example (outlets, scoped events, `StimulusNull`, dynamic classes, `values_from_props`, `class_list_for_stimulus_classes`, full JS side).