Buchungskalender: Widget-Monatsnavigation und Tastaturfokus
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9b509fefbc
commit
bc9b4d9576
2 files changed
+49
-14
No files matched your search
+29
-14
@@ -296,12 +296,17 @@
|
||||
|
||||
/* ---- Kalender ---- */
|
||||
function monatKey(j, m) { return j * 12 + (m - 1) }
|
||||
function zweiSichtbar() { return (root.offsetWidth || root.clientWidth || 0) >= 1100 }
|
||||
function aktualisiereZus() {
|
||||
if (sel.von && sel.bis) { zus.textContent = WKU_LOGIK.zusammenfassung(sel.von, sel.bis); hint.textContent = '' }
|
||||
else if (sel.von) { zus.textContent = 'Anreise ' + langesDatum(sel.von) + '. Bitte Abreise wählen.'; hint.textContent = hinweis }
|
||||
else { zus.textContent = ''; hint.textContent = '' }
|
||||
if (hinweis && !sel.bis) hint.textContent = hinweis
|
||||
}
|
||||
function tageBisFrei(von, bis) {
|
||||
for (var d = von; d < bis; d = plusTage(d, 1)) if (WKU_LOGIK.nachtBelegt(d, belegt)) return false
|
||||
return true
|
||||
}
|
||||
function tagKnopf(tag) {
|
||||
var p = zerlege(tag)
|
||||
var vergangen = tag < heute
|
||||
@@ -318,29 +323,29 @@
|
||||
else if (istAn) kl.push('wku-an', 'wku-ab')
|
||||
if (istAb) kl.push('wku-ab')
|
||||
if (sel.von && sel.bis && tag > sel.von && tag < sel.bis) kl.push('wku-bereich')
|
||||
var status = (nb ? 'belegt' : 'frei') + (istAn ? ', Anreise' : '') + (istAb ? ', Abreise' : '')
|
||||
var status = (nb ? (sel.von && !sel.bis && tag > sel.von && !vergangen && !zu && !WKU_LOGIK.nachtBelegt(sel.von, belegt) && tageBisFrei(sel.von, tag) ? 'belegt – als Abreisetag möglich' : 'belegt') : 'frei') + (istAn ? ', Anreise' : '') + (istAb ? ', Abreise' : '')
|
||||
var b = h('button', { class: kl.join(' '), type: 'button', 'data-tag': tag, 'aria-label': langesDatum(tag) + ', ' + status, 'aria-pressed': (istAn || istAb) ? 'true' : 'false', tabindex: tag === fokusTag ? '0' : '-1' }, [h('span', { text: String(p[2]) })])
|
||||
if (vergangen || zu) { b.setAttribute('aria-disabled', 'true') }
|
||||
b.addEventListener('click', function () { tagKlick(tag) })
|
||||
return b
|
||||
}
|
||||
function monatBauen(j, m, erster, letzter) {
|
||||
function monatBauen(j, m, erster) {
|
||||
var kopf = h('div', { class: 'wku-monat-kopf' })
|
||||
var min = monatKey(heute.slice(0, 4) * 1, heute.slice(5, 7) * 1)
|
||||
var max = monatKey(bisMax.slice(0, 4) * 1, bisMax.slice(5, 7) * 1)
|
||||
var zur
|
||||
if (erster) {
|
||||
var zur = h('button', { class: 'wku-pfeil', type: 'button', 'aria-label': 'Vorheriger Monat', text: '‹' })
|
||||
zur = h('button', { class: 'wku-pfeil', type: 'button', 'data-nav': 'zur', 'aria-label': 'Vorheriger Monat', text: '‹' })
|
||||
if (monatKey(j, m) <= min) zur.setAttribute('disabled', '')
|
||||
zur.addEventListener('click', function () { blaettern(-1) })
|
||||
kopf.appendChild(zur)
|
||||
} else kopf.appendChild(h('span', { class: 'wku-pfeil-leer' }))
|
||||
kopf.appendChild(h('h3', { class: 'wku-monat-name', text: MONAT_LANG[m - 1] + ' ' + j }))
|
||||
if (letzter) {
|
||||
var vor = h('button', { class: 'wku-pfeil' + (erster ? ' wku-weiter-ein' : ''), type: 'button', 'aria-label': 'Nächster Monat', text: '›' })
|
||||
if (monatKey(j, m) >= max) vor.setAttribute('disabled', '')
|
||||
vor.addEventListener('click', function () { blaettern(1) })
|
||||
kopf.appendChild(vor)
|
||||
} else kopf.appendChild(h('span', { class: 'wku-pfeil-leer wku-weiter-ein' }))
|
||||
// Erster Monat: immer ein echter Weiter-Knopf (bei zwei sichtbaren Monaten per CSS ausgeblendet, dann sitzt er im zweiten Monat).
|
||||
var vor = h('button', { class: 'wku-pfeil' + (erster ? ' wku-weiter-ein' : ''), type: 'button', 'data-nav': erster ? 'vor1' : 'vor2', 'aria-label': 'Nächster Monat', text: '›' })
|
||||
if (monatKey(j, m) >= max) vor.setAttribute('disabled', '')
|
||||
vor.addEventListener('click', function () { blaettern(1) })
|
||||
kopf.appendChild(vor)
|
||||
var grid = h('div', { class: 'wku-raster', role: 'group', 'aria-label': MONAT_LANG[m - 1] + ' ' + j })
|
||||
WOCHE_KURZ.forEach(function (w, i) { grid.appendChild(h('div', { class: 'wku-wt', 'aria-hidden': 'true', text: w, title: WOCHE_LANG[i] })) })
|
||||
WKU_LOGIK.monatsRaster(j, m).forEach(function (z) { grid.appendChild(z.tag ? tagKnopf(z.tag) : h('div', { class: 'wku-leer' })) })
|
||||
@@ -351,8 +356,8 @@
|
||||
var a = ansicht
|
||||
var b = a.m === 12 ? { j: a.j + 1, m: 1 } : { j: a.j, m: a.m + 1 }
|
||||
monate.textContent = ''
|
||||
monate.appendChild(monatBauen(a.j, a.m, true, false))
|
||||
monate.appendChild(monatBauen(b.j, b.m, false, true))
|
||||
monate.appendChild(monatBauen(a.j, a.m, true))
|
||||
monate.appendChild(monatBauen(b.j, b.m, false))
|
||||
aktualisiereZus()
|
||||
if (fokus && fokusTag) {
|
||||
var k = monate.querySelector('[data-tag="' + fokusTag + '"]')
|
||||
@@ -367,7 +372,14 @@
|
||||
var max = monatKey(bisMax.slice(0, 4) * 1, bisMax.slice(5, 7) * 1)
|
||||
if (monatKey(j, m) < min || monatKey(j, m) > max) return
|
||||
ansicht = { j: j, m: m }
|
||||
var erster = iso(j, m, 1)
|
||||
fokusTag = erster < heute ? heute : erster
|
||||
zeichne(false)
|
||||
var zwei = zweiSichtbar()
|
||||
var wunsch = d > 0 ? (zwei ? 'vor2' : 'vor1') : 'zur'
|
||||
var knopf = monate.querySelector('[data-nav="' + wunsch + '"]')
|
||||
if (!knopf || knopf.hasAttribute('disabled')) knopf = monate.querySelector(d > 0 ? '[data-nav="zur"]' : (zwei ? '[data-nav="vor2"]' : '[data-nav="vor1"]'))
|
||||
if (knopf && knopf.focus) knopf.focus()
|
||||
}
|
||||
function tagKlick(tag) {
|
||||
if (tag < heute || tag > bisMax) return
|
||||
@@ -387,18 +399,21 @@
|
||||
if (ziel < heute || ziel > bisMax) return
|
||||
fokusTag = ziel
|
||||
var p = zerlege(ziel)
|
||||
var sichtbar = monatKey(p[0], p[1]) >= monatKey(ansicht.j, ansicht.m) && monatKey(p[0], p[1]) <= monatKey(ansicht.j, ansicht.m) + 1
|
||||
var sichtbar = monatKey(p[0], p[1]) >= monatKey(ansicht.j, ansicht.m) && monatKey(p[0], p[1]) <= monatKey(ansicht.j, ansicht.m) + (zweiSichtbar() ? 1 : 0)
|
||||
if (!sichtbar) ansicht = { j: p[0], m: p[1] }
|
||||
zeichne(true)
|
||||
})
|
||||
|
||||
function pad(n) { return zwei(n) }
|
||||
var ladeNr = 0
|
||||
function laden() {
|
||||
var nr = (ladeNr += 1)
|
||||
var jetzt = new Date()
|
||||
var von = jetzt.getUTCFullYear() + '-' + pad(jetzt.getUTCMonth() + 1) + '-' + pad(jetzt.getUTCDate())
|
||||
von = plusTage(von, -1)
|
||||
var bis = plusTage(von, 540)
|
||||
return holen(basis + '/belegung?unterkunft=' + encodeURIComponent(unterk.id) + '&von=' + von + '&bis=' + bis, { headers: { Accept: 'application/json' } }).then(function (r) {
|
||||
if (nr !== ladeNr) return
|
||||
if (!r.ok || !r.body.heute || !r.body.bisMax) throw new Error('belegung')
|
||||
belegt = r.body.belegt || []
|
||||
heute = r.body.heute
|
||||
@@ -439,12 +454,12 @@
|
||||
formBox.textContent = ''
|
||||
formBox.appendChild(h('p', { class: 'wku-danke', role: 'status', tabindex: '-1', text: 'Danke! Ihre Anfrage ' + r.body.nummer + ' ist eingegangen. Sie erhalten gleich eine Bestätigung per E-Mail.' }))
|
||||
var d = formBox.querySelector('.wku-danke'); if (d && d.focus) d.focus()
|
||||
ladenOderAusweichen()
|
||||
laden().then(null, function () {})
|
||||
return
|
||||
}
|
||||
var b = r.body || {}
|
||||
var gezeigt = false
|
||||
if (b.felder) Object.keys(b.felder).forEach(function (k) { if (feldFehler(k, String(b.felder[k]))) gezeigt = true })
|
||||
if (b.felder) Object.keys(b.felder).forEach(function (k) { if (Object.prototype.hasOwnProperty.call(b.felder, k) && feldFehler(k, String(b.felder[k]))) gezeigt = true })
|
||||
if (gezeigt) return
|
||||
if (b.fehler === 'belegt' || r.status === 409) { zeigeFehler('Dieser Zeitraum ist leider nicht mehr frei. Bitte wählen Sie einen anderen.'); laden().then(null, function () {}) }
|
||||
else if (r.status === 429) zeigeFehler('Zu viele Anfragen. Bitte versuchen Sie es später noch einmal.')
|
||||
|
||||
@@ -70,3 +70,23 @@ test('Pfeiltasten bewegen den Fokus', async () => {
|
||||
assert.equal(fx.document.activeElement.getAttribute('data-tag'), '2026-11-18')
|
||||
} finally { fx.restore() }
|
||||
})
|
||||
|
||||
test('Monatsnavigation: erster Monat hat aktiven Weiter-Knopf, Fokus bleibt, Tab-Stop im gezeigten Monat, Abreisetag-Label', async () => {
|
||||
const { fx, el } = start([{ status: 200, body: CFG }, { status: 200, body: BEL }])
|
||||
try {
|
||||
await fx.ruhe(); await fx.ruhe()
|
||||
const erster = fx.finde(el, '.wku-monat')
|
||||
const weiter = fx.finde(erster, '[data-nav="vor1"]')
|
||||
assert.ok(weiter && !weiter.hasAttribute('disabled'))
|
||||
await fx.klick(weiter)
|
||||
assert.match(fx.text(el), /Dezember 2026/)
|
||||
assert.equal(fx.document.activeElement.getAttribute('data-nav'), 'vor1')
|
||||
assert.equal(fx.finde(el, '[data-tag][tabindex="0"]').getAttribute('data-tag'), '2026-12-01')
|
||||
await fx.klick(fx.finde(el, '[data-nav="zur"]'))
|
||||
assert.equal(fx.document.activeElement.getAttribute('data-nav'), 'vor1') // Zurueck ist im ersten Monat gesperrt, Fokus wechselt zum Weiter-Knopf
|
||||
assert.equal(fx.finde(el, '[data-tag][tabindex="0"]').getAttribute('data-tag'), '2026-11-01')
|
||||
await fx.klick(tag(fx, el, '2026-11-18'))
|
||||
assert.match(tag(fx, el, '2026-11-20').getAttribute('aria-label'), /belegt – als Abreisetag möglich/)
|
||||
assert.doesNotMatch(tag(fx, el, '2026-11-22').getAttribute('aria-label'), /Abreisetag möglich/)
|
||||
} finally { fx.restore() }
|
||||
})
|
||||
Reference in new issue
Block a user