plutonium 0.59.0 → 0.60.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.
@@ -98,11 +98,81 @@ module Plutonium
98
98
  end
99
99
 
100
100
  def render_fields
101
- fields_wrapper {
102
- resource_fields.each { |name|
103
- render_resource_field name
101
+ sections = resolve_form_layout
102
+ if sections.nil?
103
+ fields_wrapper {
104
+ resource_fields.each { |name| render_resource_field name }
104
105
  }
105
- }
106
+ else
107
+ sections.each { |rs| render_form_section(rs) }
108
+ end
109
+ end
110
+
111
+ # Resolve the whole form layout for THIS render, in one pass: drop
112
+ # condition-hidden sections and evaluate any proc-valued options, all in
113
+ # the form instance context (where `object`, `current_user`, `params` and
114
+ # helpers live — same context as input/section `condition:`). Returns nil
115
+ # when no form_layout is declared (caller falls back to a single grid).
116
+ def resolve_form_layout
117
+ sections = resource_definition.resolve_form_sections(resource_fields)
118
+ return nil if sections.nil?
119
+
120
+ sections.filter_map do |resolved|
121
+ section = resolved.section
122
+ condition = section.condition
123
+ next if condition && !instance_exec(&condition)
124
+
125
+ # `columns` stays a validated literal; everything else may be a proc.
126
+ options = section.options.to_h do |key, value|
127
+ [key, (key != :condition && value.is_a?(Proc)) ? instance_exec(&value) : value]
128
+ end
129
+ Plutonium::Definition::FormLayout::ResolvedSection.new(
130
+ section: Plutonium::Definition::FormLayout::Section.new(
131
+ key: section.key, fields: section.fields, options: options.freeze
132
+ ),
133
+ fields: resolved.fields
134
+ )
135
+ end
136
+ end
137
+
138
+ # Pure presentation — the section is already resolved (visible, options
139
+ # evaluated) by resolve_form_layout.
140
+ def render_form_section(resolved)
141
+ section = resolved.section
142
+ render Plutonium::UI::Form::Components::Section.new(
143
+ resolved, grid_class: section_grid_class(section.columns)
144
+ ) do
145
+ # Inside a multi-column section, let fields flow into the grid cells
146
+ # instead of forcing each to span the full row (see col-span default
147
+ # in render_simple_resource_field).
148
+ previous = @section_columns
149
+ @section_columns = section.columns
150
+ begin
151
+ resolved.fields.each { |name| render_resource_field name }
152
+ ensure
153
+ @section_columns = previous
154
+ end
155
+ end
156
+ end
157
+
158
+ # True while rendering fields inside a section that declared an explicit
159
+ # column count > 1. Such fields default to a single grid cell rather than
160
+ # `col-span-full`, so the section's grid actually lays out in columns.
161
+ def in_multi_column_section?
162
+ @section_columns.to_i > 1
163
+ end
164
+
165
+ # nil → the form's default responsive grid; an Integer overrides columns.
166
+ def section_grid_class(columns)
167
+ return themed(:fields_wrapper, nil) if columns.nil?
168
+
169
+ base = "grid gap-6 grid-flow-row-dense grid-cols-1"
170
+ case columns.to_i
171
+ when 1 then base
172
+ when 2 then "#{base} md:grid-cols-2"
173
+ when 3 then "#{base} md:grid-cols-2 lg:grid-cols-3"
174
+ else "#{base} md:grid-cols-2 2xl:grid-cols-#{columns.to_i}"
175
+ end
106
176
  end
107
177
 
108
178
  def render_actions
@@ -232,10 +302,18 @@ module Plutonium
232
302
  end
233
303
  else
234
304
  wrapper_options = input_options[:wrapper] || {}
305
+ # Only supply a default column span when the field hasn't declared its
306
+ # own (via `wrapper: {class: "col-span-..."}`). A field-level col-span
307
+ # ALWAYS wins — including inside a section with `columns:` — so authors
308
+ # can opt a single field back to full width in a multi-column section,
309
+ # or vice versa.
310
+ # TODO: remove the string check once theming supports class merges.
235
311
  if !wrapper_options[:class] || !wrapper_options[:class].include?("col-span")
236
- # temp hack to allow col span overrides
237
- # TODO: remove once we complete theming, which will support merges
238
- wrapper_options[:class] = tokens("col-span-full", wrapper_options[:class])
312
+ # In a multi-column section the field flows into a single grid cell
313
+ # (no col-span), so the declared `columns:` actually takes effect.
314
+ # Everywhere else fields span the full row.
315
+ default_span = in_multi_column_section? ? nil : "col-span-full"
316
+ wrapper_options[:class] = tokens(default_span, wrapper_options[:class])
239
317
  end
240
318
 
241
319
  render form.field(name, **field_options).wrapped(
@@ -1,5 +1,5 @@
1
1
  module Plutonium
2
- VERSION = "0.59.0"
2
+ VERSION = "0.60.0"
3
3
  NEXT_MAJOR_VERSION = VERSION.split(".").tap { |v|
4
4
  v[1] = v[1].to_i + 1
5
5
  v[2] = 0
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radioactive-labs/plutonium",
3
- "version": "0.59.0",
3
+ "version": "0.60.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",
@@ -43,9 +43,11 @@
43
43
  border-radius: var(--pu-radius-md);
44
44
  color: var(--pu-text);
45
45
  }
46
+
46
47
  .ss-main:hover {
47
48
  border-color: var(--pu-border-strong);
48
49
  }
50
+
49
51
  .ss-main:focus,
50
52
  .ss-main[aria-expanded="true"] {
51
53
  border-color: var(--pu-input-focus-ring);
@@ -196,9 +198,11 @@
196
198
  font-size: inherit;
197
199
  line-height: inherit;
198
200
  }
201
+
199
202
  .ss-content .ss-search input::placeholder {
200
203
  color: var(--pu-input-placeholder);
201
204
  }
205
+
202
206
  .ss-content .ss-search input:focus {
203
207
  border-color: var(--pu-input-focus-ring);
204
208
  box-shadow: 0 0 0 3px theme(colors.primary.500 / 15%);
@@ -225,7 +229,7 @@
225
229
 
226
230
  /* List */
227
231
  .ss-content .ss-list {
228
- @apply flex-auto h-auto overflow-x-hidden overflow-y-auto;
232
+ @apply flex-auto h-auto overflow-x-hidden overflow-y-auto mb-2;
229
233
  }
230
234
 
231
235
  .ss-content .ss-list .ss-error {
@@ -345,10 +349,12 @@
345
349
  .ss-main.ss-invalid {
346
350
  @apply border-danger-500 bg-danger-50/50 text-danger-900;
347
351
  }
352
+
348
353
  .ss-main.ss-invalid:focus,
349
354
  .ss-main.ss-invalid[aria-expanded="true"] {
350
355
  box-shadow: 0 0 0 3px theme(colors.danger.500 / 15%);
351
356
  }
357
+
352
358
  .dark .ss-main.ss-invalid {
353
359
  @apply bg-danger-950/20 border-danger-500/70 text-danger-200;
354
360
  }
@@ -360,10 +366,12 @@
360
366
  .ss-main.ss-valid {
361
367
  @apply border-success-500 bg-success-50/50 text-success-900;
362
368
  }
369
+
363
370
  .ss-main.ss-valid:focus,
364
371
  .ss-main.ss-valid[aria-expanded="true"] {
365
372
  box-shadow: 0 0 0 3px theme(colors.success.500 / 15%);
366
373
  }
374
+
367
375
  .dark .ss-main.ss-valid {
368
376
  @apply bg-success-950/20 border-success-500/70 text-success-200;
369
377
  }
@@ -385,7 +393,8 @@
385
393
  width: 100% !important;
386
394
  border-radius: 0 !important;
387
395
  margin: 0 !important;
388
- pointer-events: none !important; /* Disabled by default */
396
+ pointer-events: none !important;
397
+ /* Disabled by default */
389
398
  }
390
399
 
391
400
  /* When active (dropdown is expanded), enable pointer events */
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plutonium
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.59.0
4
+ version: 0.60.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Froelich
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-06-13 00:00:00.000000000 Z
10
+ date: 2026-06-14 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: zeitwerk
@@ -685,6 +685,8 @@ files:
685
685
  - docs/superpowers/plans/2026-06-02-structured-inputs.md.tasks.json
686
686
  - docs/superpowers/plans/2026-06-04-sqlite-tune-maintenance-generators.md
687
687
  - docs/superpowers/plans/2026-06-04-sqlite-tune-maintenance-generators.md.tasks.json
688
+ - docs/superpowers/plans/2026-06-14-form-sectioning.md
689
+ - docs/superpowers/plans/2026-06-14-form-sectioning.md.tasks.json
688
690
  - docs/superpowers/specs/2026-04-08-plutonium-skills-overhaul-design.md
689
691
  - docs/superpowers/specs/2026-04-14-plutonium-testing-design.md
690
692
  - docs/superpowers/specs/2026-05-07-ui-layout-overhaul-design.md
@@ -696,6 +698,7 @@ files:
696
698
  - docs/superpowers/specs/2026-06-01-structured-inputs-design.md
697
699
  - docs/superpowers/specs/2026-06-04-sqlite-tune-maintenance-generators-design.md
698
700
  - docs/superpowers/specs/2026-06-12-export-csv-default-action-design.md
701
+ - docs/superpowers/specs/2026-06-14-form-sectioning-design.md
699
702
  - esbuild.config.js
700
703
  - exe/pug
701
704
  - gemfiles/rails_7.gemfile
@@ -904,6 +907,7 @@ files:
904
907
  - lib/generators/pu/rodauth/templates/app/controllers/plugin_controller.rb.tt
905
908
  - lib/generators/pu/rodauth/templates/app/controllers/rodauth_controller.rb.tt
906
909
  - lib/generators/pu/rodauth/templates/app/interactions/invite_admin_interaction.rb.tt
910
+ - lib/generators/pu/rodauth/templates/app/interactions/resend_admin_interaction.rb.tt
907
911
  - lib/generators/pu/rodauth/templates/app/mailers/account_mailer.rb.tt
908
912
  - lib/generators/pu/rodauth/templates/app/mailers/rodauth_mailer.rb.tt
909
913
  - lib/generators/pu/rodauth/templates/app/models/account.rb.tt
@@ -995,6 +999,7 @@ files:
995
999
  - lib/plutonium/definition/base.rb
996
1000
  - lib/plutonium/definition/config_attr.rb
997
1001
  - lib/plutonium/definition/defineable_props.rb
1002
+ - lib/plutonium/definition/form_layout.rb
998
1003
  - lib/plutonium/definition/index_views.rb
999
1004
  - lib/plutonium/definition/inheritable_config_attr.rb
1000
1005
  - lib/plutonium/definition/metadata.rb
@@ -1131,6 +1136,7 @@ files:
1131
1136
  - lib/plutonium/ui/form/components/json.rb
1132
1137
  - lib/plutonium/ui/form/components/key_value_store.rb
1133
1138
  - lib/plutonium/ui/form/components/resource_select.rb
1139
+ - lib/plutonium/ui/form/components/section.rb
1134
1140
  - lib/plutonium/ui/form/components/secure_association.rb
1135
1141
  - lib/plutonium/ui/form/components/secure_polymorphic_association.rb
1136
1142
  - lib/plutonium/ui/form/components/sticky_footer.rb