plutonium 0.37.0 → 0.39.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium-controller/SKILL.md +38 -2
  3. data/.claude/skills/plutonium-definition-actions/SKILL.md +13 -0
  4. data/.claude/skills/plutonium-definition-fields/SKILL.md +33 -0
  5. data/.claude/skills/plutonium-nested-resources/SKILL.md +85 -23
  6. data/.claude/skills/plutonium-policy/SKILL.md +93 -6
  7. data/CHANGELOG.md +42 -0
  8. data/CLAUDE.md +8 -10
  9. data/CONTRIBUTING.md +6 -8
  10. data/Rakefile +16 -1
  11. data/app/assets/plutonium.css +1 -1
  12. data/app/assets/plutonium.js +9371 -11492
  13. data/app/assets/plutonium.js.map +4 -4
  14. data/app/assets/plutonium.min.js +55 -55
  15. data/app/assets/plutonium.min.js.map +4 -4
  16. data/docs/guides/custom-actions.md +14 -0
  17. data/docs/guides/index.md +5 -0
  18. data/docs/guides/nested-resources.md +139 -32
  19. data/docs/guides/troubleshooting.md +82 -0
  20. data/docs/og-image.html +84 -0
  21. data/docs/public/og-image.png +0 -0
  22. data/docs/reference/controller/index.md +6 -2
  23. data/docs/reference/definition/actions.md +14 -0
  24. data/docs/reference/definition/fields.md +33 -0
  25. data/docs/reference/model/index.md +1 -1
  26. data/docs/reference/policy/index.md +77 -6
  27. data/gemfiles/rails_7.gemfile.lock +5 -5
  28. data/gemfiles/rails_8.0.gemfile.lock +5 -5
  29. data/gemfiles/rails_8.1.gemfile.lock +5 -5
  30. data/lib/generators/pu/rodauth/install_generator.rb +7 -11
  31. data/lib/generators/pu/rodauth/templates/app/rodauth/rodauth_plugin.rb.tt +3 -5
  32. data/lib/plutonium/auth/sequel_adapter.rb +76 -0
  33. data/lib/plutonium/core/controller.rb +143 -19
  34. data/lib/plutonium/core/controllers/association_resolver.rb +86 -0
  35. data/lib/plutonium/helpers/display_helper.rb +12 -0
  36. data/lib/plutonium/query/filters/association.rb +25 -3
  37. data/lib/plutonium/resource/controller.rb +91 -9
  38. data/lib/plutonium/resource/controllers/authorizable.rb +17 -4
  39. data/lib/plutonium/resource/controllers/crud_actions.rb +7 -5
  40. data/lib/plutonium/resource/controllers/interactive_actions.rb +9 -0
  41. data/lib/plutonium/resource/controllers/presentable.rb +15 -11
  42. data/lib/plutonium/resource/policy.rb +85 -2
  43. data/lib/plutonium/resource/record/routes.rb +31 -1
  44. data/lib/plutonium/routing/mapper_extensions.rb +49 -10
  45. data/lib/plutonium/routing/route_set_extensions.rb +3 -0
  46. data/lib/plutonium/ui/action_button.rb +72 -11
  47. data/lib/plutonium/ui/actions_dropdown.rb +3 -25
  48. data/lib/plutonium/ui/breadcrumbs.rb +2 -2
  49. data/lib/plutonium/ui/component/methods.rb +10 -3
  50. data/lib/plutonium/ui/display/resource.rb +5 -2
  51. data/lib/plutonium/ui/form/base.rb +1 -1
  52. data/lib/plutonium/ui/form/components/key_value_store.rb +17 -5
  53. data/lib/plutonium/ui/form/interaction.rb +5 -5
  54. data/lib/plutonium/ui/form/query.rb +1 -1
  55. data/lib/plutonium/ui/form/resource.rb +1 -1
  56. data/lib/plutonium/ui/layout/base.rb +1 -1
  57. data/lib/plutonium/ui/layout/basic_layout.rb +2 -2
  58. data/lib/plutonium/ui/layout/resource_layout.rb +2 -2
  59. data/lib/plutonium/ui/layout/rodauth_layout.rb +2 -2
  60. data/lib/plutonium/ui/page/index.rb +1 -1
  61. data/lib/plutonium/ui/page/interactive_action.rb +1 -1
  62. data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +3 -25
  63. data/lib/plutonium/version.rb +1 -1
  64. data/lib/tasks/release.rake +1 -1
  65. data/package.json +6 -5
  66. data/plutonium.gemspec +2 -2
  67. data/src/js/controllers/key_value_store_controller.js +6 -0
  68. data/src/js/controllers/resource_drop_down_controller.js +3 -3
  69. data/yarn.lock +1465 -693
  70. metadata +10 -7
  71. data/app/javascript/controllers/key_value_store_controller.js +0 -119
@@ -17,6 +17,13 @@ module Plutonium
17
17
  secondary: {default: "pu-btn-secondary", soft: "pu-btn-soft-secondary"}
18
18
  }.freeze
19
19
 
20
+ # Color to CSS class mapping for dropdown item variants
21
+ DROPDOWN_COLOR_CLASSES = {
22
+ danger: "text-danger-600 dark:text-danger-400 hover:bg-danger-50 dark:hover:bg-danger-900/30"
23
+ }.freeze
24
+
25
+ DROPDOWN_DEFAULT_COLOR = "text-[var(--pu-text)] hover:bg-[var(--pu-surface-alt)]"
26
+
20
27
  def initialize(action, url:, variant: :default)
21
28
  @action = action
22
29
  @url = url
@@ -24,24 +31,23 @@ module Plutonium
24
31
  end
25
32
 
26
33
  def view_template
27
- if @action.route_options.method == :get
28
- render_link
34
+ case @variant
35
+ when :dropdown, :row_dropdown
36
+ render_dropdown_item
29
37
  else
30
- render_button
38
+ if @action.route_options.method == :get
39
+ render_link
40
+ else
41
+ render_button
42
+ end
31
43
  end
32
44
  end
33
45
 
34
46
  private
35
47
 
36
48
  def render_link
37
- uri = URI.parse(@url)
38
- params = Rack::Utils.parse_nested_query(uri.query)
39
- params["return_to"] = @action.return_to.nil? ? request.original_url : @action.return_to
40
- uri.query = params.to_query
41
- uri.to_s
42
-
43
49
  link_to(
44
- uri.to_s,
50
+ url_with_return_to,
45
51
  class: button_classes,
46
52
  data: {turbo_frame: @action.turbo_frame}
47
53
  ) do
@@ -53,7 +59,7 @@ module Plutonium
53
59
  button_to(
54
60
  @url,
55
61
  method: @action.route_options.method,
56
- name: :return_to, value: (@action.return_to.nil? ? request.original_url : @action.return_to),
62
+ name: :return_to, value: return_to_url,
57
63
  class: "inline-block",
58
64
  form: {
59
65
  data: {
@@ -69,6 +75,28 @@ module Plutonium
69
75
  end
70
76
  end
71
77
 
78
+ def render_dropdown_item
79
+ link_attrs = {
80
+ href: url_with_return_to,
81
+ class: dropdown_item_classes
82
+ }
83
+
84
+ # Add turbo frame if specified
85
+ link_attrs[:data] = {turbo_frame: @action.turbo_frame} if @action.turbo_frame
86
+
87
+ # Add confirmation and method for non-GET requests
88
+ if @action.confirmation || @action.route_options.method != :get
89
+ link_attrs[:data] ||= {}
90
+ link_attrs[:data][:turbo_method] = @action.route_options.method if @action.route_options.method != :get
91
+ link_attrs[:data][:turbo_confirm] = @action.confirmation if @action.confirmation
92
+ end
93
+
94
+ a(**link_attrs) do
95
+ render @action.icon.new(class: "w-4 h-4") if @action.icon
96
+ span { @action.label }
97
+ end
98
+ end
99
+
72
100
  def render_button_content
73
101
  if @action.icon
74
102
  render @action.icon.new(class: icon_classes)
@@ -100,6 +128,39 @@ module Plutonium
100
128
  # Table variant uses soft (tinted) buttons, default uses solid buttons
101
129
  (@variant == :table) ? color_mapping[:soft] : color_mapping[:default]
102
130
  end
131
+
132
+ def dropdown_item_classes
133
+ base_classes = "flex items-center gap-2 text-sm transition-colors"
134
+ size_classes = (@variant == :row_dropdown) ? "px-3 py-1.5" : "px-4 py-2"
135
+
136
+ # Use same color determination as buttons: color || category
137
+ color_key = (@action.color || @action.category)&.to_sym
138
+ color_classes = DROPDOWN_COLOR_CLASSES[color_key] || DROPDOWN_DEFAULT_COLOR
139
+
140
+ "#{base_classes} #{size_classes} #{color_classes}"
141
+ end
142
+
143
+ def url_with_return_to
144
+ uri = URI.parse(@url)
145
+ params = Rack::Utils.parse_nested_query(uri.query)
146
+ params["return_to"] = return_to_url
147
+ uri.query = params.to_query
148
+ uri.to_s
149
+ end
150
+
151
+ def default_return_to
152
+ # When in a turbo frame with a parent, return to parent's show page
153
+ # instead of the frame's URL (which would be the nested index)
154
+ if current_turbo_frame && current_parent
155
+ resource_url_for(current_parent, parent: nil)
156
+ else
157
+ request.original_url
158
+ end
159
+ end
160
+
161
+ def return_to_url
162
+ @action.return_to.nil? ? default_return_to : @action.return_to
163
+ end
103
164
  end
104
165
  end
105
166
  end
@@ -56,35 +56,13 @@ module Plutonium
56
56
 
57
57
  def render_danger_actions
58
58
  div(class: "py-1") do
59
- danger_actions.each { |action| render_action_item(action, danger: true) }
59
+ danger_actions.each { |action| render_action_item(action) }
60
60
  end
61
61
  end
62
62
 
63
- def render_action_item(action, danger: false)
63
+ def render_action_item(action)
64
64
  url = route_options_to_url(action.route_options, @subject)
65
-
66
- link_attrs = {
67
- href: url,
68
- class: tokens(
69
- "flex items-center gap-2 px-4 py-2 text-sm transition-colors",
70
- danger ? "text-danger-600 dark:text-danger-400 hover:bg-danger-50 dark:hover:bg-danger-900/30" : "text-[var(--pu-text)] hover:bg-[var(--pu-surface-alt)]"
71
- )
72
- }
73
-
74
- # Add turbo frame if specified
75
- link_attrs[:data] = {turbo_frame: action.turbo_frame} if action.turbo_frame
76
-
77
- # Add confirmation if specified
78
- if action.confirmation
79
- link_attrs[:data] ||= {}
80
- link_attrs[:data][:turbo_method] = :delete if action.route_options.method == :delete
81
- link_attrs[:data][:turbo_confirm] = action.confirmation
82
- end
83
-
84
- a(**link_attrs) do
85
- render action.icon.new(class: "w-4 h-4") if action.icon
86
- span { action.label }
87
- end
65
+ render ActionButton.new(action, url: url, variant: :dropdown)
88
66
  end
89
67
 
90
68
  def secondary_actions
@@ -15,7 +15,7 @@ module Plutonium
15
15
  # Dashboard
16
16
  li(class: "inline-flex items-center") do
17
17
  a(
18
- href: helpers.root_path,
18
+ href: root_path,
19
19
  class: "inline-flex items-center text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 transition-colors"
20
20
  ) do
21
21
  svg(
@@ -101,7 +101,7 @@ module Plutonium
101
101
  d: "m1 9 4-4-4-4"
102
102
  )
103
103
  end
104
- link_to resource_name_plural(resource_class),
104
+ link_to nestable_resource_name_plural(resource_class),
105
105
  resource_url_for(resource_class),
106
106
  class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
107
107
  end
@@ -11,15 +11,19 @@ module Plutonium
11
11
  private
12
12
 
13
13
  def params
14
- helpers.controller.params
14
+ view_context.controller.params
15
15
  end
16
16
 
17
17
  def request
18
- helpers.controller.request
18
+ view_context.controller.request
19
19
  end
20
20
 
21
21
  def pagy_instance
22
- helpers.controller.instance_variable_get(:@pagy)
22
+ view_context.controller.instance_variable_get(:@pagy)
23
+ end
24
+
25
+ def controller
26
+ view_context.controller
23
27
  end
24
28
 
25
29
  delegate \
@@ -29,6 +33,7 @@ module Plutonium
29
33
  :resource_record?,
30
34
  :resource_name,
31
35
  :resource_name_plural,
36
+ :nestable_resource_name_plural,
32
37
  :display_name_of,
33
38
  :resource_url_for,
34
39
  :route_options_to_url,
@@ -46,6 +51,8 @@ module Plutonium
46
51
  :allowed_to?,
47
52
  :registered_resources,
48
53
  :root_path,
54
+ :make_page_title,
55
+ :resource_logo_tag,
49
56
  to: :view_context
50
57
  end
51
58
  end
@@ -48,11 +48,14 @@ module Plutonium
48
48
 
49
49
  title = object.class.human_attribute_name(name)
50
50
  src = case reflection.macro
51
- when :belongs_to, :has_one
51
+ when :belongs_to
52
52
  associated = object.public_send name
53
53
  resource_url_for(associated, parent: nil) if associated
54
+ when :has_one
55
+ associated = object.public_send name
56
+ resource_url_for(associated, parent: object, association: name)
54
57
  when :has_many
55
- resource_url_for(reflection.klass, parent: object)
58
+ resource_url_for(reflection.klass, parent: object, association: name)
56
59
  end
57
60
 
58
61
  next unless src
@@ -109,7 +109,7 @@ module Plutonium
109
109
  end
110
110
 
111
111
  def form_action
112
- return @form_action unless object.present? && @form_action != false && helpers.present?
112
+ return @form_action unless object.present? && @form_action != false && view_context.present?
113
113
 
114
114
  @form_action ||= url_for(object, action: object.new_record? ? :create : :update)
115
115
  end
@@ -60,6 +60,12 @@ module Plutonium
60
60
  end
61
61
 
62
62
  def render_key_value_pairs
63
+ # Hidden sentinel input ensures the field is always present in params when the
64
+ # component is rendered. Without this, removing all pairs would submit nothing,
65
+ # making it impossible to distinguish "field not in form" from "field cleared".
66
+ # This allows normalize_input to return nil (preserve existing) vs {} (clear field).
67
+ input(type: :hidden, name: "#{field_name}[_submitted]", value: "1", autocomplete: "off", hidden: true)
68
+
63
69
  div(class: "key-value-pairs space-y-2", data_key_value_store_target: "container") do
64
70
  pairs.each_with_index do |(key, value), index|
65
71
  render_key_value_pair(key, value, index)
@@ -196,19 +202,25 @@ module Plutonium
196
202
  attributes.fetch(:limit, DEFAULT_LIMIT)
197
203
  end
198
204
 
199
- # Override from ExtractsInput concern to normalize form parameters
205
+ # Override from ExtractsInput concern to normalize form parameters.
206
+ # Returns nil if field wasn't submitted (preserves existing value),
207
+ # or a Hash (possibly empty) if the field was in the form.
200
208
  def normalize_input(input_value)
201
209
  case input_value
202
210
  when Hash
203
- if input_value.keys.all? { |k| k.to_s.match?(/^\d+$/) }
211
+ # Remove the sentinel key before processing
212
+ params = input_value.except("_submitted", :_submitted)
213
+
214
+ if params.keys.all? { |k| k.to_s.match?(/^\d+$/) }
204
215
  # Handle indexed form params: {"0" => {"key" => "foo", "value" => "bar"}}
205
- process_indexed_params(input_value)
216
+ process_indexed_params(params)
206
217
  else
207
218
  # Handle direct hash params
208
- input_value.reject { |k, v| k.blank? || (v.blank? && v != false) }
219
+ params.reject { |k, v| k.blank? || (v.blank? && v != false) }
209
220
  end
210
221
  when nil
211
- {}
222
+ # Field was not submitted at all - preserve existing value
223
+ nil
212
224
  end
213
225
  end
214
226
 
@@ -16,7 +16,7 @@ module Plutonium
16
16
 
17
17
  def form_action
18
18
  # Build the correct commit URL for the interactive action
19
- action = helpers.current_interactive_action
19
+ action = current_interactive_action
20
20
  return nil unless action
21
21
 
22
22
  # Create route options for the commit action (convert GET to POST action)
@@ -28,8 +28,8 @@ module Plutonium
28
28
  )
29
29
 
30
30
  # Use existing infrastructure to build the URL
31
- subject = action.record_action? ? helpers.resource_record! : helpers.resource_class
32
- helpers.route_options_to_url(commit_route_options, subject)
31
+ subject = action.record_action? ? resource_record! : resource_class
32
+ route_options_to_url(commit_route_options, subject)
33
33
  end
34
34
 
35
35
  def commit_action_name(action_name)
@@ -52,10 +52,10 @@ module Plutonium
52
52
  end
53
53
 
54
54
  def render_bulk_action_ids
55
- action = helpers.current_interactive_action
55
+ action = current_interactive_action
56
56
  return unless action&.bulk_action?
57
57
 
58
- ids = Array(helpers.params[:ids])
58
+ ids = Array(params[:ids])
59
59
  ids.each do |id|
60
60
  input(type: :hidden, name: "ids[]", value: id)
61
61
  end
@@ -207,7 +207,7 @@ module Plutonium
207
207
  def count_active_filters
208
208
  count = 0
209
209
  query_object.filter_definitions.each do |filter_name, _|
210
- filter_params = helpers.params.dig(:q, filter_name)
210
+ filter_params = params.dig(:q, filter_name)
211
211
  next unless filter_params.is_a?(Hash) || filter_params.is_a?(ActionController::Parameters)
212
212
 
213
213
  filter_params.each_value do |v|
@@ -58,7 +58,7 @@ module Plutonium
58
58
  end
59
59
 
60
60
  def form_action
61
- return @form_action unless object.present? && @form_action != false && helpers.present?
61
+ return @form_action unless object.present? && @form_action != false && view_context.present?
62
62
 
63
63
  @form_action ||= resource_url_for(object, action: object.new_record? ? :create : :update)
64
64
  end
@@ -22,7 +22,7 @@ module Plutonium
22
22
 
23
23
  def lang = nil
24
24
 
25
- def page_title = helpers.controller.instance_variable_get(:@page_title)
25
+ def page_title = view_context.controller.instance_variable_get(:@page_title)
26
26
 
27
27
  def html_attributes = {lang:, data_controller: "color-mode"}
28
28
 
@@ -5,8 +5,8 @@ module Plutonium
5
5
  private
6
6
 
7
7
  def page_title
8
- helpers.make_page_title(
9
- helpers.controller.instance_variable_get(:@page_title)
8
+ make_page_title(
9
+ controller.instance_variable_get(:@page_title)
10
10
  )
11
11
  end
12
12
  end
@@ -9,8 +9,8 @@ module Plutonium
9
9
  })
10
10
 
11
11
  def page_title
12
- helpers.make_page_title(
13
- helpers.controller.instance_variable_get(:@page_title)
12
+ make_page_title(
13
+ controller.instance_variable_get(:@page_title)
14
14
  )
15
15
  end
16
16
 
@@ -7,7 +7,7 @@ module Plutonium
7
7
  private
8
8
 
9
9
  def page_title
10
- helpers.controller.instance_variable_get(:@page_title)
10
+ controller.instance_variable_get(:@page_title)
11
11
  end
12
12
 
13
13
  def main_attributes = mix(super, {
@@ -26,7 +26,7 @@ module Plutonium
26
26
 
27
27
  def render_logo
28
28
  link_to root_path, class: "flex items-center text-2xl font-semibold text-[var(--pu-text)] mb-2" do
29
- helpers.resource_logo_tag classname: "w-24 h-24 mr-2 rounded-[var(--pu-radius-md)]"
29
+ resource_logo_tag classname: "w-24 h-24 mr-2 rounded-[var(--pu-radius-md)]"
30
30
  end
31
31
  end
32
32
 
@@ -7,7 +7,7 @@ module Plutonium
7
7
  private
8
8
 
9
9
  def page_title
10
- super || current_definition.index_page_title || resource_name_plural(resource_class)
10
+ super || current_definition.index_page_title || nestable_resource_name_plural(resource_class)
11
11
  end
12
12
 
13
13
  def page_description
@@ -17,7 +17,7 @@ module Plutonium
17
17
  end
18
18
 
19
19
  def render_default_content
20
- if helpers.current_turbo_frame == "remote_modal"
20
+ if current_turbo_frame == "remote_modal"
21
21
  dialog(
22
22
  closedby: "any",
23
23
  class: "rounded-[var(--pu-radius-lg)] w-full max-w-3xl
@@ -56,35 +56,13 @@ module Plutonium
56
56
 
57
57
  def render_danger_actions
58
58
  div(class: "py-1") do
59
- danger_actions.each { |action| render_action_item(action, danger: true) }
59
+ danger_actions.each { |action| render_action_item(action) }
60
60
  end
61
61
  end
62
62
 
63
- def render_action_item(action, danger: false)
63
+ def render_action_item(action)
64
64
  url = route_options_to_url(action.route_options, @record)
65
-
66
- link_attrs = {
67
- href: url,
68
- class: tokens(
69
- "flex items-center gap-2 px-3 py-1.5 text-sm transition-colors",
70
- danger ? "text-danger-600 dark:text-danger-400 hover:bg-danger-50 dark:hover:bg-danger-900/30" : "text-[var(--pu-text)] hover:bg-[var(--pu-surface-alt)]"
71
- )
72
- }
73
-
74
- # Add turbo frame if specified
75
- link_attrs[:data] = {turbo_frame: action.turbo_frame} if action.turbo_frame
76
-
77
- # Add confirmation if specified
78
- if action.confirmation
79
- link_attrs[:data] ||= {}
80
- link_attrs[:data][:turbo_method] = action.route_options.method if action.route_options.method
81
- link_attrs[:data][:turbo_confirm] = action.confirmation
82
- end
83
-
84
- a(**link_attrs) do
85
- render action.icon.new(class: "w-4 h-4") if action.icon
86
- span { action.label }
87
- end
65
+ render Plutonium::UI::ActionButton.new(action, url: url, variant: :row_dropdown)
88
66
  end
89
67
 
90
68
  def secondary_actions
@@ -1,5 +1,5 @@
1
1
  module Plutonium
2
- VERSION = "0.37.0"
2
+ VERSION = "0.39.0"
3
3
  NEXT_MAJOR_VERSION = VERSION.split(".").tap { |v|
4
4
  v[1] = v[1].to_i + 1
5
5
  v[2] = 0
@@ -102,7 +102,7 @@ namespace :release do
102
102
  desc "Build front-end assets"
103
103
  task :build_frontend do
104
104
  puts "Building front-end assets..."
105
- system("npm run build") || abort("Front-end build failed")
105
+ system("yarn build") || abort("Front-end build failed")
106
106
  puts "✓ Built front-end assets"
107
107
  end
108
108
 
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radioactive-labs/plutonium",
3
- "version": "0.37.0",
3
+ "version": "0.39.0",
4
4
  "description": "Build production-ready Rails apps in minutes, not days. Convention-driven, fully customizable, AI-ready.",
5
5
  "type": "module",
6
6
  "main": "src/js/core.js",
@@ -33,6 +33,7 @@
33
33
  "@tailwindcss/forms": "^0.5.10",
34
34
  "@tailwindcss/postcss": "^4.0.9",
35
35
  "@tailwindcss/typography": "^0.5.16",
36
+ "chokidar-cli": "^3.0.0",
36
37
  "concurrently": "^8.2.2",
37
38
  "cssnano": "^7.0.2",
38
39
  "esbuild": "^0.20.1",
@@ -48,10 +49,10 @@
48
49
  "vitepress-plugin-mermaid": "^2.0.17"
49
50
  },
50
51
  "scripts": {
51
- "dev": "concurrently \"npm run css:dev\" \"npm run js:dev\"",
52
- "build": "npm run js:prod && npm run css:prod",
53
- "prepare": "npm run build",
54
- "css:dev": "postcss src/css/plutonium.entry.css -o src/build/plutonium.css --watch --dev",
52
+ "dev": "concurrently \"yarn css:dev\" \"yarn js:dev\"",
53
+ "build": "yarn js:prod && yarn css:prod",
54
+ "prepare": "yarn build",
55
+ "css:dev": "chokidar \"src/css/**/*.css\" \"src/js/**/*.js\" \"app/views/**/*.{rb,erb,js}\" \"lib/plutonium/**/*.rb\" -c \"postcss src/css/plutonium.entry.css -o src/build/plutonium.css --dev\" --initial",
55
56
  "js:dev": "node esbuild.config.js --dev",
56
57
  "css:prod": "postcss src/css/plutonium.entry.css -o app/assets/plutonium.css && postcss src/css/plutonium.entry.css -o src/dist/css/plutonium.css",
57
58
  "js:prod": "node esbuild.config.js",
data/plutonium.gemspec CHANGED
@@ -44,10 +44,10 @@ Gem::Specification.new do |spec|
44
44
  spec.add_dependency "phlex-rails"
45
45
  spec.add_dependency "phlex-tabler_icons"
46
46
  spec.add_dependency "phlexi-field", ">= 0.2.0"
47
- spec.add_dependency "phlexi-form", ">= 0.13.0"
47
+ spec.add_dependency "phlexi-form", ">= 0.14.1"
48
48
  spec.add_dependency "phlexi-table", ">= 0.2.0"
49
49
  spec.add_dependency "phlexi-display", ">= 0.2.0"
50
- spec.add_dependency "phlexi-menu", ">= 0.4.0"
50
+ spec.add_dependency "phlexi-menu", ">= 0.4.1"
51
51
  spec.add_dependency "tailwind_merge"
52
52
  spec.add_dependency "phlex-slotable", ">= 1.0.0"
53
53
  spec.add_dependency "redcarpet"
@@ -25,6 +25,7 @@ export default class extends Controller {
25
25
  this.updatePairIndices(newPair, index)
26
26
 
27
27
  this.containerTarget.appendChild(newPair)
28
+ this.updateIndices()
28
29
  this.updateAddButtonState()
29
30
 
30
31
  // Focus on the key input of the new pair
@@ -52,9 +53,11 @@ export default class extends Controller {
52
53
 
53
54
  if (keyInput) {
54
55
  keyInput.name = keyInput.name.replace(/\[\d+\]/, `[${index}]`)
56
+ keyInput.id = keyInput.id.replace(/_\d+_/, `_${index}_`)
55
57
  }
56
58
  if (valueInput) {
57
59
  valueInput.name = valueInput.name.replace(/\[\d+\]/, `[${index}]`)
60
+ valueInput.id = valueInput.id.replace(/_\d+_/, `_${index}_`)
58
61
  }
59
62
  })
60
63
  }
@@ -65,6 +68,9 @@ export default class extends Controller {
65
68
  if (input.name) {
66
69
  input.name = input.name.replace('__INDEX__', index)
67
70
  }
71
+ if (input.id) {
72
+ input.id = input.id.replace('___INDEX___', `_${index}_`)
73
+ }
68
74
  })
69
75
  }
70
76
 
@@ -42,14 +42,14 @@ export default class extends Controller {
42
42
  {
43
43
  name: 'flip',
44
44
  options: {
45
- fallbackPlacements: ['left-end', 'right-start', 'right-end', 'bottom-start', 'bottom-end', 'top-start', 'top-end'],
46
- boundary: 'clippingParents',
45
+ fallbackPlacements: ['bottom-end', 'bottom-start', 'top', 'top-end', 'top-start'],
46
+ boundary: 'viewport',
47
47
  },
48
48
  },
49
49
  {
50
50
  name: 'preventOverflow',
51
51
  options: {
52
- boundary: 'clippingParents',
52
+ boundary: 'viewport',
53
53
  altAxis: true,
54
54
  padding: 8,
55
55
  },