plutonium 0.37.0 → 0.38.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 +4 -4
- data/.claude/skills/plutonium-controller/SKILL.md +25 -2
- data/.claude/skills/plutonium-definition-fields/SKILL.md +33 -0
- data/.claude/skills/plutonium-nested-resources/SKILL.md +79 -19
- data/.claude/skills/plutonium-policy/SKILL.md +93 -6
- data/CHANGELOG.md +36 -0
- data/CLAUDE.md +8 -10
- data/CONTRIBUTING.md +6 -8
- data/Rakefile +16 -1
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.js +9371 -11492
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +55 -55
- data/app/assets/plutonium.min.js.map +4 -4
- data/docs/guides/index.md +5 -0
- data/docs/guides/nested-resources.md +132 -29
- data/docs/guides/troubleshooting.md +82 -0
- data/docs/reference/controller/index.md +1 -1
- data/docs/reference/definition/fields.md +33 -0
- data/docs/reference/model/index.md +1 -1
- data/docs/reference/policy/index.md +77 -6
- data/gemfiles/rails_7.gemfile.lock +3 -3
- data/gemfiles/rails_8.0.gemfile.lock +3 -3
- data/gemfiles/rails_8.1.gemfile.lock +3 -3
- data/lib/plutonium/core/controller.rb +144 -19
- data/lib/plutonium/core/controllers/association_resolver.rb +86 -0
- data/lib/plutonium/helpers/display_helper.rb +12 -0
- data/lib/plutonium/query/filters/association.rb +25 -3
- data/lib/plutonium/resource/controller.rb +90 -9
- data/lib/plutonium/resource/controllers/authorizable.rb +17 -4
- data/lib/plutonium/resource/controllers/crud_actions.rb +7 -5
- data/lib/plutonium/resource/controllers/interactive_actions.rb +9 -0
- data/lib/plutonium/resource/controllers/presentable.rb +13 -11
- data/lib/plutonium/resource/policy.rb +85 -2
- data/lib/plutonium/resource/record/routes.rb +31 -1
- data/lib/plutonium/routing/mapper_extensions.rb +40 -4
- data/lib/plutonium/routing/route_set_extensions.rb +3 -0
- data/lib/plutonium/ui/breadcrumbs.rb +1 -1
- data/lib/plutonium/ui/display/resource.rb +5 -2
- data/lib/plutonium/ui/form/components/key_value_store.rb +17 -5
- data/lib/plutonium/ui/page/index.rb +1 -1
- data/lib/plutonium/version.rb +1 -1
- data/lib/tasks/release.rake +1 -1
- data/package.json +6 -5
- data/plutonium.gemspec +1 -1
- data/src/js/controllers/key_value_store_controller.js +6 -0
- data/src/js/controllers/resource_drop_down_controller.js +3 -3
- data/yarn.lock +1465 -693
- metadata +6 -5
- data/app/javascript/controllers/key_value_store_controller.js +0 -119
|
@@ -24,16 +24,18 @@ module Plutonium
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def submittable_attributes
|
|
27
|
-
@submittable_attributes ||=
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
@submittable_attributes ||= submittable_attributes_for(action_name)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def submittable_attributes_for(action)
|
|
31
|
+
submittable_attributes = permitted_attributes_for(action)
|
|
32
|
+
if current_parent && !submit_parent?
|
|
33
|
+
submittable_attributes -= [parent_input_param, :"#{parent_input_param}_id"]
|
|
34
|
+
end
|
|
35
|
+
if scoped_to_entity? && !submit_scoped_entity?
|
|
36
|
+
submittable_attributes -= [scoped_entity_param_key, :"#{scoped_entity_param_key}_id"]
|
|
36
37
|
end
|
|
38
|
+
submittable_attributes
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
def build_collection
|
|
@@ -44,8 +46,8 @@ module Plutonium
|
|
|
44
46
|
current_definition.detail_class.new(resource_record!, resource_fields: presentable_attributes, resource_associations: permitted_associations, resource_definition: current_definition)
|
|
45
47
|
end
|
|
46
48
|
|
|
47
|
-
def build_form(record = resource_record
|
|
48
|
-
current_definition.form_class.new(record, resource_fields:
|
|
49
|
+
def build_form(record = resource_record!, action: action_name)
|
|
50
|
+
current_definition.form_class.new(record, resource_fields: submittable_attributes_for(action), resource_definition: current_definition)
|
|
49
51
|
end
|
|
50
52
|
|
|
51
53
|
def present_parent? = false
|
|
@@ -8,11 +8,82 @@ module Plutonium
|
|
|
8
8
|
class Policy < ::ActionPolicy::Base
|
|
9
9
|
authorize :user, allow_nil: false
|
|
10
10
|
authorize :entity_scope, allow_nil: true
|
|
11
|
+
authorize :parent, optional: true
|
|
12
|
+
authorize :parent_association, optional: true
|
|
11
13
|
|
|
12
14
|
relation_scope do |relation|
|
|
13
|
-
|
|
15
|
+
default_relation_scope(relation)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Wraps apply_scope to verify default_relation_scope was called.
|
|
19
|
+
# This prevents accidental multi-tenancy leaks when overriding relation_scope.
|
|
20
|
+
def apply_scope(relation, type:, **options)
|
|
21
|
+
@_default_relation_scope_applied = false
|
|
22
|
+
result = super
|
|
23
|
+
verify_default_relation_scope_applied! if type == :active_record_relation
|
|
24
|
+
result
|
|
25
|
+
end
|
|
14
26
|
|
|
15
|
-
|
|
27
|
+
# Explicitly skip the default relation scope verification.
|
|
28
|
+
#
|
|
29
|
+
# Call this when you intentionally want to bypass parent/entity scoping.
|
|
30
|
+
# This should be rare - consider using a separate portal instead.
|
|
31
|
+
#
|
|
32
|
+
# @example Skipping default scoping (use sparingly)
|
|
33
|
+
# relation_scope do |relation|
|
|
34
|
+
# skip_default_relation_scope!
|
|
35
|
+
# relation.where(featured: true) # No parent/entity scoping
|
|
36
|
+
# end
|
|
37
|
+
def skip_default_relation_scope!
|
|
38
|
+
@_default_relation_scope_applied = true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Applies Plutonium's default scoping (parent or entity) to a relation.
|
|
42
|
+
#
|
|
43
|
+
# This method MUST be called in any custom relation_scope to ensure proper
|
|
44
|
+
# parent/entity scoping. Failure to call it will raise an error.
|
|
45
|
+
#
|
|
46
|
+
# @example Overriding inherited scope while keeping default scoping
|
|
47
|
+
# # Parent policy has custom filtering you want to replace
|
|
48
|
+
# class AdminPostPolicy < PostPolicy
|
|
49
|
+
# relation_scope do |relation|
|
|
50
|
+
# # Replace inherited scope but keep Plutonium's parent/entity scoping
|
|
51
|
+
# default_relation_scope(relation)
|
|
52
|
+
# end
|
|
53
|
+
# end
|
|
54
|
+
#
|
|
55
|
+
# @example Adding filtering on top of default scoping
|
|
56
|
+
# relation_scope do |relation|
|
|
57
|
+
# default_relation_scope(relation).where(published: true)
|
|
58
|
+
# end
|
|
59
|
+
#
|
|
60
|
+
# @param relation [ActiveRecord::Relation] The relation to scope
|
|
61
|
+
# @return [ActiveRecord::Relation] The scoped relation
|
|
62
|
+
def default_relation_scope(relation)
|
|
63
|
+
@_default_relation_scope_applied = true
|
|
64
|
+
|
|
65
|
+
if parent || parent_association
|
|
66
|
+
unless parent && parent_association
|
|
67
|
+
raise ArgumentError, "parent and parent_association must both be provided together"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Parent association scoping (nested routes)
|
|
71
|
+
# The parent was already entity-scoped during authorization, so children
|
|
72
|
+
# accessed through the parent don't need additional entity scoping
|
|
73
|
+
assoc_reflection = parent.class.reflect_on_association(parent_association)
|
|
74
|
+
if assoc_reflection.collection?
|
|
75
|
+
# has_many: merge with the association's scope
|
|
76
|
+
parent.public_send(parent_association).merge(relation)
|
|
77
|
+
else
|
|
78
|
+
# has_one: scope by foreign key
|
|
79
|
+
relation.where(assoc_reflection.foreign_key => parent.id)
|
|
80
|
+
end
|
|
81
|
+
elsif entity_scope
|
|
82
|
+
# Entity scoping (multi-tenancy)
|
|
83
|
+
relation.associated_with(entity_scope)
|
|
84
|
+
else
|
|
85
|
+
relation
|
|
86
|
+
end
|
|
16
87
|
end
|
|
17
88
|
|
|
18
89
|
# Sends a method and raises an error if the method is not implemented.
|
|
@@ -162,6 +233,18 @@ module Plutonium
|
|
|
162
233
|
record.instance_of?(Class) ? record : record.class
|
|
163
234
|
end
|
|
164
235
|
|
|
236
|
+
# Verifies that default_relation_scope was called during scope application.
|
|
237
|
+
# Raises an error if it wasn't, preventing accidental multi-tenancy leaks.
|
|
238
|
+
def verify_default_relation_scope_applied!
|
|
239
|
+
return if @_default_relation_scope_applied
|
|
240
|
+
|
|
241
|
+
raise <<~MSG.squish
|
|
242
|
+
#{self.class.name} did not call `default_relation_scope` in its relation_scope.
|
|
243
|
+
This can cause multi-tenancy leaks. Either call `default_relation_scope(relation)`
|
|
244
|
+
or `super(relation)` in your relation_scope block.
|
|
245
|
+
MSG
|
|
246
|
+
end
|
|
247
|
+
|
|
165
248
|
# Autodetects the permitted fields for a given method.
|
|
166
249
|
#
|
|
167
250
|
# @param method_name [Symbol] The name of the method.
|
|
@@ -12,10 +12,40 @@ module Plutonium
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
class_methods do
|
|
15
|
+
# Returns metadata for has_many associations that can be routed
|
|
16
|
+
# @return [Array<Hash>] Array of hashes with :name, :klass, :plural keys
|
|
17
|
+
def routable_has_many_associations
|
|
18
|
+
return @routable_has_many_associations if defined?(@routable_has_many_associations) && !Rails.env.local?
|
|
19
|
+
|
|
20
|
+
@routable_has_many_associations = reflect_on_all_associations(:has_many).map do |assoc|
|
|
21
|
+
{name: assoc.name, klass: assoc.klass, plural: assoc.klass.model_name.plural}
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Returns metadata for has_one associations that can be routed
|
|
26
|
+
# @return [Array<Hash>] Array of hashes with :name, :klass, :plural keys
|
|
27
|
+
def routable_has_one_associations
|
|
28
|
+
return @routable_has_one_associations if defined?(@routable_has_one_associations) && !Rails.env.local?
|
|
29
|
+
|
|
30
|
+
@routable_has_one_associations = reflect_on_all_associations(:has_one)
|
|
31
|
+
.reject { |assoc| assoc.options[:through] }
|
|
32
|
+
.map do |assoc|
|
|
33
|
+
{name: assoc.name, klass: assoc.klass, plural: assoc.klass.model_name.plural}
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @deprecated Use routable_has_many_associations instead
|
|
15
38
|
def has_many_association_routes
|
|
16
39
|
return @has_many_association_routes if defined?(@has_many_association_routes) && !Rails.env.local?
|
|
17
40
|
|
|
18
|
-
@has_many_association_routes =
|
|
41
|
+
@has_many_association_routes = routable_has_many_associations.map { |info| info[:plural] }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @deprecated Use routable_has_one_associations instead
|
|
45
|
+
def has_one_association_routes
|
|
46
|
+
return @has_one_association_routes if defined?(@has_one_association_routes) && !Rails.env.local?
|
|
47
|
+
|
|
48
|
+
@has_one_association_routes = routable_has_one_associations.map { |info| info[:plural] }
|
|
19
49
|
end
|
|
20
50
|
|
|
21
51
|
def all_nested_attributes_options
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Plutonium
|
|
4
4
|
module Routing
|
|
5
|
+
# Prefix used for nested resource routes to disambiguate from user-defined routes
|
|
6
|
+
NESTED_ROUTE_PREFIX = "nested_"
|
|
7
|
+
|
|
5
8
|
# MapperExtensions module provides additional functionality for route mapping in Plutonium applications.
|
|
6
9
|
#
|
|
7
10
|
# This module extends the functionality of Rails' routing mapper to support Plutonium-specific features,
|
|
@@ -77,10 +80,43 @@ module Plutonium
|
|
|
77
80
|
# @param resource [Class] The parent resource class.
|
|
78
81
|
# @return [void]
|
|
79
82
|
def define_nested_resource_routes(resource)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
# has_many associations use plural routes
|
|
84
|
+
resource.routable_has_many_associations.each do |assoc_info|
|
|
85
|
+
base_config = route_set.resource_route_config_for(assoc_info[:plural])[0]
|
|
86
|
+
next unless base_config
|
|
87
|
+
|
|
88
|
+
# Register with association-based key: "parent_plural/association_name"
|
|
89
|
+
nested_key = "#{resource.model_name.plural}/#{assoc_info[:name]}"
|
|
90
|
+
nested_config = base_config.merge(
|
|
91
|
+
association_name: assoc_info[:name],
|
|
92
|
+
resource_class: assoc_info[:klass]
|
|
93
|
+
)
|
|
94
|
+
route_set.resource_route_config_lookup[nested_key] = nested_config
|
|
95
|
+
|
|
96
|
+
resources "#{NESTED_ROUTE_PREFIX}#{assoc_info[:name]}", **base_config[:route_options].except(:path) do
|
|
97
|
+
instance_exec(&base_config[:block]) if base_config[:block]
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# has_one associations use singular routes
|
|
102
|
+
resource.routable_has_one_associations.each do |assoc_info|
|
|
103
|
+
base_config = route_set.resource_route_config_for(assoc_info[:plural])[0]
|
|
104
|
+
next unless base_config
|
|
105
|
+
|
|
106
|
+
# Register with association-based key and singular route type
|
|
107
|
+
nested_key = "#{resource.model_name.plural}/#{assoc_info[:name]}"
|
|
108
|
+
nested_config = base_config.merge(
|
|
109
|
+
route_type: :resource,
|
|
110
|
+
association_name: assoc_info[:name],
|
|
111
|
+
resource_class: assoc_info[:klass]
|
|
112
|
+
)
|
|
113
|
+
route_set.resource_route_config_lookup[nested_key] = nested_config
|
|
114
|
+
|
|
115
|
+
resource "#{NESTED_ROUTE_PREFIX}#{assoc_info[:name]}", **base_config[:route_options].except(:path) do
|
|
116
|
+
original_collection = method(:collection)
|
|
117
|
+
define_singleton_method(:collection) { |&_| } # no-op for singular resources
|
|
118
|
+
instance_exec(&base_config[:block]) if base_config[:block]
|
|
119
|
+
define_singleton_method(:collection, original_collection)
|
|
84
120
|
end
|
|
85
121
|
end
|
|
86
122
|
end
|
|
@@ -83,6 +83,8 @@ module Plutonium
|
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
# @return [Hash] A lookup table for resource route configurations.
|
|
86
|
+
# Keys are either plural names (e.g., "profiles") for top-level routes
|
|
87
|
+
# or "parent_plural/child_plural" (e.g., "users/profiles") for nested routes.
|
|
86
88
|
def resource_route_config_lookup
|
|
87
89
|
@resource_route_config_lookup ||= {}
|
|
88
90
|
end
|
|
@@ -107,6 +109,7 @@ module Plutonium
|
|
|
107
109
|
# @return [Hash] The complete resource configuration.
|
|
108
110
|
def create_resource_config(resource, route_name, concern_name, options = {}, &block)
|
|
109
111
|
{
|
|
112
|
+
resource_class: resource,
|
|
110
113
|
route_type: options[:singular] ? :resource : :resources,
|
|
111
114
|
route_name: route_name,
|
|
112
115
|
concern_name: concern_name,
|
|
@@ -101,7 +101,7 @@ module Plutonium
|
|
|
101
101
|
d: "m1 9 4-4-4-4"
|
|
102
102
|
)
|
|
103
103
|
end
|
|
104
|
-
link_to
|
|
104
|
+
link_to helpers.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
|
|
@@ -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
|
|
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
|
|
@@ -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
|
-
|
|
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(
|
|
216
|
+
process_indexed_params(params)
|
|
206
217
|
else
|
|
207
218
|
# Handle direct hash params
|
|
208
|
-
|
|
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
|
|
|
@@ -7,7 +7,7 @@ module Plutonium
|
|
|
7
7
|
private
|
|
8
8
|
|
|
9
9
|
def page_title
|
|
10
|
-
super || current_definition.index_page_title ||
|
|
10
|
+
super || current_definition.index_page_title || helpers.nestable_resource_name_plural(resource_class)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def page_description
|
data/lib/plutonium/version.rb
CHANGED
data/lib/tasks/release.rake
CHANGED
|
@@ -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("
|
|
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.
|
|
3
|
+
"version": "0.38.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 \"
|
|
52
|
-
"build": "
|
|
53
|
-
"prepare": "
|
|
54
|
-
"css:dev": "postcss src/css/plutonium.entry.css -o src/build/plutonium.css --
|
|
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,7 +44,7 @@ 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.
|
|
47
|
+
spec.add_dependency "phlexi-form", ">= 0.14.0"
|
|
48
48
|
spec.add_dependency "phlexi-table", ">= 0.2.0"
|
|
49
49
|
spec.add_dependency "phlexi-display", ">= 0.2.0"
|
|
50
50
|
spec.add_dependency "phlexi-menu", ">= 0.4.0"
|
|
@@ -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: ['
|
|
46
|
-
boundary: '
|
|
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: '
|
|
52
|
+
boundary: 'viewport',
|
|
53
53
|
altAxis: true,
|
|
54
54
|
padding: 8,
|
|
55
55
|
},
|