vident 1.0.2 → 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.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -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 +44 -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
@@ -2,24 +2,12 @@
2
2
 
3
3
  require "set"
4
4
 
5
- module Vident2
5
+ module Vident
6
6
  module Internals
7
- # @api private
8
- # Root-element class-list builder. Implements the 6-tier precedence
9
- # cascade plus optional TailwindMerger dedup.
10
- #
11
- # Tiers (render order, left -> right):
12
- # 1. component_name — always first.
13
- # 2-5. Priority cascade (only the highest non-nil wins):
14
- # root_element_classes (instance override) <
15
- # root_element_attributes[:classes] <
16
- # root_element(class:) from template <
17
- # html_options[:class] from prop
18
- # 6. classes: prop — ALWAYS appended, even when tier 5 is present.
19
- #
20
- # Plus: per-kind StimulusClassMap entries whose name is in
21
- # `stimulus_class_names` are appended as CSS. Tailwind merge runs last
22
- # if the merger is passed.
7
+ # Builds the root element's CSS class list with a 6-tier precedence cascade.
8
+ # Tiers (left-to-right): component_name, then the highest-priority non-nil of
9
+ # root_element_classes / root_element_attributes[:classes] / root_element(class:) /
10
+ # html_options[:class], then classes: prop (always appended), then stimulus class maps.
23
11
  module ClassListBuilder
24
12
  CLASSNAME_SEPARATOR = " "
25
13
 
@@ -39,7 +27,6 @@ module Vident2
39
27
  parts = []
40
28
  parts << component_name if component_name
41
29
 
42
- # Priority cascade: top-down, first non-nil wins.
43
30
  if html_options_class
44
31
  parts.concat(Array.wrap(html_options_class))
45
32
  elsif root_element_html_class
@@ -50,8 +37,6 @@ module Vident2
50
37
  parts.concat(Array.wrap(root_element_classes))
51
38
  end
52
39
 
53
- # `classes:` prop: always appended, even when something in the
54
- # cascade already contributed.
55
40
  parts.concat(Array.wrap(classes_prop)) if classes_prop
56
41
 
57
42
  parts.compact!
@@ -68,8 +53,6 @@ module Vident2
68
53
  tailwind_merger ? tailwind_merger.merge(joined) : joined
69
54
  end
70
55
 
71
- # Pick ClassMap entries whose `name` matches any of the requested
72
- # Symbols/Strings (dasherized to match the ClassMap's canonical form).
73
56
  def stimulus_class_css(class_maps, names)
74
57
  names_set = names.map { |n| n.to_s.dasherize }.to_set
75
58
  class_maps.select { |cm| names_set.include?(cm.name) }.map(&:css)
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vident
4
+ module Internals
5
+ # One unresolved DSL entry; the Resolver parses it into typed Stimulus
6
+ # value objects at instance init time.
7
+ Declaration = Data.define(:args, :when_proc, :meta) do
8
+ def self.of(*args, when_proc: nil, **meta)
9
+ new(args: args.freeze, when_proc: when_proc, meta: meta.freeze)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -2,19 +2,12 @@
2
2
 
3
3
  require_relative "declaration"
4
4
 
5
- module Vident2
5
+ module Vident
6
6
  module Internals
7
- # @api private
8
- # Frozen per-class aggregate of what `stimulus do ... end` declared.
9
- # One field per kind (plural Registry name) plus `values_from_props`.
10
- # Entries stay as raw `Declaration` records — the Resolver parses
11
- # them into `Stimulus::*` value objects at instance init, not here.
12
- #
13
- # Keyed kinds (values, params, class_maps, outlets) use `(key, entry)`
14
- # pairs to let a later block's same-key entry replace an earlier one.
15
- # Positional kinds (controllers, actions, targets) are flat arrays;
16
- # later blocks append.
17
- Declarations = Data.define(
7
+ # Frozen per-class aggregate of raw `Declaration` entries from `stimulus do` blocks.
8
+ # Keyed kinds use `[key, Declaration]` pairs so later same-key entries replace earlier ones;
9
+ # positional kinds are flat arrays where later blocks append.
10
+ class Declarations < Data.define(
18
11
  :controllers,
19
12
  :actions,
20
13
  :targets,
@@ -23,7 +16,7 @@ module Vident2
23
16
  :params,
24
17
  :class_maps,
25
18
  :values_from_props
26
- ) do
19
+ )
27
20
  EMPTY_ARRAY = [].freeze
28
21
 
29
22
  def self.empty = @empty ||= new(
@@ -43,9 +36,6 @@ module Vident2
43
36
  !class_maps.empty? || !values_from_props.empty?
44
37
  end
45
38
 
46
- # Merge two Declarations, treating `self` as parent and `other` as
47
- # child. Positional kinds concat (parent first, then child).
48
- # Keyed kinds last-wins on matching key.
49
39
  def merge(other)
50
40
  self.class.new(
51
41
  controllers: concat_positional(controllers, other.controllers),
@@ -63,8 +53,6 @@ module Vident2
63
53
 
64
54
  def concat_positional(a, b) = (a + b).freeze
65
55
 
66
- # Keyed entries are `[key, Declaration]` tuples; last write on a
67
- # given key wins, insertion order otherwise preserved.
68
56
  def merge_keyed(a, b)
69
57
  merged = {}
70
58
  a.each { |(k, d)| merged[k] = d }
@@ -3,16 +3,9 @@
3
3
  require_relative "registry"
4
4
  require_relative "plan"
5
5
 
6
- module Vident2
6
+ module Vident
7
7
  module Internals
8
- # @api private
9
- # Per-instance mutable working copy. Seven Arrays, one per Registry
10
- # kind. `add_<kind>(value_or_values)` mutators are the single sanctioned
11
- # seam for cross-instance mutation (outlet-host pattern) and for
12
- # `add_stimulus_*` calls from `after_component_initialize`.
13
- #
14
- # After `seal!` the Draft is closed — any further `add_*` raises
15
- # `Vident2::StateError`. The sealed Plan is a frozen Data.define snapshot.
8
+ # Per-instance mutable accumulator; seals into a frozen Plan once rendering begins.
16
9
  class Draft
17
10
  def initialize(**collections)
18
11
  @collections = Registry.names.to_h { |name| [name, []] }
@@ -21,12 +14,8 @@ module Vident2
21
14
  end
22
15
 
23
16
  Registry.each do |kind|
24
- # reader
25
17
  define_method(kind.name) { @collections[kind.name] }
26
18
 
27
- # mutator: one call = one logical add. Array input concats all
28
- # elements as pre-parsed values; a single non-Array value appends
29
- # as one entry.
30
19
  define_method(:"add_#{kind.name}") do |value_or_values|
31
20
  raise_if_sealed!
32
21
  Array(value_or_values).each { |v| @collections[kind.name] << v }
@@ -36,8 +25,6 @@ module Vident2
36
25
 
37
26
  def sealed? = @sealed
38
27
 
39
- # Freeze the working copy and snapshot as a frozen Plan. Idempotent:
40
- # subsequent calls return the memoised Plan.
41
28
  def seal!
42
29
  return @plan if @sealed
43
30
  @sealed = true
@@ -52,7 +39,7 @@ module Vident2
52
39
 
53
40
  def raise_if_sealed!
54
41
  return unless @sealed
55
- raise ::Vident2::StateError,
42
+ raise ::Vident::StateError,
56
43
  "cannot modify stimulus attributes after rendering has begun"
57
44
  end
58
45
  end
@@ -5,15 +5,10 @@ require_relative "declarations"
5
5
  require_relative "action_builder"
6
6
  require_relative "target_builder"
7
7
 
8
- module Vident2
8
+ module Vident
9
9
  module Internals
10
- # @api private
11
- # Block receiver for `stimulus do ... end`. Records each primitive
12
- # call as one or more `Declaration` raw entries; `finalize` folds
13
- # them into a frozen `Declarations` aggregate.
14
- #
15
- # Parsing into `Stimulus::*` value objects is deferred to the
16
- # Resolver — this class stores only raw argument tuples.
10
+ # Block receiver for `stimulus do ... end`. Records raw Declaration entries;
11
+ # parsing into typed Stimulus value objects is deferred to the Resolver.
17
12
  class DSL
18
13
  attr_reader :caller_location
19
14
 
@@ -31,9 +26,6 @@ module Vident2
31
26
 
32
27
  # ---- plural (kwargs) forms --------------------------------------
33
28
 
34
- # Each arg becomes one controller entry. An Array arg is splatted
35
- # into positional args for a single controller (e.g. a tuple
36
- # `[path, {as: :alias}]`); anything else is treated as a path.
37
29
  def controllers(*args)
38
30
  args.each do |arg|
39
31
  case arg
@@ -46,10 +38,6 @@ module Vident2
46
38
  self
47
39
  end
48
40
 
49
- # Array in the plural form splats into the singular parser (matching
50
- # V1's plural→singular forwarding) so `actions [:click, :handle]`
51
- # records a single Action entry with event+method rather than two
52
- # separate Actions.
53
41
  def actions(*names)
54
42
  names.each do |name|
55
43
  case name
@@ -89,10 +77,7 @@ module Vident2
89
77
  self
90
78
  end
91
79
 
92
- # Outlets accept a positional Hash (for keys like `"admin--users"`
93
- # that can't be a Ruby kwarg) plus kwargs. Order: positional first,
94
- # kwargs after — last-write wins on duplicates per the keyed merge
95
- # rule.
80
+ # Positional Hash arg supports keys like `"admin--users"` that can't be Ruby kwargs.
96
81
  def outlets(positional = nil, **hash)
97
82
  if positional.is_a?(Hash)
98
83
  positional.each { |k, v| record_keyed(@outlets, k, v) }
@@ -110,31 +95,23 @@ module Vident2
110
95
 
111
96
  # ---- singular forms --------------------------------------------
112
97
 
113
- # Optional `as: :alias` captured in meta for the Resolver.
114
98
  def controller(*args, **meta)
115
99
  @controllers << Declaration.of(*args, **meta)
116
100
  self
117
101
  end
118
102
 
119
- # Returns an `ActionBuilder` so users can fluent-chain
120
- # `.on(:event).modifier(:prevent).keyboard("ctrl+s").window.when { ... }`.
121
- # If no chain methods are called, the raw args pass through unchanged.
122
- def action(*args)
123
- builder = ActionBuilder.new(*args)
103
+ def action(*args, **meta)
104
+ builder = ActionBuilder.new(*args, **meta)
124
105
  @actions << builder
125
106
  builder
126
107
  end
127
108
 
128
- # Returns a `TargetBuilder` so users can chain `.when { ... }` for
129
- # conditional inclusion.
130
109
  def target(*args)
131
110
  builder = TargetBuilder.new(*args)
132
111
  @targets << builder
133
112
  builder
134
113
  end
135
114
 
136
- # `value(:url, "x")`, `value(:url, -> { ... })`,
137
- # `value(:count, static: 0)`, `value(:clicked_count, from_prop: true)`.
138
115
  def value(name, *args, **meta)
139
116
  entry = [name, Declaration.of(*args, **meta)]
140
117
  replace_or_append(@values, entry)
@@ -161,8 +138,6 @@ module Vident2
161
138
 
162
139
  # ---- folding ----------------------------------------------------
163
140
 
164
- # Returns a frozen Declarations snapshot of what this block
165
- # received. Called once the block finishes executing.
166
141
  def to_declarations
167
142
  Declarations.new(
168
143
  controllers: @controllers.dup.freeze,
@@ -183,7 +158,6 @@ module Vident2
183
158
  replace_or_append(bucket, entry)
184
159
  end
185
160
 
186
- # Last-write wins on matching key, insertion order otherwise.
187
161
  def replace_or_append(bucket, entry)
188
162
  key = entry.first
189
163
  idx = bucket.index { |(k, _)| k == key }
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "registry"
4
+
5
+ module Vident
6
+ module Internals
7
+ Plan = Data.define(*Registry.names)
8
+ end
9
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../stimulus/controller"
4
+ require_relative "../stimulus/action"
5
+ require_relative "../stimulus/target"
6
+ require_relative "../stimulus/outlet"
7
+ require_relative "../stimulus/value"
8
+ require_relative "../stimulus/param"
9
+ require_relative "../stimulus/class_map"
10
+
11
+ module Vident
12
+ module Internals
13
+ module Registry
14
+ Kind = Data.define(:name, :plural_name, :singular_name, :value_class, :keyed) do
15
+ alias_method :keyed?, :keyed
16
+ end
17
+
18
+ KINDS = [
19
+ Kind.new(:controllers, :controllers, :controller, Vident::Stimulus::Controller, false),
20
+ Kind.new(:actions, :actions, :action, Vident::Stimulus::Action, false),
21
+ Kind.new(:targets, :targets, :target, Vident::Stimulus::Target, false),
22
+ Kind.new(:outlets, :outlets, :outlet, Vident::Stimulus::Outlet, true),
23
+ Kind.new(:values, :values, :value, Vident::Stimulus::Value, true),
24
+ Kind.new(:params, :params, :param, Vident::Stimulus::Param, true),
25
+ Kind.new(:class_maps, :classes, :class, Vident::Stimulus::ClassMap, true)
26
+ ].freeze
27
+
28
+ BY_NAME = KINDS.to_h { |k| [k.name, k] }.freeze
29
+
30
+ def self.fetch(name) = BY_NAME.fetch(name)
31
+
32
+ def self.each(&block) = KINDS.each(&block)
33
+
34
+ def self.names = BY_NAME.keys
35
+ end
36
+ end
37
+ end