vident 1.0.0 → 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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +4 -1
  4. data/lib/vident/component_attribute_resolver.rb +27 -8
  5. data/lib/vident/component_class_lists.rb +7 -1
  6. data/lib/vident/stimulus_builder.rb +28 -11
  7. data/lib/vident/stimulus_helper.rb +4 -4
  8. data/lib/vident/version.rb +1 -1
  9. data/lib/vident2/caching.rb +93 -0
  10. data/lib/vident2/component.rb +538 -0
  11. data/lib/vident2/engine.rb +18 -0
  12. data/lib/vident2/error.rb +30 -0
  13. data/lib/vident2/internals/action_builder.rb +101 -0
  14. data/lib/vident2/internals/attribute_writer.rb +22 -0
  15. data/lib/vident2/internals/class_list_builder.rb +79 -0
  16. data/lib/vident2/internals/declaration.rb +17 -0
  17. data/lib/vident2/internals/declarations.rb +76 -0
  18. data/lib/vident2/internals/draft.rb +60 -0
  19. data/lib/vident2/internals/dsl.rb +198 -0
  20. data/lib/vident2/internals/plan.rb +12 -0
  21. data/lib/vident2/internals/registry.rb +41 -0
  22. data/lib/vident2/internals/resolver.rb +306 -0
  23. data/lib/vident2/internals/target_builder.rb +29 -0
  24. data/lib/vident2/phlex/html.rb +84 -0
  25. data/lib/vident2/phlex.rb +9 -0
  26. data/lib/vident2/stimulus/action.rb +140 -0
  27. data/lib/vident2/stimulus/class_map.rb +69 -0
  28. data/lib/vident2/stimulus/collection.rb +42 -0
  29. data/lib/vident2/stimulus/controller.rb +59 -0
  30. data/lib/vident2/stimulus/naming.rb +26 -0
  31. data/lib/vident2/stimulus/null.rb +16 -0
  32. data/lib/vident2/stimulus/outlet.rb +113 -0
  33. data/lib/vident2/stimulus/param.rb +62 -0
  34. data/lib/vident2/stimulus/target.rb +57 -0
  35. data/lib/vident2/stimulus/value.rb +77 -0
  36. data/lib/vident2/tailwind.rb +19 -0
  37. data/lib/vident2/version.rb +5 -0
  38. data/lib/vident2/view_component/base.rb +124 -0
  39. data/lib/vident2/view_component.rb +9 -0
  40. data/lib/vident2.rb +50 -0
  41. data/skills/vident/SKILL.md +11 -2
  42. data/skills/vident/api-reference.md +518 -0
  43. data/skills/vident/examples.md +492 -0
  44. metadata +35 -1
@@ -0,0 +1,492 @@
1
+ # Vident worked examples
2
+
3
+ End-to-end walkthroughs. Every example here runs against the public API verified in
4
+ `test/public_api_spec/` and the dummy app under `test/dummy/app/components/`. If a pattern
5
+ isn't shown below but is referenced in SKILL.md, it almost certainly shows up in
6
+ `test/dummy/app/components/dashboard/`.
7
+
8
+ The examples are grouped by shape:
9
+
10
+ 1. [Dashboard: outlets + scoped events + StimulusNull](#1-dashboard-outlets--scoped-events--stimulusnull) (Phlex)
11
+ 2. [Greeter with slot trigger: parent-child Stimulus wiring](#2-greeter-with-slot-trigger) (ViewComponent + Phlex)
12
+ 3. [ERB syntax: three ways to emit data attributes in a template](#3-erb-three-ways-to-emit-data-attributes)
13
+ 4. [Avatar: conditional root tag + class-list precedence](#4-avatar-conditional-root-tag--class-list-precedence) (Phlex)
14
+ 5. [Stimulus params on sibling buttons sharing one handler](#5-stimulus-params-on-sibling-buttons)
15
+
16
+ ---
17
+
18
+ ## 1. Dashboard: outlets + scoped events + StimulusNull
19
+
20
+ A page hosts many release cards; cards are filterable via a filter bar; selecting a card
21
+ opens a detail panel; promoting/cancelling a card fires a toast. Full source in
22
+ `test/dummy/app/components/dashboard/`.
23
+
24
+ ### Page (host of card outlets)
25
+
26
+ ```ruby
27
+ module Dashboard
28
+ class PageComponent < ApplicationComponent
29
+ prop :releases, _Array(Hash), default: -> { [] }
30
+ prop :active_filter, _Union(:all, :pending, :deployed, :failed), default: :all
31
+
32
+ stimulus do
33
+ values active_filter: -> { @active_filter.to_s },
34
+ count: -> { @releases.size }
35
+
36
+ # Listen to a scoped `filterChanged` event dispatched on window by FilterBar.
37
+ # Ruby side: reference the DISPATCHER's class.
38
+ actions -> { [FilterBarComponent.stimulus_scoped_event_on_window(:filter_changed), :handle_filter_changed] }
39
+ end
40
+
41
+ def view_template
42
+ root_element(class: "space-y-6") do |page|
43
+ render FilterBarComponent.new(active_filter: @active_filter, total: @releases.size)
44
+
45
+ div(class: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4") do
46
+ @releases.each do |release|
47
+ # `stimulus_outlet_host: page` is the child-registers-with-host hook:
48
+ # each card's initialize calls `page.add_stimulus_outlets(self)`, which
49
+ # writes a `data-dashboard--page-component-dashboard--release-card-component-outlet`
50
+ # onto the page root. No need to list cards in the page's `stimulus do`.
51
+ render ReleaseCardComponent.new(**release, stimulus_outlet_host: page)
52
+ end
53
+ end
54
+
55
+ render DetailPanelComponent.new
56
+ render ToastComponent.new
57
+ end
58
+ end
59
+ end
60
+ end
61
+ ```
62
+
63
+ ```js
64
+ // dashboard/page_component_controller.js
65
+ export default class extends Controller {
66
+ static values = { activeFilter: String, count: Number }
67
+ static outlets = ["dashboard--release-card-component"]
68
+
69
+ handleFilterChanged(event) {
70
+ const { filter, query } = event.detail ?? {}
71
+ if (filter !== undefined) this.activeFilterValue = filter
72
+ this.#applyFilter(query ?? "")
73
+ }
74
+
75
+ // GOTCHA: do not iterate `this.dashboardReleaseCardComponentOutlets` inside
76
+ // dashboardReleaseCardComponentOutletConnected — Stimulus warns for each
77
+ // selector match whose controller hasn't attached yet. Iterate on real events.
78
+
79
+ #applyFilter(query) {
80
+ const q = query.trim().toLowerCase()
81
+ let visible = 0
82
+ for (const card of this.dashboardReleaseCardComponentOutlets) {
83
+ const show = (this.activeFilterValue === "all" || card.statusValue === this.activeFilterValue)
84
+ && (q === "" || card.nameValue.toLowerCase().includes(q))
85
+ card.setVisible(show)
86
+ if (show) visible += 1
87
+ }
88
+ this.countValue = visible
89
+ this.dispatch("filterApplied", { detail: { count: visible }, target: window })
90
+ }
91
+ }
92
+ ```
93
+
94
+ ### Release card (self-registers with host, uses `classes` DSL + SSR)
95
+
96
+ ```ruby
97
+ module Dashboard
98
+ class ReleaseCardComponent < ApplicationComponent
99
+ prop :release_id, Integer
100
+ prop :name, String
101
+ prop :version, String
102
+ prop :environment, _Union(:production, :staging, :preview), default: :staging
103
+ prop :status, _Union(:pending, :deployed, :failed), default: :pending
104
+
105
+ # `stimulus_outlet_host:` is inherited from Vident::Component — no prop
106
+ # declaration needed in this class.
107
+
108
+ stimulus do
109
+ values_from_props :release_id, :name, :status
110
+
111
+ # Proc sees @status at render time; emits
112
+ # `data-<this-controller>-status-class="..."` for the JS side, AND the same
113
+ # value is inlined via `class_list_for_stimulus_classes(:status)` below
114
+ # for SSR first paint.
115
+ classes status: -> {
116
+ case @status
117
+ when :deployed then "border-green-500 bg-green-50"
118
+ when :failed then "border-red-500 bg-red-50"
119
+ else "border-yellow-400 bg-yellow-50"
120
+ end
121
+ }
122
+
123
+ actions [:click, :select]
124
+ end
125
+
126
+ def view_template
127
+ root_element(
128
+ class: "block cursor-pointer rounded-lg border-2 p-4 shadow-sm #{class_list_for_stimulus_classes(:status)}",
129
+ role: "button",
130
+ tabindex: 0
131
+ ) do |card|
132
+ # Two buttons share one `apply` handler. `event.params.kind` on the JS side
133
+ # tells them apart — see example 5 for the params idiom.
134
+ card.child_element(
135
+ :button,
136
+ stimulus_action: [:click, :apply],
137
+ stimulus_target: :promote_button,
138
+ stimulus_params: { kind: "promote" },
139
+ type: "button", class: "..."
140
+ ) { "Promote" }
141
+
142
+ card.child_element(
143
+ :button,
144
+ stimulus_action: [:click, :apply],
145
+ stimulus_target: :cancel_button,
146
+ stimulus_params: { kind: "cancel" },
147
+ type: "button", class: "..."
148
+ ) { "Cancel" }
149
+ end
150
+ end
151
+ end
152
+ end
153
+ ```
154
+
155
+ ```js
156
+ // dashboard/release_card_component_controller.js
157
+ export default class extends Controller {
158
+ static targets = ["promoteButton", "cancelButton"]
159
+ static values = { releaseId: Number, name: String, status: String }
160
+
161
+ select(event) {
162
+ if (event.target.closest("button")) return // let buttons handle themselves
163
+ this.dispatch("selected", { detail: this.#payload(), target: window })
164
+ }
165
+
166
+ apply(event) {
167
+ const kind = event.params.kind // "promote" | "cancel"
168
+ this.#disable()
169
+ this.dispatch(`${kind}d`, { detail: this.#payload(), target: window })
170
+ }
171
+
172
+ setVisible(show) { this.element.classList.toggle("hidden", !show) }
173
+
174
+ #payload() { return { releaseId: this.releaseIdValue, name: this.nameValue, status: this.statusValue } }
175
+ #disable() { this.promoteButtonTarget.disabled = true; this.cancelButtonTarget.disabled = true }
176
+ }
177
+ ```
178
+
179
+ ### Detail panel (StimulusNull + keyboard modifier action)
180
+
181
+ ```ruby
182
+ module Dashboard
183
+ class DetailPanelComponent < ApplicationComponent
184
+ stimulus do
185
+ # Vident::StimulusNull emits the literal string "null" as the data attribute
186
+ # value. Stimulus's Object parser runs it through JSON.parse, so `releaseValue`
187
+ # starts as JS `null` instead of the default `{}`. Use ONLY with Object/Array
188
+ # typed Stimulus values — for String/Number/Boolean the "null" string reads
189
+ # as garbage. A bare `nil` would omit the attribute entirely (Stimulus uses
190
+ # its per-type default); StimulusNull is an explicit "emit null" opt-in.
191
+ values release: -> { Vident::StimulusNull }
192
+
193
+ classes state: "fixed right-0 top-0 h-full w-80 border-l bg-white p-6 shadow-xl transition-transform duration-200 translate-x-full"
194
+
195
+ # Three action entries in one `actions` call:
196
+ # 1. scoped window event from ReleaseCard → opens the panel
197
+ # 2. Hash form with keyboard filter + @window → Escape closes it.
198
+ # Expands to `keydown.esc@window->dashboard--detail-panel-component#close`.
199
+ # 3. plain `:close` — the close button's local click target
200
+ actions -> { [ReleaseCardComponent.stimulus_scoped_event_on_window(:selected), :handle_selected] },
201
+ { event: :keydown, method: :close, keyboard: "esc", window: true },
202
+ :close
203
+ end
204
+
205
+ def view_template
206
+ root_element(class: class_list_for_stimulus_classes(:state)) do |panel|
207
+ panel.child_element(:button, stimulus_action: :close, type: "button") { "X" }
208
+ panel.child_element(:div, stimulus_target: :body, class: "mt-4 space-y-2") do
209
+ p(class: "italic text-gray-400") { "Click a release to see details." }
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
215
+ ```
216
+
217
+ ```js
218
+ // dashboard/detail_panel_component_controller.js
219
+ export default class extends Controller {
220
+ static targets = ["body"]
221
+ static values = { release: Object }
222
+
223
+ handleSelected(event) {
224
+ this.releaseValue = event.detail
225
+ this.#render()
226
+ this.element.classList.remove("translate-x-full")
227
+ }
228
+
229
+ close() { this.element.classList.add("translate-x-full") }
230
+
231
+ #render() {
232
+ const r = this.releaseValue
233
+ if (!r || !r.releaseId) return
234
+ this.bodyTarget.innerHTML = `<p>${r.name} — ${r.status}</p>`
235
+ }
236
+ }
237
+ ```
238
+
239
+ Identifier walk:
240
+ `Dashboard::ReleaseCardComponent` → class method `stimulus_identifier` returns
241
+ `"dashboard--release-card-component"`. `stimulus_scoped_event_on_window(:selected)`
242
+ returns the Symbol `:"dashboard--release-card-component:selected@window"`. On the JS
243
+ side, the card's `this.dispatch("selected", { target: window })` fires an event of type
244
+ `dashboard--release-card-component:selected` on window, matching exactly.
245
+
246
+ ---
247
+
248
+ ## 2. Greeter with slot trigger
249
+
250
+ Parent exposes a named slot; parent passes its own action descriptor into the slot at
251
+ render time so the slot triggers a method on the parent.
252
+
253
+ ### ViewComponent + ERB
254
+
255
+ ```ruby
256
+ # app/components/greeters/greeter_with_trigger_component.rb
257
+ module Greeters
258
+ class GreeterWithTriggerComponent < Vident::ViewComponent::Base
259
+ renders_one :trigger, GreeterButtonComponent
260
+
261
+ def root_element_attributes
262
+ {
263
+ stimulus_classes: {
264
+ pre_click: "text-md text-gray-500",
265
+ post_click: "text-xl text-blue-700"
266
+ }
267
+ }
268
+ end
269
+
270
+ # Default fallback — used when the consumer doesn't pass a custom trigger.
271
+ def default_trigger
272
+ GreeterButtonComponent.new(
273
+ before_clicked_message: "Click me to greet.",
274
+ after_clicked_message: "Greeted! Click to reset.",
275
+ stimulus_actions: [stimulus_action(:click, :greet)]
276
+ )
277
+ end
278
+ end
279
+ end
280
+ ```
281
+
282
+ ```erb
283
+ <%= root_element do |greeter| %>
284
+ <input type="text"
285
+ <%= greeter.as_stimulus_target(:name) %>
286
+ class="shadow appearance-none border rounded py-2 px-3">
287
+
288
+ <% if trigger? %>
289
+ <%= trigger %>
290
+ <% end %>
291
+
292
+ <%= greeter.child_element(:span, stimulus_target: :output,
293
+ class: "ml-4 #{greeter.class_list_for_stimulus_classes(:pre_click)}") do %>
294
+ ...
295
+ <% end %>
296
+ <% end %>
297
+ ```
298
+
299
+ At the render site, the consumer can override the trigger while still wiring it to the
300
+ parent's action:
301
+
302
+ ```erb
303
+ <%= render GreeterWithTriggerComponent.new do |greeter| %>
304
+ <% greeter.with_trigger(
305
+ before_clicked_message: "Custom label",
306
+ stimulus_actions: [greeter.stimulus_action(:click, :greet)]
307
+ ) %>
308
+ <% end %>
309
+ ```
310
+
311
+ `greeter.stimulus_action(:click, :greet)` returns a `Vident::StimulusAction` whose
312
+ `controller` is the parent's (greeter's) identifier, so the click handler on the child's
313
+ button routes to `greeter-with-trigger-component#greet`, not to the child.
314
+
315
+ ### Phlex version
316
+
317
+ Same component, Phlex syntax:
318
+
319
+ ```ruby
320
+ module PhlexGreeters
321
+ class GreeterWithTriggerComponent < ApplicationComponent
322
+ def trigger(**args)
323
+ @trigger ||= GreeterButtonComponent.new(**args)
324
+ end
325
+
326
+ private
327
+
328
+ def trigger_or_default(greeter)
329
+ return render(@trigger) if @trigger
330
+
331
+ render(trigger(
332
+ before_clicked_message: "Greet",
333
+ stimulus_actions: [greeter.stimulus_action(:click, :greet)]
334
+ ))
335
+ end
336
+
337
+ def root_element_attributes
338
+ { stimulus_classes: { pre_click: "text-md text-gray-500", post_click: "text-xl text-blue-700" } }
339
+ end
340
+
341
+ def view_template(&)
342
+ vanish(&) # capture & discard the block content so consumers can call `#trigger` inside it
343
+ root_element do |greeter|
344
+ input(type: "text", data: { **greeter.stimulus_target(:name) })
345
+ trigger_or_default(greeter)
346
+ greeter.child_element(:span, stimulus_target: :output,
347
+ class: "ml-4 #{greeter.class_list_for_stimulus_classes(:pre_click)}")
348
+ end
349
+ end
350
+ end
351
+ end
352
+ ```
353
+
354
+ ---
355
+
356
+ ## 3. ERB: three ways to emit data attributes
357
+
358
+ ViewComponent/ERB users have three stylistic choices for attaching Stimulus wiring to
359
+ a hand-authored HTML tag. All three are equivalent; pick one per file for consistency.
360
+
361
+ ```erb
362
+ <%= root_element do |greeter| %>
363
+ <%# (a) Inline `as_stimulus_*` helpers — embed the raw data-* attributes directly in the HTML tag. %>
364
+ <%# Most compatible with better_html only if you allow embedded expressions inside tag bodies. %>
365
+ <input type="text"
366
+ <%= greeter.as_stimulus_target(:name) %>
367
+ class="...">
368
+ <button <%= greeter.as_stimulus_action([:click, :greet]) %>
369
+ class="...">
370
+ <%= @cta %>
371
+ </button>
372
+
373
+ <%# (b) Rails `content_tag` with `data:` spread — works anywhere `content_tag` does, %>
374
+ <%# plays nicely with strict HTML linters. Singular helpers return a Hash shape %>
375
+ <%# like { "data-greeter-target" => "name" }, spread with `**`. %>
376
+ <%= content_tag(:input, nil, type: "text", data: { **greeter.stimulus_target(:name) }) %>
377
+ <%= content_tag(:button, @cta, data: { **greeter.stimulus_action([:click, :greet]) }) %>
378
+
379
+ <%# (c) Vident's `child_element` helper — one call, tag + stimulus_* kwargs + block. %>
380
+ <%# Plural kwargs (`stimulus_actions:`) take an Enumerable; singular take one entry. %>
381
+ <%= greeter.child_element(:input, stimulus_target: :name, type: "text", class: "...") %>
382
+ <%= greeter.child_element(:button, stimulus_action: [:click, :greet], class: "...") do %>
383
+ <%= @cta %>
384
+ <% end %>
385
+ <% end %>
386
+ ```
387
+
388
+ Phlex users have two choices — `child_element` (identical) and the native Phlex tag
389
+ methods with `data: { **component.stimulus_target(:name) }`.
390
+
391
+ ---
392
+
393
+ ## 4. Avatar: conditional root tag + class-list precedence
394
+
395
+ Shows `element_tag:` varying by prop, `no_stimulus_controller`, `with_cache_key`, and
396
+ the full class-list precedence via `root_element_classes`.
397
+
398
+ ```ruby
399
+ module Phlex
400
+ class AvatarComponent < ApplicationComponent
401
+ no_stimulus_controller # don't emit the implied `data-controller`
402
+ with_cache_key # relies on ApplicationComponent `include Vident::Caching` — see api-reference.md
403
+
404
+ prop :url, _Nilable(String), predicate: :private, reader: :public
405
+ prop :initials, String, reader: :public
406
+ prop :shape, Symbol, default: :circle, reader: :public
407
+ prop :border, _Boolean, default: false, predicate: :private, reader: :public
408
+ prop :size, Symbol, default: :normal, reader: :public
409
+
410
+ private
411
+
412
+ def view_template
413
+ root_element do
414
+ span(class: "#{text_size_class} font-medium leading-none text-white") { @initials } unless image_avatar?
415
+ end
416
+ end
417
+
418
+ # Flip the root to <img> when a URL is given.
419
+ def root_element_attributes
420
+ {
421
+ element_tag: image_avatar? ? :img : :div,
422
+ html_options: default_html_options
423
+ }
424
+ end
425
+
426
+ def default_html_options
427
+ if image_avatar?
428
+ { class: "inline-block object-contain", src: @url, alt: "Profile image" }
429
+ else
430
+ { class: "inline-flex items-center justify-center bg-gray-500" }
431
+ end
432
+ end
433
+
434
+ # Lower precedence than `html_options[:class]` — wins only when `html_options` has no `:class`.
435
+ def root_element_classes
436
+ [size_classes, shape_class, (@border ? "border" : "")]
437
+ end
438
+
439
+ def image_avatar? = @url.present?
440
+ def shape_class = (@shape == :circle) ? "rounded-full" : "rounded-md"
441
+ def size_classes = { tiny: "w-6 h-6", small: "w-8 h-8", medium: "w-12 h-12" }[@size] || "w-10 h-10"
442
+ def text_size_class = (@size == :tiny || @size == :small) ? "text-xs" : "text-medium"
443
+ end
444
+ end
445
+ ```
446
+
447
+ Because this AvatarComponent sets `html_options[:class]` in `root_element_attributes`,
448
+ `root_element_classes` is NOT applied — `html_options[:class]` wins per the precedence
449
+ rules in SKILL.md §4. If you want the `root_element_classes` values kept AND extra
450
+ overrides, merge them yourself (see `PhlexGreeters::InheritedGreeterComponent` in the
451
+ dummy app for a `tailwind_merge`-aware merge).
452
+
453
+ ---
454
+
455
+ ## 5. Stimulus params on sibling buttons
456
+
457
+ Both buttons fire the same `apply` action on the parent card controller; the
458
+ per-button `stimulus_params:` tells the handler which one fired via `event.params.kind`:
459
+
460
+ ```ruby
461
+ card.child_element(:button,
462
+ stimulus_action: [:click, :apply],
463
+ stimulus_params: { kind: "promote" }) { "Promote" }
464
+
465
+ card.child_element(:button,
466
+ stimulus_action: [:click, :apply],
467
+ stimulus_params: { kind: "cancel" }) { "Cancel" }
468
+ ```
469
+
470
+ ```js
471
+ apply(event) {
472
+ const kind = event.params.kind // "promote" | "cancel"
473
+ this.dispatch(`${kind}d`, { detail: this.#payload(), target: window })
474
+ }
475
+ ```
476
+
477
+ **Element-scoped, not action-scoped.** In Stimulus, params live on the element, so every
478
+ action on the same element sees the same `event.params`. Vident mirrors this: `params`
479
+ is a sibling of `actions` in the DSL, not nested inside it. If you need per-action
480
+ params, split the buttons. This is usually preferable anyway — the shared-handler
481
+ pattern above is a tiny bit RPC-ish and is shown because params are useful to know
482
+ about, not because one action/two params is the recommended shape.
483
+
484
+ ---
485
+
486
+ ## Where to read more
487
+
488
+ - `test/dummy/app/components/dashboard/` — the full dashboard (5 components + JS) is
489
+ Vident's reference example. Every feature in SKILL.md is exercised there.
490
+ - `test/public_api_spec/specs/core_dsl.rb` — one locked-behaviour test per input shape
491
+ of every DSL primitive. Useful when unsure about an edge case.
492
+ - `test/dummy/app/components/greeters/` (ERB) and `test/dummy/app/components/phlex_greeters/` (Phlex) — side-by-side renditions of the same component in both engines.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vident
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Ierodiaconou
@@ -120,7 +120,41 @@ files:
120
120
  - lib/vident/stimulus_value_collection.rb
121
121
  - lib/vident/tailwind.rb
122
122
  - lib/vident/version.rb
123
+ - lib/vident2.rb
124
+ - lib/vident2/caching.rb
125
+ - lib/vident2/component.rb
126
+ - lib/vident2/engine.rb
127
+ - lib/vident2/error.rb
128
+ - lib/vident2/internals/action_builder.rb
129
+ - lib/vident2/internals/attribute_writer.rb
130
+ - lib/vident2/internals/class_list_builder.rb
131
+ - lib/vident2/internals/declaration.rb
132
+ - lib/vident2/internals/declarations.rb
133
+ - lib/vident2/internals/draft.rb
134
+ - lib/vident2/internals/dsl.rb
135
+ - lib/vident2/internals/plan.rb
136
+ - lib/vident2/internals/registry.rb
137
+ - lib/vident2/internals/resolver.rb
138
+ - lib/vident2/internals/target_builder.rb
139
+ - lib/vident2/phlex.rb
140
+ - lib/vident2/phlex/html.rb
141
+ - lib/vident2/stimulus/action.rb
142
+ - lib/vident2/stimulus/class_map.rb
143
+ - lib/vident2/stimulus/collection.rb
144
+ - lib/vident2/stimulus/controller.rb
145
+ - lib/vident2/stimulus/naming.rb
146
+ - lib/vident2/stimulus/null.rb
147
+ - lib/vident2/stimulus/outlet.rb
148
+ - lib/vident2/stimulus/param.rb
149
+ - lib/vident2/stimulus/target.rb
150
+ - lib/vident2/stimulus/value.rb
151
+ - lib/vident2/tailwind.rb
152
+ - lib/vident2/version.rb
153
+ - lib/vident2/view_component.rb
154
+ - lib/vident2/view_component/base.rb
123
155
  - skills/vident/SKILL.md
156
+ - skills/vident/api-reference.md
157
+ - skills/vident/examples.md
124
158
  homepage: https://github.com/stevegeek/vident
125
159
  licenses:
126
160
  - MIT