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.
- checksums.yaml +4 -4
- data/.claude/skills/plutonium-app/SKILL.md +2 -0
- data/.claude/skills/plutonium-auth/SKILL.md +6 -4
- data/.claude/skills/plutonium-behavior/SKILL.md +1 -1
- data/.claude/skills/plutonium-resource/SKILL.md +6 -4
- data/.claude/skills/plutonium-tenancy/SKILL.md +31 -7
- data/.claude/skills/plutonium-testing/SKILL.md +3 -1
- data/.claude/skills/plutonium-ui/SKILL.md +32 -8
- data/CHANGELOG.md +33 -0
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.js +258 -11
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +39 -39
- data/app/assets/plutonium.min.js.map +4 -4
- data/app/views/plutonium/_resource_header.html.erb +2 -1
- data/docs/.vitepress/config.ts +2 -2
- data/docs/.vitepress/theme/components/HomeAudienceSplit.vue +53 -0
- data/docs/.vitepress/theme/components/HomeCta.vue +108 -0
- data/docs/.vitepress/theme/components/HomeHero.vue +70 -0
- data/docs/.vitepress/theme/components/HomeInTheBox.vue +74 -0
- data/docs/.vitepress/theme/components/HomePillars.vue +42 -0
- data/docs/.vitepress/theme/components/HomeStopWriting.vue +49 -0
- data/docs/.vitepress/theme/components/HomeWalkthrough.vue +111 -0
- data/docs/.vitepress/theme/components/SectionLanding.vue +115 -0
- data/docs/.vitepress/theme/custom.css +144 -0
- data/docs/.vitepress/theme/index.ts +58 -1
- data/docs/getting-started/index.md +33 -50
- data/docs/getting-started/tutorial/02-first-resource.md +17 -8
- data/docs/getting-started/tutorial/03-authentication.md +31 -23
- data/docs/getting-started/tutorial/05-custom-actions.md +9 -4
- data/docs/getting-started/tutorial/06-nested-resources.md +7 -1
- data/docs/getting-started/tutorial/07-author-portal.md +8 -0
- data/docs/getting-started/tutorial/08-customizing-ui.md +4 -0
- data/docs/guides/authentication.md +11 -6
- data/docs/guides/authorization.md +3 -3
- data/docs/guides/creating-packages.md +8 -11
- data/docs/guides/custom-actions.md +8 -2
- data/docs/guides/customizing-ui.md +259 -0
- data/docs/guides/index.md +49 -32
- data/docs/guides/multi-tenancy.md +14 -6
- data/docs/guides/nested-resources.md +69 -0
- data/docs/guides/search-filtering.md +6 -0
- data/docs/guides/testing.md +5 -1
- data/docs/guides/theming.md +14 -1
- data/docs/guides/user-invites.md +10 -4
- data/docs/guides/user-profile.md +8 -0
- data/docs/index.md +10 -219
- data/docs/public/asciinema/home-scaffold.cast +305 -0
- data/docs/public/images/components/avatar.png +0 -0
- data/docs/public/images/guides/custom-actions-bulk.png +0 -0
- data/docs/public/images/guides/multi-tenancy-dashboard.png +0 -0
- data/docs/public/images/guides/multi-tenancy-welcome.png +0 -0
- data/docs/public/images/guides/nested-inputs.png +0 -0
- data/docs/public/images/guides/nested-resources-tab.png +0 -0
- data/docs/public/images/guides/search-filtering-index.png +0 -0
- data/docs/public/images/guides/search-filtering-panel.png +0 -0
- data/docs/public/images/guides/theming-after.png +0 -0
- data/docs/public/images/guides/theming-before.png +0 -0
- data/docs/public/images/guides/user-invites-landing.png +0 -0
- data/docs/public/images/guides/user-profile-edit.png +0 -0
- data/docs/public/images/guides/user-profile-show.png +0 -0
- data/docs/public/images/home-index.png +0 -0
- data/docs/public/images/home-new.png +0 -0
- data/docs/public/images/home-show.png +0 -0
- data/docs/public/images/tutorial/02-empty-index.png +0 -0
- data/docs/public/images/tutorial/02-index-with-posts.png +0 -0
- data/docs/public/images/tutorial/02-new-form-modal.png +0 -0
- data/docs/public/images/tutorial/02-new-form.png +0 -0
- data/docs/public/images/tutorial/03-create-account.png +0 -0
- data/docs/public/images/tutorial/03-login.png +0 -0
- data/docs/public/images/tutorial/04-admin-index.png +0 -0
- data/docs/public/images/tutorial/05-actions-menu.png +0 -0
- data/docs/public/images/tutorial/05-row-actions.png +0 -0
- data/docs/public/images/tutorial/06-comments-tab.png +0 -0
- data/docs/public/images/tutorial/06-post-with-comments.png +0 -0
- data/docs/public/images/tutorial/07-author-dashboard.png +0 -0
- data/docs/public/images/tutorial/07-author-portal.png +0 -0
- data/docs/public/images/tutorial/08-customized-index.png +0 -0
- data/docs/reference/app/generators.md +4 -4
- data/docs/reference/auth/accounts.md +7 -8
- data/docs/reference/auth/index.md +1 -1
- data/docs/reference/behavior/policies.md +2 -2
- data/docs/reference/configuration.md +61 -0
- data/docs/reference/index.md +67 -55
- data/docs/reference/resource/actions.md +2 -1
- data/docs/reference/resource/definition.md +5 -4
- data/docs/reference/tenancy/entity-scoping.md +14 -8
- data/docs/reference/tenancy/index.md +1 -1
- data/docs/reference/tenancy/invites.md +12 -5
- data/docs/reference/ui/components.md +53 -0
- data/docs/reference/ui/forms.md +1 -1
- data/docs/reference/ui/pages.md +6 -5
- data/docs/reference/ui/tables.md +8 -4
- data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md +1648 -0
- data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md.tasks.json +109 -0
- data/docs/superpowers/specs/2026-05-15-public-pages-overhaul-design.md +263 -0
- data/docs/superpowers/specs/2026-05-29-avatar-component-design.md +153 -0
- data/gemfiles/rails_7.gemfile.lock +1 -1
- data/gemfiles/rails_8.0.gemfile.lock +1 -1
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/generators/pu/core/assets/assets_generator.rb +10 -0
- data/lib/generators/pu/invites/install_generator.rb +44 -0
- data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +1 -0
- data/lib/generators/pu/lite/solid_errors/solid_errors_generator.rb +7 -3
- data/lib/generators/pu/profile/conn_generator.rb +2 -2
- data/lib/generators/pu/res/conn/conn_generator.rb +33 -6
- data/lib/generators/pu/res/model/templates/model.rb.tt +4 -0
- data/lib/generators/pu/rodauth/account_generator.rb +2 -1
- data/lib/generators/pu/rodauth/admin_generator.rb +0 -2
- data/lib/generators/pu/rodauth/migration_generator.rb +0 -2
- data/lib/generators/pu/rodauth/views_generator.rb +0 -2
- data/lib/generators/pu/saas/membership/USAGE +4 -1
- data/lib/generators/pu/saas/setup_generator.rb +16 -4
- data/lib/generators/pu/saas/welcome/templates/app/controllers/welcome_controller.rb.tt +1 -1
- data/lib/plutonium/action/base.rb +43 -63
- data/lib/plutonium/configuration.rb +7 -0
- data/lib/plutonium/definition/actions.rb +10 -11
- data/lib/plutonium/definition/base.rb +29 -0
- data/lib/plutonium/helpers/assets_helper.rb +0 -30
- data/lib/plutonium/helpers/content_helper.rb +0 -44
- data/lib/plutonium/helpers/display_helper.rb +0 -62
- data/lib/plutonium/helpers/turbo_helper.rb +17 -2
- data/lib/plutonium/helpers.rb +0 -2
- data/lib/plutonium/resource/controllers/crud_actions.rb +4 -4
- data/lib/plutonium/resource/controllers/interactive_actions.rb +3 -3
- data/lib/plutonium/resource/definition.rb +0 -42
- data/lib/plutonium/ui/action_button.rb +4 -3
- data/lib/plutonium/ui/avatar.rb +182 -0
- data/lib/plutonium/ui/component/kit.rb +2 -0
- data/lib/plutonium/ui/component/methods.rb +1 -0
- data/lib/plutonium/ui/form/base.rb +32 -2
- data/lib/plutonium/ui/form/components/secure_association.rb +14 -8
- data/lib/plutonium/ui/form/interaction.rb +1 -1
- data/lib/plutonium/ui/form/resource.rb +58 -0
- data/lib/plutonium/ui/form/theme.rb +8 -4
- data/lib/plutonium/ui/grid/card.rb +10 -26
- data/lib/plutonium/ui/modal/base.rb +36 -1
- data/lib/plutonium/ui/modal/centered.rb +24 -6
- data/lib/plutonium/ui/modal/slideover.rb +26 -11
- data/lib/plutonium/ui/nav_user.rb +3 -23
- data/lib/plutonium/ui/page/edit.rb +7 -4
- data/lib/plutonium/ui/page/interactive_action.rb +5 -3
- data/lib/plutonium/ui/page/new.rb +7 -4
- data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +1 -1
- data/lib/plutonium/ui/table/components/filter_form.rb +12 -4
- data/lib/plutonium/version.rb +1 -1
- data/package.json +4 -1
- data/src/css/components.css +38 -1
- data/src/css/slim_select.css +3 -2
- data/src/js/controllers/dirty_form_guard_controller.js +165 -0
- data/src/js/controllers/form_controller.js +5 -4
- data/src/js/controllers/register_controllers.js +2 -0
- data/src/js/controllers/remote_modal_controller.js +53 -19
- data/src/js/turbo/index.js +1 -0
- data/src/js/turbo/turbo_confirm.js +128 -0
- data/yarn.lock +108 -1
- metadata +52 -6
- data/lib/plutonium/helpers/attachment_helper.rb +0 -73
- data/lib/plutonium/helpers/table_helper.rb +0 -35
- /data/lib/generators/pu/rodauth/templates/app/views/rodauth_mailer/{password_changed.text.erb → change_password_notify.text.erb} +0 -0
data/app/assets/plutonium.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
27596
|
-
this.restoreBodyState();
|
|
27613
|
+
this.#animateClose();
|
|
27597
27614
|
}
|
|
27598
|
-
|
|
27599
|
-
|
|
27600
|
-
|
|
27615
|
+
#onCancel(event) {
|
|
27616
|
+
if (event.defaultPrevented) return;
|
|
27617
|
+
event.preventDefault();
|
|
27618
|
+
this.#animateClose();
|
|
27601
27619
|
}
|
|
27602
|
-
|
|
27603
|
-
this
|
|
27620
|
+
#onClose() {
|
|
27621
|
+
this.#restoreBodyState();
|
|
27604
27622
|
}
|
|
27605
|
-
|
|
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
|
|
28199
|
-
if (
|
|
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);
|