| 1 |
// Button loading-state helpers ported from mnw.js:343,407. The delegated HTMX |
| 2 |
// loading listeners (beforeRequest/afterRequest/etc.) live in htmx-glue (Phase-1 |
| 3 |
// cutover); these two are the reusable pieces feature code calls directly. |
| 4 |
|
| 5 |
/** |
| 6 |
* Resolve the button that should reflect loading state for an interaction. |
| 7 |
* Preference: the element itself if it's a `<button>`, else the submit/primary |
| 8 |
* button in the closest form. Ported from mnw.js:343. |
| 9 |
*/ |
| 10 |
export function resolveHtmxLoadingButton(elt: Element | null | undefined): HTMLButtonElement | null { |
| 11 |
if (elt && elt.tagName === 'BUTTON') return elt as HTMLButtonElement; |
| 12 |
const form = elt?.closest?.('form'); |
| 13 |
return form ? form.querySelector<HTMLButtonElement>('button[type="submit"], .primary') : null; |
| 14 |
} |
| 15 |
|
| 16 |
/** |
| 17 |
* Wrap an async operation with button loading state for plain `fetch()` flows |
| 18 |
* (non-HTMX), restoring text + enabled state on completion regardless of |
| 19 |
* outcome. Ported from mnw.js:407. |
| 20 |
*/ |
| 21 |
export function withLoadingState<T>( |
| 22 |
btn: HTMLButtonElement | null, |
| 23 |
loadingText: string, |
| 24 |
fn: () => Promise<T> | T, |
| 25 |
): Promise<T> { |
| 26 |
if (!btn) return Promise.resolve().then(fn); |
| 27 |
const origText = btn.textContent; |
| 28 |
const origDisabled = btn.disabled; |
| 29 |
btn.textContent = loadingText; |
| 30 |
btn.disabled = true; |
| 31 |
return Promise.resolve() |
| 32 |
.then(fn) |
| 33 |
.finally(() => { |
| 34 |
btn.textContent = origText; |
| 35 |
btn.disabled = origDisabled; |
| 36 |
}); |
| 37 |
} |
| 38 |
|