plutonium 0.51.0 → 0.53.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 (160) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium-app/SKILL.md +2 -0
  3. data/.claude/skills/plutonium-auth/SKILL.md +6 -4
  4. data/.claude/skills/plutonium-behavior/SKILL.md +1 -1
  5. data/.claude/skills/plutonium-resource/SKILL.md +6 -4
  6. data/.claude/skills/plutonium-tenancy/SKILL.md +31 -7
  7. data/.claude/skills/plutonium-testing/SKILL.md +3 -1
  8. data/.claude/skills/plutonium-ui/SKILL.md +32 -8
  9. data/CHANGELOG.md +33 -0
  10. data/app/assets/plutonium.css +1 -1
  11. data/app/assets/plutonium.js +258 -11
  12. data/app/assets/plutonium.js.map +4 -4
  13. data/app/assets/plutonium.min.js +39 -39
  14. data/app/assets/plutonium.min.js.map +4 -4
  15. data/app/views/plutonium/_resource_header.html.erb +2 -1
  16. data/docs/.vitepress/config.ts +2 -2
  17. data/docs/.vitepress/theme/components/HomeAudienceSplit.vue +53 -0
  18. data/docs/.vitepress/theme/components/HomeCta.vue +108 -0
  19. data/docs/.vitepress/theme/components/HomeHero.vue +70 -0
  20. data/docs/.vitepress/theme/components/HomeInTheBox.vue +74 -0
  21. data/docs/.vitepress/theme/components/HomePillars.vue +42 -0
  22. data/docs/.vitepress/theme/components/HomeStopWriting.vue +49 -0
  23. data/docs/.vitepress/theme/components/HomeWalkthrough.vue +111 -0
  24. data/docs/.vitepress/theme/components/SectionLanding.vue +115 -0
  25. data/docs/.vitepress/theme/custom.css +144 -0
  26. data/docs/.vitepress/theme/index.ts +58 -1
  27. data/docs/getting-started/index.md +33 -50
  28. data/docs/getting-started/tutorial/02-first-resource.md +17 -8
  29. data/docs/getting-started/tutorial/03-authentication.md +31 -23
  30. data/docs/getting-started/tutorial/05-custom-actions.md +9 -4
  31. data/docs/getting-started/tutorial/06-nested-resources.md +7 -1
  32. data/docs/getting-started/tutorial/07-author-portal.md +8 -0
  33. data/docs/getting-started/tutorial/08-customizing-ui.md +4 -0
  34. data/docs/guides/authentication.md +11 -6
  35. data/docs/guides/authorization.md +3 -3
  36. data/docs/guides/creating-packages.md +8 -11
  37. data/docs/guides/custom-actions.md +8 -2
  38. data/docs/guides/customizing-ui.md +259 -0
  39. data/docs/guides/index.md +49 -32
  40. data/docs/guides/multi-tenancy.md +14 -6
  41. data/docs/guides/nested-resources.md +69 -0
  42. data/docs/guides/search-filtering.md +6 -0
  43. data/docs/guides/testing.md +5 -1
  44. data/docs/guides/theming.md +14 -1
  45. data/docs/guides/user-invites.md +10 -4
  46. data/docs/guides/user-profile.md +8 -0
  47. data/docs/index.md +10 -219
  48. data/docs/public/asciinema/home-scaffold.cast +305 -0
  49. data/docs/public/images/components/avatar.png +0 -0
  50. data/docs/public/images/guides/custom-actions-bulk.png +0 -0
  51. data/docs/public/images/guides/multi-tenancy-dashboard.png +0 -0
  52. data/docs/public/images/guides/multi-tenancy-welcome.png +0 -0
  53. data/docs/public/images/guides/nested-inputs.png +0 -0
  54. data/docs/public/images/guides/nested-resources-tab.png +0 -0
  55. data/docs/public/images/guides/search-filtering-index.png +0 -0
  56. data/docs/public/images/guides/search-filtering-panel.png +0 -0
  57. data/docs/public/images/guides/theming-after.png +0 -0
  58. data/docs/public/images/guides/theming-before.png +0 -0
  59. data/docs/public/images/guides/user-invites-landing.png +0 -0
  60. data/docs/public/images/guides/user-profile-edit.png +0 -0
  61. data/docs/public/images/guides/user-profile-show.png +0 -0
  62. data/docs/public/images/home-index.png +0 -0
  63. data/docs/public/images/home-new.png +0 -0
  64. data/docs/public/images/home-show.png +0 -0
  65. data/docs/public/images/tutorial/02-empty-index.png +0 -0
  66. data/docs/public/images/tutorial/02-index-with-posts.png +0 -0
  67. data/docs/public/images/tutorial/02-new-form-modal.png +0 -0
  68. data/docs/public/images/tutorial/02-new-form.png +0 -0
  69. data/docs/public/images/tutorial/03-create-account.png +0 -0
  70. data/docs/public/images/tutorial/03-login.png +0 -0
  71. data/docs/public/images/tutorial/04-admin-index.png +0 -0
  72. data/docs/public/images/tutorial/05-actions-menu.png +0 -0
  73. data/docs/public/images/tutorial/05-row-actions.png +0 -0
  74. data/docs/public/images/tutorial/06-comments-tab.png +0 -0
  75. data/docs/public/images/tutorial/06-post-with-comments.png +0 -0
  76. data/docs/public/images/tutorial/07-author-dashboard.png +0 -0
  77. data/docs/public/images/tutorial/07-author-portal.png +0 -0
  78. data/docs/public/images/tutorial/08-customized-index.png +0 -0
  79. data/docs/reference/app/generators.md +4 -4
  80. data/docs/reference/auth/accounts.md +7 -8
  81. data/docs/reference/auth/index.md +1 -1
  82. data/docs/reference/behavior/policies.md +2 -2
  83. data/docs/reference/configuration.md +61 -0
  84. data/docs/reference/index.md +67 -55
  85. data/docs/reference/resource/actions.md +2 -1
  86. data/docs/reference/resource/definition.md +5 -4
  87. data/docs/reference/tenancy/entity-scoping.md +14 -8
  88. data/docs/reference/tenancy/index.md +1 -1
  89. data/docs/reference/tenancy/invites.md +12 -5
  90. data/docs/reference/ui/components.md +53 -0
  91. data/docs/reference/ui/forms.md +1 -1
  92. data/docs/reference/ui/pages.md +6 -5
  93. data/docs/reference/ui/tables.md +8 -4
  94. data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md +1648 -0
  95. data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md.tasks.json +109 -0
  96. data/docs/superpowers/specs/2026-05-15-public-pages-overhaul-design.md +263 -0
  97. data/docs/superpowers/specs/2026-05-29-avatar-component-design.md +153 -0
  98. data/gemfiles/rails_7.gemfile.lock +1 -1
  99. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  100. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  101. data/lib/generators/pu/core/assets/assets_generator.rb +10 -0
  102. data/lib/generators/pu/invites/install_generator.rb +44 -0
  103. data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +1 -0
  104. data/lib/generators/pu/lite/solid_errors/solid_errors_generator.rb +7 -3
  105. data/lib/generators/pu/profile/conn_generator.rb +2 -2
  106. data/lib/generators/pu/res/conn/conn_generator.rb +33 -6
  107. data/lib/generators/pu/res/model/templates/model.rb.tt +4 -0
  108. data/lib/generators/pu/rodauth/account_generator.rb +2 -1
  109. data/lib/generators/pu/rodauth/admin_generator.rb +0 -2
  110. data/lib/generators/pu/rodauth/migration_generator.rb +0 -2
  111. data/lib/generators/pu/rodauth/views_generator.rb +0 -2
  112. data/lib/generators/pu/saas/membership/USAGE +4 -1
  113. data/lib/generators/pu/saas/setup_generator.rb +16 -4
  114. data/lib/generators/pu/saas/welcome/templates/app/controllers/welcome_controller.rb.tt +1 -1
  115. data/lib/plutonium/action/base.rb +43 -63
  116. data/lib/plutonium/configuration.rb +7 -0
  117. data/lib/plutonium/definition/actions.rb +10 -11
  118. data/lib/plutonium/definition/base.rb +29 -0
  119. data/lib/plutonium/helpers/assets_helper.rb +0 -30
  120. data/lib/plutonium/helpers/content_helper.rb +0 -44
  121. data/lib/plutonium/helpers/display_helper.rb +0 -62
  122. data/lib/plutonium/helpers/turbo_helper.rb +17 -2
  123. data/lib/plutonium/helpers.rb +0 -2
  124. data/lib/plutonium/resource/controllers/crud_actions.rb +4 -4
  125. data/lib/plutonium/resource/controllers/interactive_actions.rb +3 -3
  126. data/lib/plutonium/resource/definition.rb +0 -42
  127. data/lib/plutonium/ui/action_button.rb +4 -3
  128. data/lib/plutonium/ui/avatar.rb +182 -0
  129. data/lib/plutonium/ui/component/kit.rb +2 -0
  130. data/lib/plutonium/ui/component/methods.rb +1 -0
  131. data/lib/plutonium/ui/form/base.rb +32 -2
  132. data/lib/plutonium/ui/form/components/secure_association.rb +14 -8
  133. data/lib/plutonium/ui/form/interaction.rb +1 -1
  134. data/lib/plutonium/ui/form/resource.rb +58 -0
  135. data/lib/plutonium/ui/form/theme.rb +8 -4
  136. data/lib/plutonium/ui/grid/card.rb +10 -26
  137. data/lib/plutonium/ui/modal/base.rb +36 -1
  138. data/lib/plutonium/ui/modal/centered.rb +24 -6
  139. data/lib/plutonium/ui/modal/slideover.rb +26 -11
  140. data/lib/plutonium/ui/nav_user.rb +3 -23
  141. data/lib/plutonium/ui/page/edit.rb +7 -4
  142. data/lib/plutonium/ui/page/interactive_action.rb +5 -3
  143. data/lib/plutonium/ui/page/new.rb +7 -4
  144. data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +1 -1
  145. data/lib/plutonium/ui/table/components/filter_form.rb +12 -4
  146. data/lib/plutonium/version.rb +1 -1
  147. data/package.json +4 -1
  148. data/src/css/components.css +38 -1
  149. data/src/css/slim_select.css +3 -2
  150. data/src/js/controllers/dirty_form_guard_controller.js +165 -0
  151. data/src/js/controllers/form_controller.js +5 -4
  152. data/src/js/controllers/register_controllers.js +2 -0
  153. data/src/js/controllers/remote_modal_controller.js +53 -19
  154. data/src/js/turbo/index.js +1 -0
  155. data/src/js/turbo/turbo_confirm.js +128 -0
  156. data/yarn.lock +108 -1
  157. metadata +52 -6
  158. data/lib/plutonium/helpers/attachment_helper.rb +0 -73
  159. data/lib/plutonium/helpers/table_helper.rb +0 -35
  160. /data/lib/generators/pu/rodauth/templates/app/views/rodauth_mailer/{password_changed.text.erb → change_password_notify.text.erb} +0 -0
@@ -11515,6 +11515,7 @@
11515
11515
  connect() {
11516
11516
  }
11517
11517
  preSubmit() {
11518
+ this.element.querySelectorAll('input[name="pre_submit"]').forEach((n3) => n3.remove());
11518
11519
  const hiddenField = document.createElement("input");
11519
11520
  hiddenField.type = "hidden";
11520
11521
  hiddenField.name = "pre_submit";
@@ -27587,22 +27588,47 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
27587
27588
  this.originalScrollPosition = window.scrollY;
27588
27589
  this.originalOverflow = document.body.style.overflow;
27589
27590
  this.bodyStateRestored = false;
27591
+ this._closing = false;
27590
27592
  document.body.style.overflow = "hidden";
27591
27593
  this.element.showModal();
27592
- this.element.addEventListener("close", this.handleClose.bind(this));
27594
+ requestAnimationFrame(() => {
27595
+ requestAnimationFrame(() => {
27596
+ this.element.setAttribute("data-open", "");
27597
+ });
27598
+ });
27599
+ this.onCancel = this.#onCancel.bind(this);
27600
+ this.onClose = this.#onClose.bind(this);
27601
+ this.onRequestClose = () => this.#animateClose();
27602
+ this.element.addEventListener("cancel", this.onCancel);
27603
+ this.element.addEventListener("close", this.onClose);
27604
+ this.element.addEventListener("modal:request-close", this.onRequestClose);
27605
+ }
27606
+ disconnect() {
27607
+ this.element.removeEventListener("cancel", this.onCancel);
27608
+ this.element.removeEventListener("close", this.onClose);
27609
+ this.element.removeEventListener("modal:request-close", this.onRequestClose);
27610
+ this.#restoreBodyState();
27593
27611
  }
27594
27612
  close() {
27595
- this.element.close();
27596
- this.restoreBodyState();
27613
+ this.#animateClose();
27597
27614
  }
27598
- disconnect() {
27599
- this.element.removeEventListener("close", this.handleClose);
27600
- this.restoreBodyState();
27615
+ #onCancel(event) {
27616
+ if (event.defaultPrevented) return;
27617
+ event.preventDefault();
27618
+ this.#animateClose();
27601
27619
  }
27602
- handleClose() {
27603
- this.restoreBodyState();
27620
+ #onClose() {
27621
+ this.#restoreBodyState();
27604
27622
  }
27605
- restoreBodyState() {
27623
+ async #animateClose() {
27624
+ if (this._closing) return;
27625
+ this._closing = true;
27626
+ this.element.removeAttribute("data-open");
27627
+ const animations = this.element.getAnimations({ subtree: true });
27628
+ await Promise.allSettled(animations.map((a4) => a4.finished));
27629
+ this.element.close();
27630
+ }
27631
+ #restoreBodyState() {
27606
27632
  if (this.bodyStateRestored) return;
27607
27633
  this.bodyStateRestored = true;
27608
27634
  document.body.style.overflow = this.originalOverflow || "";
@@ -28147,6 +28173,129 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
28147
28173
  }
28148
28174
  };
28149
28175
 
28176
+ // src/js/controllers/dirty_form_guard_controller.js
28177
+ var dirty_form_guard_controller_default = class extends Controller {
28178
+ static targets = ["confirmDialog"];
28179
+ // Set by controllers, not the user — comparing them would flag
28180
+ // every form as dirty on connect (return_to) or on submit (pre_submit).
28181
+ static IGNORED_KEYS = /* @__PURE__ */ new Set(["authenticity_token", "return_to", "pre_submit"]);
28182
+ connect() {
28183
+ this.dialog = this.element.closest("dialog");
28184
+ if (!this.dialog) return;
28185
+ this.snapshot = this.#serialize();
28186
+ this.forceClose = false;
28187
+ this.submitting = false;
28188
+ this.onCancel = this.#onCancel.bind(this);
28189
+ this.onSubmit = this.#onSubmit.bind(this);
28190
+ this.onCloseButtonClick = this.#onCloseButtonClick.bind(this);
28191
+ this.onConfirmCancel = this.#onConfirmCancel.bind(this);
28192
+ this.onKeydown = this.#onKeydown.bind(this);
28193
+ document.addEventListener("keydown", this.onKeydown, true);
28194
+ this.dialog.addEventListener("cancel", this.onCancel, true);
28195
+ this.element.addEventListener("submit", this.onSubmit);
28196
+ this.#closeButtons().forEach(
28197
+ (btn) => btn.addEventListener("click", this.onCloseButtonClick, true)
28198
+ );
28199
+ if (this.hasConfirmDialogTarget) {
28200
+ this.confirmDialogTarget.addEventListener("cancel", this.onConfirmCancel);
28201
+ }
28202
+ }
28203
+ disconnect() {
28204
+ if (!this.dialog) return;
28205
+ document.removeEventListener("keydown", this.onKeydown, true);
28206
+ this.dialog.removeEventListener("cancel", this.onCancel, true);
28207
+ this.element.removeEventListener("submit", this.onSubmit);
28208
+ this.#closeButtons().forEach(
28209
+ (btn) => btn.removeEventListener("click", this.onCloseButtonClick, true)
28210
+ );
28211
+ if (this.hasConfirmDialogTarget) {
28212
+ this.confirmDialogTarget.removeEventListener("cancel", this.onConfirmCancel);
28213
+ }
28214
+ }
28215
+ async discard() {
28216
+ this.forceClose = true;
28217
+ await this.#closeConfirm();
28218
+ this.dialog.dispatchEvent(new CustomEvent("modal:request-close"));
28219
+ }
28220
+ keepEditing() {
28221
+ this.#closeConfirm();
28222
+ }
28223
+ #closeButtons() {
28224
+ if (!this.dialog) return [];
28225
+ return this.dialog.querySelectorAll('[data-action~="remote-modal#close"]');
28226
+ }
28227
+ #serialize() {
28228
+ const data = new FormData(this.element);
28229
+ const enc = encodeURIComponent;
28230
+ return [...data.entries()].filter(([key]) => !this.constructor.IGNORED_KEYS.has(key)).map(([key, value]) => {
28231
+ const v4 = value instanceof File ? value.name : value;
28232
+ return `${enc(key)}=${enc(v4)}`;
28233
+ }).sort().join("&");
28234
+ }
28235
+ #isDirty() {
28236
+ return this.#serialize() !== this.snapshot;
28237
+ }
28238
+ #onSubmit() {
28239
+ this.submitting = true;
28240
+ }
28241
+ #confirmIsOpen() {
28242
+ return this.hasConfirmDialogTarget && this.confirmDialogTarget.open;
28243
+ }
28244
+ #onKeydown(event) {
28245
+ if (event.key !== "Escape") return;
28246
+ if (!this.dialog.open) return;
28247
+ if (this.#confirmIsOpen()) {
28248
+ event.preventDefault();
28249
+ event.stopPropagation();
28250
+ event.stopImmediatePropagation();
28251
+ return;
28252
+ }
28253
+ if (this.forceClose || this.submitting) return;
28254
+ if (!this.#isDirty()) return;
28255
+ event.preventDefault();
28256
+ event.stopPropagation();
28257
+ event.stopImmediatePropagation();
28258
+ this.#promptDiscard();
28259
+ }
28260
+ #onCancel(event) {
28261
+ if (this.forceClose || this.submitting) return;
28262
+ if (!this.#isDirty()) return;
28263
+ event.preventDefault();
28264
+ this.#promptDiscard();
28265
+ }
28266
+ #onCloseButtonClick(event) {
28267
+ if (this.forceClose || this.submitting) return;
28268
+ if (!this.#isDirty()) return;
28269
+ event.preventDefault();
28270
+ event.stopPropagation();
28271
+ this.#promptDiscard();
28272
+ }
28273
+ #onConfirmCancel(event) {
28274
+ event.preventDefault();
28275
+ }
28276
+ #promptDiscard() {
28277
+ if (this.hasConfirmDialogTarget) {
28278
+ const d4 = this.confirmDialogTarget;
28279
+ d4.showModal();
28280
+ requestAnimationFrame(() => {
28281
+ requestAnimationFrame(() => d4.setAttribute("data-open", ""));
28282
+ });
28283
+ } else if (window.confirm("Discard your changes?")) {
28284
+ this.forceClose = true;
28285
+ this.dialog.dispatchEvent(new CustomEvent("modal:request-close"));
28286
+ }
28287
+ }
28288
+ async #closeConfirm() {
28289
+ if (!this.hasConfirmDialogTarget) return;
28290
+ const d4 = this.confirmDialogTarget;
28291
+ if (!d4.open) return;
28292
+ d4.removeAttribute("data-open");
28293
+ const animations = d4.getAnimations({ subtree: true });
28294
+ await Promise.allSettled(animations.map((a4) => a4.finished));
28295
+ d4.close();
28296
+ }
28297
+ };
28298
+
28150
28299
  // src/js/controllers/register_controllers.js
28151
28300
  function register_controllers_default(application2) {
28152
28301
  application2.register("password-visibility", password_visibility_controller_default);
@@ -28182,6 +28331,7 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
28182
28331
  application2.register("row-click", row_click_controller_default);
28183
28332
  application2.register("view-switcher", view_switcher_controller_default);
28184
28333
  application2.register("autosubmit", autosubmit_controller_default);
28334
+ application2.register("dirty-form-guard", dirty_form_guard_controller_default);
28185
28335
  }
28186
28336
 
28187
28337
  // src/js/turbo/turbo_actions.js
@@ -28195,8 +28345,8 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
28195
28345
  if (!frameId) return;
28196
28346
  const frame = document.getElementById(frameId);
28197
28347
  if (!frame) return;
28198
- const dialog = frame.querySelector("dialog");
28199
- if (dialog && typeof dialog.close === "function") dialog.close();
28348
+ const dialog2 = frame.querySelector("dialog");
28349
+ if (dialog2 && typeof dialog2.close === "function") dialog2.close();
28200
28350
  frame.innerHTML = "";
28201
28351
  frame.removeAttribute("src");
28202
28352
  };
@@ -28208,6 +28358,103 @@ this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e4.byteLength}`), e4.tif
28208
28358
  frame.reload();
28209
28359
  };
28210
28360
 
28361
+ // src/js/turbo/turbo_confirm.js
28362
+ var dialog;
28363
+ var messageEl;
28364
+ var confirmButton;
28365
+ var cancelButton;
28366
+ function ensureDialog() {
28367
+ if (dialog) {
28368
+ if (!dialog.isConnected) document.body.appendChild(dialog);
28369
+ return;
28370
+ }
28371
+ dialog = document.createElement("dialog");
28372
+ dialog.className = [
28373
+ "pu-dialog",
28374
+ "top-1/2",
28375
+ "-translate-y-1/2",
28376
+ "left-1/2",
28377
+ "-translate-x-1/2",
28378
+ "w-full",
28379
+ "max-w-md",
28380
+ "p-0",
28381
+ "open:flex",
28382
+ "flex-col",
28383
+ "opacity-0",
28384
+ "scale-95",
28385
+ "data-[open]:opacity-100",
28386
+ "data-[open]:scale-100",
28387
+ "transition-[opacity,transform]",
28388
+ "duration-200",
28389
+ "ease-out"
28390
+ ].join(" ");
28391
+ dialog.setAttribute("aria-labelledby", "pu-turbo-confirm-message");
28392
+ const header = document.createElement("div");
28393
+ header.className = "px-6 pt-5 pb-4 border-b border-[var(--pu-border)]";
28394
+ messageEl = document.createElement("h2");
28395
+ messageEl.id = "pu-turbo-confirm-message";
28396
+ messageEl.className = "text-lg font-semibold text-[var(--pu-text)]";
28397
+ header.appendChild(messageEl);
28398
+ const footer = document.createElement("div");
28399
+ footer.className = "flex items-center justify-end gap-2 px-6 py-4";
28400
+ cancelButton = document.createElement("button");
28401
+ cancelButton.type = "button";
28402
+ cancelButton.className = "pu-btn pu-btn-md pu-btn-outline";
28403
+ cancelButton.textContent = "Cancel";
28404
+ confirmButton = document.createElement("button");
28405
+ confirmButton.type = "button";
28406
+ confirmButton.className = "pu-btn pu-btn-md pu-btn-primary";
28407
+ confirmButton.textContent = "Confirm";
28408
+ footer.appendChild(cancelButton);
28409
+ footer.appendChild(confirmButton);
28410
+ dialog.appendChild(header);
28411
+ dialog.appendChild(footer);
28412
+ document.body.appendChild(dialog);
28413
+ }
28414
+ async function animateClose() {
28415
+ dialog.removeAttribute("data-open");
28416
+ const animations = dialog.getAnimations({ subtree: true });
28417
+ await Promise.allSettled(animations.map((a4) => a4.finished));
28418
+ if (dialog.open) dialog.close();
28419
+ }
28420
+ function themedConfirm(message) {
28421
+ ensureDialog();
28422
+ messageEl.textContent = message || "Are you sure?";
28423
+ return new Promise((resolve) => {
28424
+ let settled = false;
28425
+ const settle = (value) => {
28426
+ if (settled) return;
28427
+ settled = true;
28428
+ cleanup();
28429
+ resolve(value);
28430
+ animateClose();
28431
+ };
28432
+ const onConfirm = () => settle(true);
28433
+ const onCancel = () => settle(false);
28434
+ const onClose = () => settle(false);
28435
+ const cleanup = () => {
28436
+ confirmButton.removeEventListener("click", onConfirm);
28437
+ cancelButton.removeEventListener("click", onCancel);
28438
+ dialog.removeEventListener("close", onClose);
28439
+ };
28440
+ confirmButton.addEventListener("click", onConfirm);
28441
+ cancelButton.addEventListener("click", onCancel);
28442
+ dialog.addEventListener("close", onClose);
28443
+ dialog.showModal();
28444
+ requestAnimationFrame(() => {
28445
+ requestAnimationFrame(() => dialog.setAttribute("data-open", ""));
28446
+ });
28447
+ confirmButton.focus();
28448
+ });
28449
+ }
28450
+ if (typeof window !== "undefined" && window.Turbo) {
28451
+ if (window.Turbo.config?.forms) {
28452
+ window.Turbo.config.forms.confirm = themedConfirm;
28453
+ } else if (window.Turbo.setConfirmMethod) {
28454
+ window.Turbo.setConfirmMethod(themedConfirm);
28455
+ }
28456
+ }
28457
+
28211
28458
  // src/js/plutonium.js
28212
28459
  var application = Application.start();
28213
28460
  register_controllers_default(application);