plutonium 0.53.1 → 0.55.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium-behavior/SKILL.md +22 -0
  3. data/.claude/skills/plutonium-resource/SKILL.md +55 -0
  4. data/.claude/skills/plutonium-ui/SKILL.md +2 -1
  5. data/CHANGELOG.md +38 -0
  6. data/app/assets/plutonium.css +1 -1
  7. data/app/assets/plutonium.js +40 -6
  8. data/app/assets/plutonium.js.map +4 -4
  9. data/app/assets/plutonium.min.js +32 -32
  10. data/app/assets/plutonium.min.js.map +4 -4
  11. data/app/views/plutonium/_flash_alerts.html.erb +8 -17
  12. data/app/views/plutonium/_flash_toasts.html.erb +9 -18
  13. data/docs/public/images/reference/structured-inputs-removed.png +0 -0
  14. data/docs/public/images/reference/structured-inputs.png +0 -0
  15. data/docs/reference/resource/definition.md +110 -0
  16. data/docs/superpowers/plans/2026-06-02-structured-inputs.md +1061 -0
  17. data/docs/superpowers/plans/2026-06-02-structured-inputs.md.tasks.json +60 -0
  18. data/docs/superpowers/specs/2026-06-01-structured-inputs-design.md +191 -0
  19. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  20. data/lib/plutonium/definition/base.rb +1 -0
  21. data/lib/plutonium/definition/structured_inputs.rb +67 -0
  22. data/lib/plutonium/engine/validator.rb +11 -4
  23. data/lib/plutonium/interaction/README.md +24 -78
  24. data/lib/plutonium/interaction/base.rb +10 -2
  25. data/lib/plutonium/resource/controller.rb +6 -1
  26. data/lib/plutonium/resource/controllers/interactive_actions.rb +10 -6
  27. data/lib/plutonium/structured_inputs/param_cleaner.rb +36 -0
  28. data/lib/plutonium/structured_inputs/params_concern.rb +36 -0
  29. data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +3 -3
  30. data/lib/plutonium/ui/form/concerns/renders_structured_inputs.rb +178 -0
  31. data/lib/plutonium/ui/form/concerns/repeater_field_styles.rb +24 -0
  32. data/lib/plutonium/ui/form/resource.rb +15 -5
  33. data/lib/plutonium/ui/form/theme.rb +14 -3
  34. data/lib/plutonium/ui/modal/slideover.rb +9 -3
  35. data/lib/plutonium/version.rb +1 -1
  36. data/package.json +1 -1
  37. data/src/css/components.css +119 -5
  38. data/src/js/controllers/capture_url_controller.js +20 -7
  39. data/src/js/controllers/dirty_form_guard_controller.js +28 -4
  40. data/src/js/controllers/icon_rail_flyout_controller.js +5 -2
  41. data/src/js/controllers/register_controllers.js +2 -0
  42. data/src/js/controllers/remote_modal_controller.js +5 -0
  43. data/src/js/controllers/structured_input_row_controller.js +26 -0
  44. metadata +14 -4
  45. data/lib/plutonium/interaction/nested_attributes.rb +0 -93
@@ -11510,6 +11510,23 @@
11510
11510
  }
11511
11511
  };
11512
11512
 
11513
+ // src/js/controllers/structured_input_row_controller.js
11514
+ var structured_input_row_controller_default = class extends Controller {
11515
+ static targets = ["content", "removed"];
11516
+ remove(e4) {
11517
+ e4.preventDefault();
11518
+ this.contentTarget.disabled = true;
11519
+ this.contentTarget.hidden = true;
11520
+ this.removedTarget.hidden = false;
11521
+ }
11522
+ restore(e4) {
11523
+ e4.preventDefault();
11524
+ this.contentTarget.disabled = false;
11525
+ this.contentTarget.hidden = false;
11526
+ this.removedTarget.hidden = true;
11527
+ }
11528
+ };
11529
+
11513
11530
  // src/js/controllers/form_controller.js
11514
11531
  var form_controller_default = class extends Controller {
11515
11532
  connect() {
@@ -27613,6 +27630,7 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
27613
27630
  this.#animateClose();
27614
27631
  }
27615
27632
  #onCancel(event) {
27633
+ if (event.target !== this.element) return;
27616
27634
  if (event.defaultPrevented) return;
27617
27635
  event.preventDefault();
27618
27636
  this.#animateClose();
@@ -28066,7 +28084,8 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
28066
28084
  const viewportH = window.innerHeight;
28067
28085
  if (panelRect.bottom > viewportH - 8) {
28068
28086
  const overflow = panelRect.bottom - (viewportH - 8);
28069
- panel.style.top = `${parseFloat(panel.style.top) - overflow}px`;
28087
+ const top2 = Math.max(8, parseFloat(panel.style.top) - overflow);
28088
+ panel.style.top = `${top2}px`;
28070
28089
  }
28071
28090
  });
28072
28091
  }
@@ -28125,9 +28144,12 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
28125
28144
  // src/js/controllers/capture_url_controller.js
28126
28145
  var capture_url_controller_default = class extends Controller {
28127
28146
  connect() {
28128
- if ("value" in this.element) {
28129
- this.element.value = window.location.href;
28130
- }
28147
+ if (!("value" in this.element)) return;
28148
+ const base = this.element.value;
28149
+ if (!base) return;
28150
+ const { hash: hash3 } = window.location;
28151
+ if (!hash3) return;
28152
+ this.element.value = base.split("#")[0] + hash3;
28131
28153
  }
28132
28154
  };
28133
28155
 
@@ -28212,9 +28234,9 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
28212
28234
  this.confirmDialogTarget.removeEventListener("cancel", this.onConfirmCancel);
28213
28235
  }
28214
28236
  }
28215
- async discard() {
28237
+ discard() {
28216
28238
  this.forceClose = true;
28217
- await this.#closeConfirm();
28239
+ this.#snapConfirmClosed();
28218
28240
  this.dialog.dispatchEvent(new CustomEvent("modal:request-close"));
28219
28241
  }
28220
28242
  keepEditing() {
@@ -28258,6 +28280,7 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
28258
28280
  this.#promptDiscard();
28259
28281
  }
28260
28282
  #onCancel(event) {
28283
+ if (event.target !== this.dialog) return;
28261
28284
  if (this.forceClose || this.submitting) return;
28262
28285
  if (!this.#isDirty()) return;
28263
28286
  event.preventDefault();
@@ -28285,6 +28308,16 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
28285
28308
  this.dialog.dispatchEvent(new CustomEvent("modal:request-close"));
28286
28309
  }
28287
28310
  }
28311
+ // Close the confirm immediately, skipping its exit transition. Used by
28312
+ // discard(), where the parent modal is about to animate away and a
28313
+ // separate confirm fade would only stutter against the modal's live
28314
+ // backdrop blur.
28315
+ #snapConfirmClosed() {
28316
+ if (!this.hasConfirmDialogTarget) return;
28317
+ const d4 = this.confirmDialogTarget;
28318
+ d4.removeAttribute("data-open");
28319
+ if (d4.open) d4.close();
28320
+ }
28288
28321
  async #closeConfirm() {
28289
28322
  if (!this.hasConfirmDialogTarget) return;
28290
28323
  const d4 = this.confirmDialogTarget;
@@ -28302,6 +28335,7 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
28302
28335
  application2.register("sidebar", sidebar_controller_default);
28303
28336
  application2.register("resource-header", resource_header_controller_default);
28304
28337
  application2.register("nested-resource-form-fields", nested_resource_form_fields_controller_default);
28338
+ application2.register("structured-input-row", structured_input_row_controller_default);
28305
28339
  application2.register("form", form_controller_default);
28306
28340
  application2.register("resource-drop-down", resource_drop_down_controller_default);
28307
28341
  application2.register("resource-collapse", resource_collapse_controller_default);