22-05-2026 Optimierung der User und Admin Panels
This commit is contained in:
parent
d2ba22c0cf
commit
e8c47b7553
73 changed files with 10282 additions and 1546 deletions
81
resources/js/portal-form-hooks.js
Normal file
81
resources/js/portal-form-hooks.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue