/** * Portal Form Hooks * * Globale UX-Helper für FluxUI-Forms im Hub/Portal-Bereich. * * Aktuell: * 1) Smooth-Scroll zum ersten Validation-Error nach Submit-Klick, * damit der User in langen Forms (z.B. PR-Edit) nicht nach Errors * suchen muss. * * Wird im Portal-Layout NACH @fluxScripts eingebunden — Livewire ist * dann garantiert verfügbar. Bewusst KEIN Alpine.start() o.ä.; FluxUI * bringt seine eigene Alpine-Instanz mit, doppelter Bootstrap würde * Komponenten brechen (siehe partials/head.blade.php Kommentar). */ (function () { if (typeof document === 'undefined') { return; } // Pending-Flag: wird nur gesetzt, wenn der User explizit auf einen // Submit-/Save-Button klickt. Andernfalls würde JEDES wire:model-Update // einen Scroll triggern, was bei Live-Validation extrem nervig wäre. let scrollPending = false; // Selektoren für Buttons, die wir als "Submit-Intent" interpretieren. const SUBMIT_SELECTORS = [ '[wire\\:click*="save"]', '[wire\\:click*="submit"]', '[wire\\:click*="update"]', '[wire\\:submit]', 'button[type="submit"]', ].join(','); document.addEventListener('click', (event) => { const trigger = event.target.closest(SUBMIT_SELECTORS); if (trigger) { scrollPending = true; } }, true); document.addEventListener('livewire:init', () => { if (!window.Livewire || typeof window.Livewire.hook !== 'function') { return; } window.Livewire.hook('commit', ({ succeed }) => { succeed(() => { if (!scrollPending) { return; } scrollPending = false; requestAnimationFrame(() => { const invalid = document.querySelector('[data-flux-control][aria-invalid="true"]') || document.querySelector('[aria-invalid="true"]') || document.querySelector('[data-flux-error]:not(:empty)'); if (!invalid) { return; } const field = invalid.closest('[data-flux-field]') || invalid.closest('[data-flux-control]') || invalid; field.scrollIntoView({ behavior: 'smooth', block: 'center' }); const focusable = field.querySelector('input, textarea, select, [contenteditable="true"]'); if (focusable && typeof focusable.focus === 'function') { // Kleine Verzögerung, damit der Scroll erst sichtbar startet, // bevor wir den Cursor reinpacken — sonst springt der Browser // direkt zum Element und das smooth-Scroll wirkt unruhig. setTimeout(() => focusable.focus({ preventScroll: true }), 320); } }); }); }); }); })();