vident-phlex 2.0.0 → 3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6fd97e08c66d8473338aded609cf45f63d982dddc72e376a598218a5224139c5
4
- data.tar.gz: 15db0bb5c243e378327792710e491b49c93ab185a0286730e1420d8eae0de2e0
3
+ metadata.gz: c416c516698b5baa7fa1781d7214304c47e1538914d8ca223999ed445d31668a
4
+ data.tar.gz: d4a7734bd601437da2c0ae138af4fa842d3fb4f6e54d1c0eb63b248570ed6bff
5
5
  SHA512:
6
- metadata.gz: e53d2df34d5720dbd801139e0326bc2837b6af70d4a6ef6a3bbe6b718c8021275931a5adae8b1ce409063aec68c9f286af5aa1ba434e91ca97dc812331506f73
7
- data.tar.gz: 4ea8a5f6707e92a0340bb8f409d33533eac3cd509e4e4f0e108218d303cbb077e3965ec551c8f0f9a8bcfd4fd73e817b98f336923e02258aaf5d452507b242ba
6
+ metadata.gz: e42d3a768f1764c41ef278a40c0a568db966e029dcd010fa62dc6a53f0551842bb853caf375cbe67f6d2a3c5de32d7ff826a10cbf738850a7828950f8afbbc88
7
+ data.tar.gz: edd99c2f09799da5fe30f477c5ddb07d77d20c3a0820ef5d23e7e24ec493320ed45568756f8d7ca6fe812d573ac0aef4c07f774759cb89fa0dd52b7a4e432c87
data/CHANGELOG.md CHANGED
@@ -6,6 +6,36 @@ 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
+ ## [3.0.0] - 2026-05-04
10
+
11
+ A bare `String` now means the same thing across every Stimulus primitive — a controller path. The 2.x ambiguity where a `String` could variously mean controller path, outlet name, CSS selector, or fully-qualified action descriptor (depending on primitive and position) is gone, closing a class of silent-failure footguns. See `UPGRADING.md` "Upgrading to Vident 3.0" for symptom → fix on each breaking change.
12
+
13
+ ### Breaking
14
+
15
+ - **Outlet: bare String is no longer a CSS selector** (#32). Wrap verbatim selectors in `Vident::Selector(...)`, or pass `nil` for auto-selector. Class-level `stimulus_outlet(name, selector)` and the `[Symbol, String]` / `[String, String]` array forms follow the same rule.
16
+ - **Action: bare String is no longer a qualified descriptor.** Use structured args `:event, "ctrl", :method`, the Hash descriptor, or `Vident::Stimulus::Action.parse_descriptor(s)` for genuine wire strings.
17
+ - **Target: bare String is no longer a target name.** Names must be Symbols. The cross-controller `[String, Symbol]` form is unchanged.
18
+
19
+ ### Added
20
+
21
+ - `Vident::Stimulus::Selector` value class + `Vident::Selector(css)` (and `Vident::Stimulus.Selector(css)`) constructors for tagging verbatim CSS selectors at outlet sites (#32).
22
+ - `Vident::Stimulus::Action.parse_descriptor(string)` — public escape hatch for parsing wire-format Stimulus action descriptors verbatim (formerly the private `parse_qualified_string`).
23
+ - Component scaffold generators: `bin/rails g vident:phlex:component …` and `bin/rails g vident:view_component:component …` scaffold a component, sidecar Stimulus controller, and unit test, with `--skip-stimulus` / `--skip-controller` / `--skip-test` / `--typescript` / `--parent` flags.
24
+ - `vident:component` umbrella generator dispatches to the right engine; requires `--engine=phlex` or `--engine=view_component` when both adapters are loaded.
25
+ - `vident:install` now scaffolds `ApplicationPhlexComponent` / `ApplicationViewComponent` based on the installed adapter gem, mirroring `ApplicationRecord` / `ApplicationController`.
26
+
27
+ ### Changed
28
+
29
+ - `bin/rails generate vident:install --force` overwrites an existing `.claude/skills/vident/SKILL.md` so upgrades can refresh it; without `--force` the existing file is preserved.
30
+
31
+
32
+ ## [2.0.1] - 2026-04-24
33
+
34
+ ### Fixed
35
+
36
+ - Base gem no longer fails to load when installed without the adapter gems — adapter requires moved to their own entry points (`lib/vident-phlex.rb`, `lib/vident-view_component.rb`) (#29).
37
+
38
+
9
39
  ## [2.0.0] - 2026-04-24
10
40
 
11
41
  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.
data/README.md CHANGED
@@ -1,10 +1,28 @@
1
- # Vident
1
+ <p align="center">
2
+ <a href="https://vident-gem.diaconou.com">
3
+ <img src="https://vident-gem.diaconou.com/assets/img/vident-logo.png" alt="Vident" width="180">
4
+ </a>
5
+ </p>
6
+
7
+ <h1 align="center">Vident</h1>
8
+
9
+ <p align="center">
10
+ <strong>Type-safe Rails components with first-class Stimulus, on Phlex or ViewComponent.</strong>
11
+ <br>
12
+ <a href="https://vident-gem.diaconou.com">Documentation</a>
13
+ &nbsp;·&nbsp;
14
+ <a href="https://vident-gem.diaconou.com/introduction/getting-started/">Getting started</a>
15
+ &nbsp;·&nbsp;
16
+ <a href="https://rubygems.org/gems/vident">RubyGems</a>
17
+ </p>
2
18
 
3
19
  A powerful Ruby gem for building interactive, type-safe components in Rails applications with seamless [Stimulus.js](https://stimulus.hotwired.dev/) integration.
4
20
 
5
21
  Vident supports both [ViewComponent](https://viewcomponent.org/) and [Phlex](https://www.phlex.fun/) rendering engines while providing a consistent API for creating
6
22
  reusable UI components powered by [Stimulus.js](https://stimulus.hotwired.dev/).
7
23
 
24
+ > **Full documentation lives at [vident-gem.diaconou.com](https://vident-gem.diaconou.com)** — including a live, clickable component demo on the home page.
25
+
8
26
  ## Table of Contents
9
27
 
10
28
  - [Introduction](#introduction)
@@ -62,7 +80,31 @@ bundle install
62
80
  bin/rails generate vident:install
63
81
  ```
64
82
 
65
- The `vident:install` generator writes `config/initializers/vident.rb`, wires per-request ID seeding into `ApplicationController`, and (if you use Claude Code) drops a Vident skill into `.claude/skills/vident/SKILL.md` so the model has first-party guidance on the gem's conventions. See [Element IDs and request-scoped seeding](#element-ids-and-request-scoped-seeding) for the initializer rationale, and [Claude Code skill](#claude-code-skill) for the skill.
83
+ The `vident:install` generator writes `config/initializers/vident.rb`, wires per-request ID seeding into `ApplicationController`, generates `app/components/application_phlex_component.rb` and/or `application_view_component.rb` (one per engine gem in your Gemfile, mirroring `ApplicationRecord`), and (if you use Claude Code) drops a Vident skill into `.claude/skills/vident/SKILL.md` so the model has first-party guidance on the gem's conventions. See [Element IDs and request-scoped seeding](#element-ids-and-request-scoped-seeding) for the initializer rationale, and [Claude Code skill](#claude-code-skill) for the skill.
84
+
85
+ ### Scaffolding components
86
+
87
+ Once `vident:install` has run, scaffold a component, its Stimulus controller sidecar, and a unit test in one go:
88
+
89
+ ```bash
90
+ bin/rails generate vident:phlex:component Dashboard::TaskCard
91
+ bin/rails generate vident:view_component:component Dashboard::TaskCard
92
+ ```
93
+
94
+ There's also an umbrella `vident:component` dispatcher that picks the right engine when only one is in the Gemfile (pass `--engine=phlex` or `--engine=view_component` if both are):
95
+
96
+ ```bash
97
+ bin/rails generate vident:component Dashboard::TaskCard
98
+ ```
99
+
100
+ Useful flags:
101
+ - `--skip-stimulus` — omit the `stimulus do` block and the JS controller sidecar.
102
+ - `--skip-controller` — omit the JS sidecar but keep the `stimulus do` block (e.g. when sharing a controller).
103
+ - `--skip-test` — skip the unit test.
104
+ - `--typescript` / `-t` — emit a `.ts` controller instead of `.js`.
105
+ - `--parent=ClassName` — override the default base class.
106
+
107
+ A trailing `Component` in the input is stripped, so `g vident:component TaskCardComponent` and `g vident:component TaskCard` produce the same files.
66
108
 
67
109
  ## Quick Start
68
110
 
@@ -640,12 +682,16 @@ Parallel helpers exist for every attribute kind: `as_stimulus_controller(s)`, `a
640
682
  ```ruby
641
683
  class DashboardComponent < Vident::ViewComponent::Base
642
684
  stimulus do
643
- # kwarg form: outlet name is the implied controller's identifier
644
- outlets modal: ".modal", user_status: ".online-user"
645
-
646
- # positional-hash form: required when the outlet identifier contains "--"
647
- # (e.g. cross-namespace controllers) because Ruby kwarg keys can't have dashes
648
- outlets({"admin--users" => ".admin-users"})
685
+ # kwarg form: key is the *child controller identifier*. nil = auto-selector
686
+ # (`#<host-id> [data-controller~=modal]`); wrap a verbatim CSS selector in
687
+ # Vident::Selector(...). Bare String values are rejected.
688
+ outlets modal: nil
689
+ outlets user_status: Vident::Selector(".online-user")
690
+
691
+ # positional-hash form: required when the child controller identifier contains "--"
692
+ # (e.g. cross-namespace) because Ruby kwarg keys can't carry dashes
693
+ outlets({"admin--users" => nil})
694
+ outlets({"admin--users" => Vident::Selector(".admin-users")})
649
695
  end
650
696
  end
651
697
  ```
@@ -654,14 +700,15 @@ Or via the `stimulus_outlets:` prop / `root_element_attributes`:
654
700
 
655
701
  ```ruby
656
702
  stimulus_outlets: [
657
- [:modal, ".modal"], # [name, selector] on implied controller
658
- ["admin/users", :row, ".user-row"], # [controller_path, name, selector] for cross-controller
659
- :user_status, # single symbol → auto-selector (see below)
660
- other_component # component instance reuses its stimulus_identifier + id
703
+ :user_status, # symbol → auto-selector
704
+ [:modal, Vident::Selector(".modal")], # [name, Selector] verbatim
705
+ ["admin/users", :row], # cross-controller, auto-selector
706
+ ["admin/users", :row, Vident::Selector(".x")], # cross-controller, verbatim
707
+ other_component # component instance → reuses its stimulus_identifier + id
661
708
  ]
662
709
  ```
663
710
 
664
- **Auto-generated selectors.** Pass just a name (symbol or string) and the selector becomes `[data-controller~=<name>]`. Pass a component instance and the selector additionally scopes to the component's id (`#<id> [data-controller~=...]`), which is what lets you target a specific instance rather than every matching controller on the page.
711
+ **Auto-generated selectors.** Pass just a name (Symbol or String) and the selector becomes `#<host-id> [data-controller~=<name>]` scoped to this component's element id so you target a specific instance rather than every matching controller on the page. Wrap a verbatim CSS selector in `Vident::Selector(...)` to opt out of auto-scoping. A bare String inside an outlet position is treated as a controller identifier, never as a CSS selector — that ambiguity used to silently produce broken outlets in v2 and is now rejected.
665
712
 
666
713
  **Self-registration via `stimulus_outlet_host`.** A built-in prop on every Vident component. When set to another component, the child registers itself as an outlet on that host at initialization — the host doesn't need to know about the child in its DSL:
667
714
 
@@ -671,7 +718,7 @@ render DashboardComponent.new do |dashboard|
671
718
  end
672
719
  ```
673
720
 
674
- The modal now appears on the dashboard's root element as `data-dashboard-component-modal-component-outlet="#<modal-id>"` without the dashboard declaring it upfront.
721
+ The modal now appears on the dashboard's root element as `data-dashboard-component-modal-component-outlet="#<dashboard-id> [data-controller~=modal-component]"` without the dashboard declaring it upfront.
675
722
 
676
723
  **On child elements** — `child_element` accepts `stimulus_outlet:` (singular) and `stimulus_outlets:` (plural / Enumerable) exactly like the target/action kwargs, so a nested `<div>` can carry its own outlet declarations.
677
724
 
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/named_base"
4
+
5
+ module Vident
6
+ module Phlex
7
+ module Generators
8
+ class ComponentGenerator < ::Rails::Generators::NamedBase
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ desc "Scaffold a Vident Phlex component (.rb), its Stimulus controller sidecar, and a unit test."
12
+
13
+ class_option :skip_stimulus, type: :boolean, default: false,
14
+ desc: "Omit the stimulus DSL block and the JS controller sidecar"
15
+ class_option :skip_controller, type: :boolean, default: false,
16
+ desc: "Omit the JS controller sidecar (keeps the stimulus DSL block)"
17
+ class_option :skip_test, type: :boolean, default: false,
18
+ desc: "Skip generating a unit test"
19
+ class_option :typescript, type: :boolean, default: false, aliases: "-t",
20
+ desc: "Emit a TypeScript controller (.ts) instead of JavaScript (.js)"
21
+ class_option :parent, type: :string, default: "ApplicationPhlexComponent",
22
+ desc: "Parent class for the component"
23
+
24
+ def create_component_file
25
+ template "component.rb.tt", File.join("app/components", class_path, "#{file_name}_component.rb")
26
+ end
27
+
28
+ def create_controller_file
29
+ return if options[:skip_stimulus] || options[:skip_controller]
30
+ ext = options[:typescript] ? "ts" : "js"
31
+ template "controller.#{ext}.tt", File.join("app/components", class_path, "#{file_name}_component_controller.#{ext}")
32
+ end
33
+
34
+ def create_test_file
35
+ return if options[:skip_test]
36
+ template "component_test.rb.tt", File.join("test/components", class_path, "#{file_name}_component_test.rb")
37
+ end
38
+
39
+ private
40
+
41
+ # Allow `g vident:phlex:component TaskCardComponent` to produce the
42
+ # same files as `g ... TaskCard` rather than `TaskCardComponentComponent`.
43
+ # Matches ViewComponent's own generator behaviour.
44
+ def class_name
45
+ super.sub(/Component\z/, "")
46
+ end
47
+
48
+ def file_name
49
+ super.sub(/_component\z/, "")
50
+ end
51
+
52
+ def component_class_name
53
+ "#{class_name}Component"
54
+ end
55
+
56
+ def parent_class
57
+ options[:parent]
58
+ end
59
+
60
+ def stimulus_block?
61
+ !options[:skip_stimulus]
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ <% module_namespacing do -%>
4
+ class <%= component_class_name %> < <%= parent_class %>
5
+ prop :title, String
6
+
7
+ <% if stimulus_block? -%>
8
+ stimulus do
9
+ values_from_props :title
10
+ action(:select).on(:click)
11
+ end
12
+
13
+ <% end -%>
14
+ def view_template
15
+ root_element(class: "rounded border p-4") do
16
+ h3(class: "font-semibold") { @title }
17
+ end
18
+ end
19
+ end
20
+ <% end -%>
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ <% module_namespacing do -%>
6
+ class <%= component_class_name %>Test < ActiveSupport::TestCase
7
+ test "renders the title" do
8
+ html = <%= component_class_name %>.new(title: "Hello").call
9
+ assert_includes html, "Hello"
10
+ end
11
+ end
12
+ <% end -%>
@@ -0,0 +1,11 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ title: String,
6
+ }
7
+
8
+ select(event) {
9
+ this.dispatch("selected", { detail: { title: this.titleValue } })
10
+ }
11
+ }
@@ -0,0 +1,13 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ title: String,
6
+ }
7
+
8
+ declare readonly titleValue: string
9
+
10
+ select(event: Event): void {
11
+ this.dispatch("selected", { detail: { title: this.titleValue } })
12
+ }
13
+ }
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Entry point for the `vident-phlex` gem — loaded automatically by
4
+ # `Bundler.require` when `gem "vident-phlex"` is in the Gemfile.
5
+
6
+ require "phlex"
7
+ require "vident"
8
+ require "vident/phlex"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vident-phlex
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Ierodiaconou
@@ -55,14 +55,14 @@ dependencies:
55
55
  requirements:
56
56
  - - "~>"
57
57
  - !ruby/object:Gem::Version
58
- version: 2.0.0
58
+ version: 3.0.0
59
59
  type: :runtime
60
60
  prerelease: false
61
61
  version_requirements: !ruby/object:Gem::Requirement
62
62
  requirements:
63
63
  - - "~>"
64
64
  - !ruby/object:Gem::Version
65
- version: 2.0.0
65
+ version: 3.0.0
66
66
  - !ruby/object:Gem::Dependency
67
67
  name: phlex
68
68
  requirement: !ruby/object:Gem::Requirement
@@ -113,6 +113,12 @@ files:
113
113
  - CHANGELOG.md
114
114
  - LICENSE.txt
115
115
  - README.md
116
+ - lib/generators/vident/phlex/component/component_generator.rb
117
+ - lib/generators/vident/phlex/component/templates/component.rb.tt
118
+ - lib/generators/vident/phlex/component/templates/component_test.rb.tt
119
+ - lib/generators/vident/phlex/component/templates/controller.js.tt
120
+ - lib/generators/vident/phlex/component/templates/controller.ts.tt
121
+ - lib/vident-phlex.rb
116
122
  - lib/vident/phlex.rb
117
123
  - lib/vident/phlex/html.rb
118
124
  homepage: https://github.com/stevegeek/vident