main
js 75 lines 2.24 KB
Raw
1 // Inline button two-click confirmation for destructive actions.
2 // First click arms, second click confirms, timeout resets.
3
4 import { ICON_SELECTOR, getIconName, setIconName } from "./icons.js";
5
6 const CONFIRM_TIMEOUT = 2000;
7 const CONFIRM_CLASS = 'confirming';
8 const CONFIRM_ICON = 'check';
9 const CONFIRM_TEXT = 'Confirm';
10
11 const buttonStates = new WeakMap();
12
13 // Handles inline two-click confirmation for a button.
14 export function confirmClick(event, action) {
15 const button = event.currentTarget;
16 if (!button) return;
17
18 const state = buttonStates.get(button);
19
20 if (state?.confirming) {
21 clearTimeout(state.timeoutId);
22 resetButton(button, state);
23 buttonStates.delete(button);
24 action();
25 } else {
26 const iconEl = button.querySelector(ICON_SELECTOR);
27 const isIconButton = iconEl && button.textContent.trim() === iconEl.textContent.trim();
28
29 const newState = {
30 confirming: true,
31 isIconButton,
32 originalIcon: getIconName(iconEl),
33 originalHTML: isIconButton ? null : button.innerHTML,
34 timeoutId: setTimeout(() => {
35 resetButton(button, newState);
36 buttonStates.delete(button);
37 }, CONFIRM_TIMEOUT)
38 };
39
40 buttonStates.set(button, newState);
41 button.classList.add(CONFIRM_CLASS);
42
43 if (isIconButton && iconEl) {
44 // Icon-only button: just swap icon
45 setIconName(iconEl, CONFIRM_ICON);
46 } else {
47 // Text button: show icon + optional "Confirm" text
48 const originalText = button.textContent.trim();
49 const confirmContent = originalText.length >= 4
50 ? `<x-icon name="${CONFIRM_ICON}"></x-icon>${CONFIRM_TEXT}`
51 : `<x-icon name="${CONFIRM_ICON}"></x-icon>`;
52 button.innerHTML = confirmContent;
53 }
54 }
55 }
56
57 // Reset button to original state
58 function resetButton(button, state) {
59 button.classList.remove(CONFIRM_CLASS);
60 if (state.isIconButton) {
61 const iconEl = button.querySelector(ICON_SELECTOR);
62 if (iconEl && state.originalIcon) {
63 setIconName(iconEl, state.originalIcon);
64 }
65 } else if (state.originalHTML) {
66 button.innerHTML = state.originalHTML;
67 }
68 }
69
70 // Register Alpine magic helper
71 export function registerAlpineMagic() {
72 if (globalThis.Alpine) {
73 globalThis.Alpine.magic('confirmClick', () => confirmClick);
74 }
75 }