Compare commits
2 Commits
d3698a506d
...
v1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 04969a45ea | |||
| 3789f25d0d |
282
CHANGELOG.md
Normal file
282
CHANGELOG.md
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
Tutte le modifiche significative a questo progetto sono documentate in questo file.
|
||||||
|
|
||||||
|
Il formato si basa su [Keep a Changelog](https://keepachangelog.com/it/1.0.0/),
|
||||||
|
e questo progetto aderisce al [Versionamento Semantico](https://semver.org/lang/it/).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.0.0] - 2026-02-10
|
||||||
|
|
||||||
|
Rilascio iniziale di **Segnapunti Anto**, un'applicazione web Progressive Web App (PWA) professionale per il tracciamento in tempo reale dei punteggi durante partite di pallavolo.
|
||||||
|
|
||||||
|
### Funzionalità Principali
|
||||||
|
|
||||||
|
#### Gestione Partite e Punteggi
|
||||||
|
- **Tracciamento punti in tempo reale** per entrambe le squadre (casa e ospite)
|
||||||
|
- **Conteggio automatico dei set** con supporto per modalità al meglio di 3 o al meglio di 5
|
||||||
|
- **Logica regolamentare completa**:
|
||||||
|
- Set regolari: primo a 25 punti con almeno 2 di vantaggio
|
||||||
|
- Set decisivo (tie-break): primo a 15 punti con almeno 2 di vantaggio
|
||||||
|
- Blocco automatico assegnazione punti al raggiungimento della vittoria
|
||||||
|
- **Indicatore visivo del servizio** per identificare quale squadra è al servizio
|
||||||
|
- **Cronologia punti** con striscia visiva per seguire l'andamento della partita
|
||||||
|
- Possibilità di **incrementare e decrementare punti** e **set**
|
||||||
|
- **Annullamento punti** con ripristino automatico del servizio precedente
|
||||||
|
|
||||||
|
#### Formazioni e Rotazioni
|
||||||
|
- **Visualizzazione interattiva** della formazione in campo con 6 giocatori per squadra
|
||||||
|
- **Rotazione automatica regolamentare** quando la squadra conquista il servizio (cambio palla)
|
||||||
|
- **Configurazione manuale dei numeri di maglia** per ogni giocatore
|
||||||
|
- **Sistema di cambi giocatori**:
|
||||||
|
- Dialog dedicato per effettuare i cambi
|
||||||
|
- Supporto per cambi singoli o multipli
|
||||||
|
- Tabella IN/OUT con validazione degli input
|
||||||
|
- Verifica che i numeri di maglia siano numerici
|
||||||
|
- Scorciatoie da tastiera dedicate per squadra casa e ospite
|
||||||
|
- **Limitazione cambio palla manuale** solo a inizio set (0-0) per prevenire errori nella rotazione
|
||||||
|
|
||||||
|
#### Interfaccia Utente e Personalizzazione
|
||||||
|
- **Interfaccia fullscreen touch-friendly** ottimizzata per tablet e smartphone
|
||||||
|
- **Layout responsive** con media queries per schermi piccoli (<768px)
|
||||||
|
- **Modalità di visualizzazione**:
|
||||||
|
- Punteggio semplice
|
||||||
|
- Formazioni complete con posizioni giocatori
|
||||||
|
- Toggle tra le due modalità con scorciatoia da tastiera
|
||||||
|
- **Personalizzazione squadre**:
|
||||||
|
- Configurazione dinamica dei nomi squadre
|
||||||
|
- Inversione layout orizzontale (scambio posizione casa/ospite)
|
||||||
|
- Configurazione numeri di maglia giocatori
|
||||||
|
- **Controlli nascondibili**: possibilità di nascondere/mostrare barra pulsanti e cronologia
|
||||||
|
- **Stabilizzazione UI**: dimensioni fisse per riquadri punteggio per evitare spostamenti durante gli aggiornamenti
|
||||||
|
|
||||||
|
#### Controlli e Accessibilità
|
||||||
|
- **Controlli da tastiera completi** con scorciatoie dedicate:
|
||||||
|
- **Squadra Casa**: `Ctrl + ↑/↓` (punti), `Ctrl + →` (set), `Ctrl + C` (cambi)
|
||||||
|
- **Squadra Ospite**: `Shift + ↑/↓` (punti), `Shift + →` (set), `Shift + C` (cambi)
|
||||||
|
- **Comandi globali**:
|
||||||
|
- `Ctrl + ←` (cambio palla, solo a 0-0)
|
||||||
|
- `Ctrl + M` (configurazione)
|
||||||
|
- `Ctrl + B` (toggle barra pulsanti)
|
||||||
|
- `Ctrl + F` (fullscreen)
|
||||||
|
- `Ctrl + S` (annuncio vocale)
|
||||||
|
- `Ctrl + Z` (switch visualizzazione)
|
||||||
|
- **Sintesi vocale** per annunci punteggio in italiano usando Web Speech API
|
||||||
|
- **Sistema di alert professionale** usando Wave UI per notifiche e conferme
|
||||||
|
|
||||||
|
#### Progressive Web App (PWA)
|
||||||
|
- **Installabile** come app nativa su smartphone, tablet e desktop
|
||||||
|
- **Funzionamento offline completo** grazie ai Service Worker
|
||||||
|
- **Auto-update automatico** per ricevere nuovi aggiornamenti senza reinstallazione
|
||||||
|
- **Display fullscreen** per massimizzare lo spazio visivo
|
||||||
|
- **Orientamento landscape ottimizzato** per utilizzo su tablet
|
||||||
|
- **Orientamento sensor landscape** con supporto rotazione 180°
|
||||||
|
- **Icone PWA personalizzate** (192x192 e 512x512)
|
||||||
|
- **Prevenzione scroll indesiderato** su mobile (overscroll-behavior)
|
||||||
|
- **Supporto 100dvh** e position:fixed per layout mobile stabile
|
||||||
|
- **Blocco scroll** per evitare ricariche accidentali con swipe-down
|
||||||
|
|
||||||
|
#### Build e Deployment
|
||||||
|
- **Build APK Android** tramite Capacitor per distribuzione nativa
|
||||||
|
- **Setup automatico icone Android** con script dedicato per multi-densità (ldpi, mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi)
|
||||||
|
- **Configurazione Capacitor** ottimizzata per landscape senza splash screen
|
||||||
|
- **Output unificato** in `/dist/android` per build Android
|
||||||
|
- **Base path configurabile** (`/segnap`) per deployment su sottocartelle
|
||||||
|
|
||||||
|
### Tecnologie e Dipendenze
|
||||||
|
|
||||||
|
#### Stack Tecnologico
|
||||||
|
- **Vue 3.4.38** - Framework JavaScript reattivo
|
||||||
|
- **Vite 5.4.10** - Build tool e dev server veloce
|
||||||
|
- **Wave UI 3.17.0** - Libreria UI components per alert e dialogs
|
||||||
|
- **NoSleep.js 0.12.0** - Prevenzione standby durante le partite
|
||||||
|
- **vite-plugin-pwa 0.20.5** - Plugin per generazione PWA
|
||||||
|
- **Capacitor 6.2.0** - Framework per build app native Android/iOS
|
||||||
|
|
||||||
|
#### Ambiente di Sviluppo
|
||||||
|
- **Node.js 20.x LTS** (consigliato v20.2.0)
|
||||||
|
- **npm 9.0.0+** per gestione dipendenze
|
||||||
|
- **NVM support** per gestione versioni Node.js
|
||||||
|
- **Hot Module Replacement (HMR)** in modalità sviluppo
|
||||||
|
- **Source maps** per debugging
|
||||||
|
- **Vue DevTools** supportato
|
||||||
|
|
||||||
|
#### Browser Supportati
|
||||||
|
- **Chrome/Chromium 90+** - Supporto completo (consigliato)
|
||||||
|
- **Firefox 88+** - Supporto completo
|
||||||
|
|
||||||
|
### Build e Comandi
|
||||||
|
|
||||||
|
#### Comandi Disponibili
|
||||||
|
- `npm run dev` - Server di sviluppo con hot-reload su http://localhost:5173
|
||||||
|
- `npm run build` - Build di produzione ottimizzata in `/dist`
|
||||||
|
- `npm run preview` - Anteprima locale della build di produzione
|
||||||
|
|
||||||
|
#### Output Build
|
||||||
|
- File statici ottimizzati e minificati
|
||||||
|
- Service Worker generato automaticamente
|
||||||
|
- PWA manifest configurato
|
||||||
|
- Assets con hash per cache busting
|
||||||
|
- Permessi automatici per cartella dist in ambiente Docker
|
||||||
|
|
||||||
|
### Documentazione
|
||||||
|
- **README.md completo** con:
|
||||||
|
- Panoramica funzionalità
|
||||||
|
- Requisiti di sistema dettagliati
|
||||||
|
- Istruzioni di installazione e setup con NVM
|
||||||
|
- Guida ai comandi per sviluppo e build
|
||||||
|
- Tabella completa shortcuts tastiera
|
||||||
|
- Configurazione PWA documentata
|
||||||
|
- Spiegazione logica regolamentare pallavolo
|
||||||
|
- Browser testati e supportati
|
||||||
|
- **Encoding corretto** per caratteri accentati italiani
|
||||||
|
|
||||||
|
### Miglioramenti Architetturali
|
||||||
|
- **Separazione componenti**: HomePage estratta in componente dedicato
|
||||||
|
- **Rimozione codice legacy**: eliminati file non utilizzati (HelloWorld.vue)
|
||||||
|
- **Refactoring CSS**: file style.css organizzato e ottimizzato
|
||||||
|
- **Gestione servizio migliorata**: variabile booleana `servHome` per gestione cambio servizio
|
||||||
|
- **Validazione input**: controlli su numeri di maglia e cambi giocatori
|
||||||
|
- **Gestione errori**: prevenzione incrementi a set concluso senza notifiche spam
|
||||||
|
|
||||||
|
### Note di Sviluppo
|
||||||
|
- **Orientamento sensor landscape**: permette rotazione 180° del dispositivo
|
||||||
|
- **Fix tentati audio mobile**: implementati ma richiedono ancora debug (WIP)
|
||||||
|
- Aggiunto supporto @capacitor-community/text-to-speech per audio nativo
|
||||||
|
- Implementato fallback Web API su desktop, plugin nativo su mobile
|
||||||
|
- Correzione lingua da 'it_IT' a 'it-IT'
|
||||||
|
- **Java 17 → 21**: aggiornamento per compatibilità plugin TTS
|
||||||
|
- **ImageMagick in Dockerfile**: per generazione automatica icone Android
|
||||||
|
|
||||||
|
### Configurazione
|
||||||
|
- **Base path**: `/segnap` (configurabile in vite.config.js)
|
||||||
|
- **Tema PWA**: background `#eee`, theme color `#ffffff`
|
||||||
|
- **Display**: fullscreen landscape
|
||||||
|
- **Manifest name**: app_segnap
|
||||||
|
- **Short name**: segnap
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architettura e Funzionamento Interno
|
||||||
|
|
||||||
|
### Come Funziona l'Applicazione Web
|
||||||
|
|
||||||
|
L'applicazione è una **Single Page Application (SPA)** completamente **client-side**:
|
||||||
|
|
||||||
|
- **Server**: Serve solo file statici (nessun backend, nessun database)
|
||||||
|
- **Esecuzione**: Tutto gira nel browser dell'utente
|
||||||
|
- **Stato**: Memorizzato solo nella RAM del browser (si perde al refresh)
|
||||||
|
- **Offline**: Funziona completamente offline dopo la prima visita (Service Worker)
|
||||||
|
|
||||||
|
#### Build e Deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Vite compila il codice Vue/JavaScript
|
||||||
|
2. Ottimizza e minifica JavaScript e CSS
|
||||||
|
3. Genera Service Worker e manifest PWA
|
||||||
|
4. Output in `/dist` con file statici pronti
|
||||||
|
|
||||||
|
**Deploy**: Copia `/dist` su web server statico (nginx, Apache, Vercel, Netlify). Non serve Node.js, PHP o database sul server.
|
||||||
|
|
||||||
|
#### Funzionamento Runtime
|
||||||
|
|
||||||
|
**Prima visita:**
|
||||||
|
1. Browser scarica index.html dal server
|
||||||
|
2. Carica bundle JavaScript e CSS
|
||||||
|
3. Vue.js inizializza l'applicazione
|
||||||
|
4. Service Worker cachea tutti gli asset
|
||||||
|
5. App pronta (nessuna ulteriore chiamata al server)
|
||||||
|
|
||||||
|
**Visite successive:**
|
||||||
|
1. Service Worker serve i file dalla cache locale
|
||||||
|
2. App si avvia istantaneamente anche senza internet
|
||||||
|
3. In background verifica se esistono aggiornamenti
|
||||||
|
4. Aggiornamenti applicati al prossimo refresh
|
||||||
|
|
||||||
|
#### Gestione Stato
|
||||||
|
|
||||||
|
Tutto lo stato della partita vive nella memoria del browser:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
sp: {
|
||||||
|
punt: { home: 0, guest: 0 },
|
||||||
|
set: { home: 0, guest: 0 },
|
||||||
|
servHome: true,
|
||||||
|
form: { home: ["1","2","3","4","5","6"], guest: ["1","2","3","4","5","6"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Limitazioni:**
|
||||||
|
- Refresh della pagina azzera tutto lo stato
|
||||||
|
- Nessuna sincronizzazione tra dispositivi
|
||||||
|
- Nessuno storico partite
|
||||||
|
|
||||||
|
**Vantaggi:**
|
||||||
|
- Privacy totale (nessun dato esce dal dispositivo)
|
||||||
|
- Velocità massima (nessuna latenza di rete)
|
||||||
|
- Funzionamento offline completo
|
||||||
|
|
||||||
|
**Possibili evoluzioni:**
|
||||||
|
- Persistenza con localStorage
|
||||||
|
- Backend con database per multi-dispositivo
|
||||||
|
- WebSocket per sincronizzazione real-time
|
||||||
|
|
||||||
|
### Note Tecniche
|
||||||
|
|
||||||
|
**Problemi noti:**
|
||||||
|
- Audio mobile: sintesi vocale non funziona su alcuni dispositivi Android
|
||||||
|
- Persistenza: lo stato si perde al refresh della pagina
|
||||||
|
- Codice legacy: presenza di file HomePage duplicati
|
||||||
|
|
||||||
|
**Architettura futura:**
|
||||||
|
- Stato centralizzato con Pinia/Vuex
|
||||||
|
- Architettura client-server con WebSocket per display remoto
|
||||||
|
- Persistenza con localStorage o database
|
||||||
|
- Testing con Vitest e Cypress
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Informazioni sul Progetto
|
||||||
|
|
||||||
|
**Nome progetto**: Segnapunti Anto
|
||||||
|
**Descrizione**: Applicazione web PWA per tracciare i punteggi di partite di pallavolo in tempo reale
|
||||||
|
**Sviluppato per**: Team Antoniana
|
||||||
|
**Licenza**: Privata
|
||||||
|
**Repository**: https://github.com/[username]/segnapunti
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Come Leggere Questo Changelog
|
||||||
|
|
||||||
|
- **Funzionalità Principali**: nuove caratteristiche aggiunte all'applicazione
|
||||||
|
- **Tecnologie e Dipendenze**: stack tecnologico e versioni utilizzate
|
||||||
|
- **Build e Comandi**: istruzioni per compilare ed eseguire il progetto
|
||||||
|
- **Documentazione**: miglioramenti alla documentazione utente e sviluppatore
|
||||||
|
- **Miglioramenti Architetturali**: refactoring e ottimizzazioni del codice
|
||||||
|
- **Note di Sviluppo**: work in progress e problemi noti
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Supporto e Contributi
|
||||||
|
|
||||||
|
Per segnalare bug o richiedere funzionalità, aprire una issue nel repository del progetto.
|
||||||
|
|
||||||
|
Per contribuire al progetto:
|
||||||
|
1. Fork del repository
|
||||||
|
2. Creare un branch per la feature (`git checkout -b feature/nome-feature`)
|
||||||
|
3. Commit delle modifiche (`git commit -m 'Aggiunge nome-feature'`)
|
||||||
|
4. Push al branch (`git push origin feature/nome-feature`)
|
||||||
|
5. Aprire una Pull Request
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Data primo commit**: 2024
|
||||||
|
**Data rilascio 1.0.0**: 2026-02-10
|
||||||
@@ -252,4 +252,4 @@ Visualizzazione a 6 posizioni standard:
|
|||||||
└─────┴─────┴─────┘
|
└─────┴─────┴─────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
La rotazione avviene in senso orario: 1→2→3→4→5→6→1
|
La rotazione avviene in senso orario: 1→6→5→4→3→2→1
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<section class="homepage">
|
<section class="homepage">
|
||||||
<w-dialog v-model="diaNomi.show" :width="600" @close="abilitaTastiSpeciali()">
|
<w-dialog v-model="diaNomi.show" :width="600" @close="chiudiDialogConfig()">
|
||||||
<w-input v-model="sp.nomi.home" type="text" class="pa3">Nome Home</w-input>
|
<w-input v-model="sp.nomi.home" type="text" class="pa3" tabindex="1">Nome Home</w-input>
|
||||||
<w-input v-model="sp.nomi.guest" type="text" class="pa3">Nome Guest</w-input>
|
<w-input v-model="sp.nomi.guest" type="text" class="pa3" tabindex="2">Nome Guest</w-input>
|
||||||
|
|
||||||
<w-flex justify-center align-center class="pa3">
|
<w-flex justify-center align-center class="pa3">
|
||||||
<span class="mr3">Modalità partita:</span>
|
<span class="mr3">Modalità partita:</span>
|
||||||
@@ -9,14 +9,16 @@
|
|||||||
@click="modalitaPartita = '2/3'"
|
@click="modalitaPartita = '2/3'"
|
||||||
:bg-color="modalitaPartita === '2/3' ? 'success' : 'grey-light4'"
|
:bg-color="modalitaPartita === '2/3' ? 'success' : 'grey-light4'"
|
||||||
:dark="modalitaPartita === '2/3'"
|
:dark="modalitaPartita === '2/3'"
|
||||||
class="ma1">
|
class="ma1"
|
||||||
|
tabindex="-1">
|
||||||
2/3
|
2/3
|
||||||
</w-button>
|
</w-button>
|
||||||
<w-button
|
<w-button
|
||||||
@click="modalitaPartita = '3/5'"
|
@click="modalitaPartita = '3/5'"
|
||||||
:bg-color="modalitaPartita === '3/5' ? 'success' : 'grey-light4'"
|
:bg-color="modalitaPartita === '3/5' ? 'success' : 'grey-light4'"
|
||||||
:dark="modalitaPartita === '3/5'"
|
:dark="modalitaPartita === '3/5'"
|
||||||
class="ma1">
|
class="ma1"
|
||||||
|
tabindex="-1">
|
||||||
3/5
|
3/5
|
||||||
</w-button>
|
</w-button>
|
||||||
</w-flex>
|
</w-flex>
|
||||||
@@ -27,17 +29,17 @@
|
|||||||
<div class="campo-pallavolo">
|
<div class="campo-pallavolo">
|
||||||
<!-- Fila anteriore - index [3, 2, 1] - VICINO ALLA RETE (prima fila visualizzata) -->
|
<!-- Fila anteriore - index [3, 2, 1] - VICINO ALLA RETE (prima fila visualizzata) -->
|
||||||
<w-flex justify-center class="fila-anteriore">
|
<w-flex justify-center class="fila-anteriore">
|
||||||
<w-input v-model="sp.form.home[3]" type="text" style="width: 50px; text-align: center;" class="ma1"></w-input>
|
<w-input v-model="sp.form.home[3]" type="text" style="width: 50px; text-align: center;" class="ma1" tabindex="6"></w-input>
|
||||||
<w-input v-model="sp.form.home[2]" type="text" style="width: 50px; text-align: center;" class="ma1"></w-input>
|
<w-input v-model="sp.form.home[2]" type="text" style="width: 50px; text-align: center;" class="ma1" tabindex="5"></w-input>
|
||||||
<w-input v-model="sp.form.home[1]" type="text" style="width: 50px; text-align: center;" class="ma1"></w-input>
|
<w-input v-model="sp.form.home[1]" type="text" style="width: 50px; text-align: center;" class="ma1" tabindex="4"></w-input>
|
||||||
</w-flex>
|
</w-flex>
|
||||||
<!-- Linea dei 3 metri -->
|
<!-- Linea dei 3 metri -->
|
||||||
<div class="linea-tre-metri"></div>
|
<div class="linea-tre-metri"></div>
|
||||||
<!-- Fila posteriore - index [4, 5, 0] - ZONA DIFESA (seconda fila visualizzata) -->
|
<!-- Fila posteriore - index [4, 5, 0] - ZONA DIFESA (seconda fila visualizzata) -->
|
||||||
<w-flex justify-center class="fila-posteriore">
|
<w-flex justify-center class="fila-posteriore">
|
||||||
<w-input v-model="sp.form.home[4]" type="text" style="width: 50px; text-align: center;" class="ma1"></w-input>
|
<w-input v-model="sp.form.home[4]" type="text" style="width: 50px; text-align: center;" class="ma1" tabindex="7"></w-input>
|
||||||
<w-input v-model="sp.form.home[5]" type="text" style="width: 50px; text-align: center;" class="ma1"></w-input>
|
<w-input v-model="sp.form.home[5]" type="text" style="width: 50px; text-align: center;" class="ma1" tabindex="8"></w-input>
|
||||||
<w-input v-model="sp.form.home[0]" type="text" style="width: 50px; text-align: center;" class="ma1"></w-input>
|
<w-input v-model="sp.form.home[0]" type="text" style="width: 50px; text-align: center;" class="ma1" tabindex="3"></w-input>
|
||||||
</w-flex>
|
</w-flex>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,24 +49,24 @@
|
|||||||
<div class="campo-pallavolo">
|
<div class="campo-pallavolo">
|
||||||
<!-- Fila anteriore - index [3, 2, 1] - VICINO ALLA RETE (prima fila visualizzata) -->
|
<!-- Fila anteriore - index [3, 2, 1] - VICINO ALLA RETE (prima fila visualizzata) -->
|
||||||
<w-flex justify-center class="fila-anteriore">
|
<w-flex justify-center class="fila-anteriore">
|
||||||
<w-input v-model="sp.form.guest[3]" type="text" style="width: 50px; text-align: center;" class="ma1"></w-input>
|
<w-input v-model="sp.form.guest[3]" type="text" style="width: 50px; text-align: center;" class="ma1" tabindex="12"></w-input>
|
||||||
<w-input v-model="sp.form.guest[2]" type="text" style="width: 50px; text-align: center;" class="ma1"></w-input>
|
<w-input v-model="sp.form.guest[2]" type="text" style="width: 50px; text-align: center;" class="ma1" tabindex="11"></w-input>
|
||||||
<w-input v-model="sp.form.guest[1]" type="text" style="width: 50px; text-align: center;" class="ma1"></w-input>
|
<w-input v-model="sp.form.guest[1]" type="text" style="width: 50px; text-align: center;" class="ma1" tabindex="10"></w-input>
|
||||||
</w-flex>
|
</w-flex>
|
||||||
<!-- Linea dei 3 metri -->
|
<!-- Linea dei 3 metri -->
|
||||||
<div class="linea-tre-metri"></div>
|
<div class="linea-tre-metri"></div>
|
||||||
<!-- Fila posteriore - index [4, 5, 0] - ZONA DIFESA (seconda fila visualizzata) -->
|
<!-- Fila posteriore - index [4, 5, 0] - ZONA DIFESA (seconda fila visualizzata) -->
|
||||||
<w-flex justify-center class="fila-posteriore">
|
<w-flex justify-center class="fila-posteriore">
|
||||||
<w-input v-model="sp.form.guest[4]" type="text" style="width: 50px; text-align: center;" class="ma1"></w-input>
|
<w-input v-model="sp.form.guest[4]" type="text" style="width: 50px; text-align: center;" class="ma1" tabindex="13"></w-input>
|
||||||
<w-input v-model="sp.form.guest[5]" type="text" style="width: 50px; text-align: center;" class="ma1"></w-input>
|
<w-input v-model="sp.form.guest[5]" type="text" style="width: 50px; text-align: center;" class="ma1" tabindex="14"></w-input>
|
||||||
<w-input v-model="sp.form.guest[0]" type="text" style="width: 50px; text-align: center;" class="ma1"></w-input>
|
<w-input v-model="sp.form.guest[0]" type="text" style="width: 50px; text-align: center;" class="ma1" tabindex="9"></w-input>
|
||||||
</w-flex>
|
</w-flex>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</w-flex>
|
</w-flex>
|
||||||
|
|
||||||
<w-button @click="order = !order" class="ma2">Inverti ordine</w-button>
|
<w-button @click="order = !order" class="ma2" tabindex="-1">Inverti ordine</w-button>
|
||||||
<w-button bg-color="success" @click="diaNomi.show = false" class="ma2">
|
<w-button bg-color="success" @click="diaNomi.show = false" class="ma2" tabindex="-1">
|
||||||
Ok
|
Ok
|
||||||
</w-button>
|
</w-button>
|
||||||
</w-dialog>
|
</w-dialog>
|
||||||
|
|||||||
@@ -244,6 +244,79 @@ export default {
|
|||||||
apriDialogConfig() {
|
apriDialogConfig() {
|
||||||
this.disabilitaTastiSpeciali();
|
this.disabilitaTastiSpeciali();
|
||||||
this.diaNomi.show = true;
|
this.diaNomi.show = true;
|
||||||
|
|
||||||
|
// Aggiungi gestore Tab per il dialog
|
||||||
|
this.dialogConfigTabHandler = (e) => {
|
||||||
|
if (e.key === 'Tab' && this.diaNomi.show) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const dialog = document.querySelector('.w-dialog');
|
||||||
|
if (!dialog) return;
|
||||||
|
|
||||||
|
const allInputs = Array.from(dialog.querySelectorAll('input[type="text"]'))
|
||||||
|
.sort((a, b) => {
|
||||||
|
const tabA = parseInt(a.closest('[tabindex]')?.getAttribute('tabindex') || '0');
|
||||||
|
const tabB = parseInt(b.closest('[tabindex]')?.getAttribute('tabindex') || '0');
|
||||||
|
return tabA - tabB;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (allInputs.length === 0) return;
|
||||||
|
|
||||||
|
// Verifica se il focus è già dentro il dialog
|
||||||
|
const focusInDialog = dialog.contains(document.activeElement);
|
||||||
|
|
||||||
|
// Se non è nel dialog o non è in un input, vai al primo
|
||||||
|
if (!focusInDialog || !allInputs.includes(document.activeElement)) {
|
||||||
|
allInputs[0].focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigazione normale tra i campi
|
||||||
|
const currentIndex = allInputs.indexOf(document.activeElement);
|
||||||
|
let nextIndex;
|
||||||
|
|
||||||
|
if (e.shiftKey) {
|
||||||
|
nextIndex = currentIndex <= 0 ? allInputs.length - 1 : currentIndex - 1;
|
||||||
|
} else {
|
||||||
|
nextIndex = currentIndex >= allInputs.length - 1 ? 0 : currentIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allInputs[nextIndex]) {
|
||||||
|
allInputs[nextIndex].focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('keydown', this.dialogConfigTabHandler, true);
|
||||||
|
|
||||||
|
// Focus immediato + retry con timeout
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const focusFirst = () => {
|
||||||
|
const dialog = document.querySelector('.w-dialog');
|
||||||
|
if (dialog) {
|
||||||
|
const firstInput = dialog.querySelector('input[type="text"]');
|
||||||
|
if (firstInput) {
|
||||||
|
firstInput.focus();
|
||||||
|
firstInput.select();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prova immediatamente
|
||||||
|
if (!focusFirst()) {
|
||||||
|
// Se fallisce, riprova dopo un breve delay
|
||||||
|
setTimeout(focusFirst, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
chiudiDialogConfig() {
|
||||||
|
if (this.dialogConfigTabHandler) {
|
||||||
|
window.removeEventListener('keydown', this.dialogConfigTabHandler, true);
|
||||||
|
this.dialogConfigTabHandler = null;
|
||||||
|
}
|
||||||
|
this.abilitaTastiSpeciali();
|
||||||
},
|
},
|
||||||
resettaCambi(team) {
|
resettaCambi(team) {
|
||||||
const teams = team ? [team] : ["home", "guest"];
|
const teams = team ? [team] : ["home", "guest"];
|
||||||
|
|||||||
Reference in New Issue
Block a user