---
title: 'Erişilebilir Bileşen Kalıpları'
source: 'https://academia.sh/tr/kurslar/erisilebilir-kaliplar'
language: tr
updated: '2026-08-17T04:41:10+00:00'
license: 'CC BY-SA 4.0'
lessons: 25
outcomes:
    - 'Yaygın bileşenleri klavye ve ekran okuyucu davranışıyla birlikte belirtir'
    - 'Karmaşık etkileşim kalıplarının odak yönetimini tasarlar'
    - 'Arayüz metnini ve yerelleştirme gereksinimlerini sistemin parçası yapar'
---

# Erişilebilir Bileşen Kalıpları

Yaygın arayüz bileşenlerini rol, ad, durum, klavye sözleşmesi ve ölçülebilir kısıtlarıyla birlikte belirtim olarak yazmayı; karmaşık kalıplarda odak yönetimini ve arayüz metnini sistemin parçası kılmayı öğreten ileri kurs.

## Temel Bileşenler

Düğme, metin girdisi, seçim denetimi, açılır liste ve form kalıplarının rol, ad, durum ve klavye sözleşmesiyle belirtilmesi; bağlantı ile düğme ayrımının davranışa bağlanması.

### Düğmeler

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/dugmeler

Düğme belirtiminin altı bölümü, yerel ögenin getirdiği davranışlar, basılı kalma durumunun bildirimi, klavye sözleşmesi ve tıklanabilir alanın hedef boyutu ölçütleriyle denetimi.

Bileşen kataloğu kuruldu, adlandırma sözleşmesi yazıldı ve sürümleme ve katkı süreci işliyor.
Katalogdaki her kayıt bir ad, bir olgunluk düzeyi ve bir kullanım rehberi taşıyor. Ancak bir
bileşenin katalogda bulunması, onun ne yaptığının yazılı olduğu anlamına gelmez: "birincil düğme"
kaydı düğmenin nasıl göründüğünü söyler, klavyeden nasıl çalıştırıldığını, erişilebilirlik
ağacında hangi adla ve hangi durumla göründüğünü söylemez.

Bu kurs o eksik yarıyı yazar. Her ders bir bileşeni **belirtim** olarak ele alır ve altı
soruyu aynı sırayla yanıtlar. Kütüphane katalog arayüzü — arama alanı, süzgeç seçimleri,
sonuç tablosu, kayıt ayrıntısı ve ödünç alma formu — bütün derslerin ortak örneğidir. İlk
bileşen düğmedir, çünkü kalan bileşenlerin çoğu onu içerir.

#### Belirtimin Altı Bölümü

Bir bileşen belirtimi şu sırayla yazılır: bileşenin çözdüğü sorun ve ne zaman
kullanılmayacağı; yerel karşılığın ne olduğu; rol, ad ve durumun nereden hesaplandığı;
klavye sözleşmesi; ölçülebilir kısıtlar; sık yapılan hata ve nasıl saptandığı.

Sıra keyfi değildir. İlk iki bölüm yanıtlanmadan üçüncüsüne geçilirse, yerel bir öge
yerine anlamsız bir kapsayıcı seçilmiş olur ve kalan dört bölüm o yanlışın üzerine yazılır.
Frontend Kalitesi kursundaki birinci kural bu yüzden belirtimin de ikinci maddesidir:
gereken anlamı taşıyan yerel bir öge varsa ARIA kullanılmaz.

#### Düğmenin İşi

Düğme, sayfada **bir eylem başlatan** denetimdir: aramayı çalıştırmak, süzgeçleri
temizlemek, bir kaydı ödünç alma listesine eklemek, formu göndermek. Ortak özellikleri,
çalıştırıldığında adres çubuğundaki adresin değişmemesidir.

Düğme kullanılmayacağı yer de aynı ölçütle belirlenir: kullanıcı başka bir kaynağa
gidiyorsa bileşen düğme değil bağlantıdır. Katalogda bir kayda tıklamak kayıt ayrıntısı
sayfasını açar; bu bir eylem değil, bir gezinmedir. Ayrımın tam kuralı ve ihlalinin nasıl
saptandığı bu konunun son dersindedir.

#### Yerel Öge Önce

Karşılık `button` ögesidir ve `type` özniteliği üç değer alır. `submit` değeri ögeyi form
gönderimine bağlar; öznitelik hiç yazılmadığında öge, `command`/`commandfor` taşımıyorsa
**gönderim düğmesi gibi davranır**; `button` değeri hiçbir varsayılan eylem taşımaz; davranışı
yalnızca bağlanan kod belirler; `reset` değeri alanları başlangıç değerlerine döndürür.

Bu ayrımın atlanması somut bir kırılma üretir: ödünç alma formunun içine konan "Süzgeci
temizle" düğmesine `type="button"` yazılmadığında düğme formu gönderir. Kullanıcı bir
alanı temizlemek isterken kaydı ödünç almış olur.

Yerel öge, rolün yanında beş şey daha getirir: odaklanabilirlik, giriş ve boşluk tuşlarıyla
çalıştırılabilirlik, devre dışı durumunun ağaca bildirimi, form gönderimiyle ilişki ve
gönderici olarak gönderim listesine kendi ad–değer çiftini eklemesi. Bunların hiçbiri
`div` ögesine rol yazılarak gelmez.

#### Rol, Ad ve Durum

Rol örtük olarak `button` ögesinden gelir; ayrıca bildirilmez.

Ad, düğmenin içeriğinden hesaplanır. Metin taşıyan düğmelerde bu kendiliğinden çalışır.
Yalnız simge taşıyan düğmelerde — sonuç tablosundaki işaretleme ve ödünç alma
düğmelerinde — simgenin alternatif metni boşsa düğme adsız kalır. Katalog belirtiminde
kural şudur: simge düğmelerinin adı, simgeye değil düğmeye yazılır; simge her koşulda
sunumsaldır.

İki durum bildirimi düğmeye aittir. Birincisi devre dışı olmaktır: `disabled` özniteliği hem ağaca
bildirir hem ögeyi odak dışına çıkarır. `aria-disabled="true"` yalnızca bildirir — öge odak alır
ve tıklanabilir kalır; bu, kullanıcının düğmeyi bulup neden çalışmadığını okuyabilmesi
istendiğinde seçilir ve tıklamanın kod tarafında yok sayılması gerekir.

İkincisi **basılı kalma durumudur** (`aria-pressed`): iki durumlu bir düğmenin açık mı
kapalı mı olduğunu bildirir. Katalogda "Yalnız rafta olanlar" süzgeci böyle bir düğmedir.
Bu durum, Arayüz Tasarımının Temelleri kursunda tanımlanan geçici **basılı** etkileşim
durumundan ayrıdır: biri tuş bırakılınca biter, diğeri kullanıcı yeniden basana kadar
sürer. Görsel tasarımın ikisini ayrı kanallarda kodlaması gerekir.

#### Klavye Sözleşmesi

| Tuş | Davranış |
|---|---|
| `Tab` | Odağı düğmeye getirir; her düğme kendi başına bir sekme durağıdır. |
| `Shift+Tab` | Önceki sekme durağına döner. |
| `Enter` | Düğmeyi çalıştırır; tuşa basıldığı anda tetiklenir. |
| `Boşluk` | Düğmeyi çalıştırır; tuş **bırakıldığında** tetiklenir. |

İki tuşun tetiklenme anı arasındaki fark yerel ögede kendiliğinden vardır ve elle yazılan
bir düğmede yeniden üretilmesi gerekir. Boşluk tuşunun bırakılışta tetiklenmesi, kullanıcıya
tuşu basılı tutup düğmeden vazgeçme olanağı verir; giriş tuşunun basılışta tetiklenmesi ise
form gönderiminin beklenen davranışıdır.

Odak, düğme çalıştırıldıktan sonra düğmede kalır. Düğme sayfadan kaldırılıyorsa —
"Süzgeci temizle" düğmesi süzgeç kalmadığında gizleniyorsa — odağın nereye gideceği
belirtilmek zorundadır; belirtilmezse odak belgenin köküne düşer.

#### Tıklanabilir Alan Ölçülür

Düğmenin görsel boyutu bir tasarım tercihi değil, ölçütlere bağlı bir kısıttır. 2.5.8 Hedef
Boyutu (En Az) ölçütü her hedefin en az 24×24 CSS pikseli olmasını ister ve beş istisna
tanır; bunların en çok işe yarayanı **aralık istisnasıdır**: küçük kalan bir hedefin
merkezine yerleştirilen 24 piksel çaplı daire, başka bir hedefe ya da başka bir küçük
hedefin dairesine değmiyorsa ölçüt karşılanır. 2.5.5 Hedef Boyutu (Gelişmiş) ölçütü aynı
ölçüyü 44 piksele çıkarır.

```js
// hedef.mjs — dugme hedef boyutunun 2.5.8 ve 2.5.5 olcutlerine gore denetimi

// Katalog arac cubugu ve sonuc tablosu satiri: CSS pikseli cinsinden kutular.
// x,y sol ust kose; w,h genislik ve yukseklik.
const ARAYUZ = [
  { ad: "button#ara",              x: 16,  y: 16, w: 88,  h: 44 },
  { ad: "button#suzgecleri-sil",   x: 112, y: 16, w: 140, h: 44 },
  { ad: "button#gorunum-liste",    x: 600, y: 26, w: 24,  h: 24 },
  { ad: "button#gorunum-izgara",   x: 628, y: 26, w: 24,  h: 24 },
  { ad: "a#kayit[K-118]",          x: 60,  y: 80, w: 220, h: 20 },
  { ad: "button#isaretle[K-118]",  x: 700, y: 80, w: 20,  h: 20 },
  { ad: "button#odunc[K-118]",     x: 720, y: 80, w: 20,  h: 20 },
];

const EN_KUCUK = 24;   // 2.5.8 Hedef Boyutu (En Az), AA
const GELISMIS = 44;   // 2.5.5 Hedef Boyutu (Gelismis), AAA

const merkez = (t) => [t.x + t.w / 2, t.y + t.h / 2];

// 24 px capli daire ile bir kutunun kesisip kesismedigi
function daireKutuKesisir(t, k) {
  const [cx, cy] = merkez(t);
  const yx = Math.max(k.x, Math.min(cx, k.x + k.w));
  const yy = Math.max(k.y, Math.min(cy, k.y + k.h));
  return Math.hypot(cx - yx, cy - yy) < EN_KUCUK / 2;
}

// Iki 24 px capli dairenin kesismesi: merkezler arasi uzaklik < 24
function daireDaireKesisir(a, b) {
  const [ax, ay] = merkez(a);
  const [bx, by] = merkez(b);
  return Math.hypot(ax - bx, ay - by) < EN_KUCUK;
}

const kucuk = (t) => Math.min(t.w, t.h) < EN_KUCUK;

// Aralik istisnasi: kucuk hedefin dairesi baska bir hedefe ya da
// baska bir kucuk hedefin dairesine degmemeli.
function aralikIstisnasi(t, hepsi) {
  const carpisan = [];
  for (const b of hepsi) {
    if (b === t) continue;
    const carpti = kucuk(b) ? daireDaireKesisir(t, b) : daireKutuKesisir(t, b);
    if (carpti) carpisan.push(b.ad);
  }
  return carpisan;
}

function denetle(baslik, arayuz) {
  console.log(baslik);
  console.log("hedef                    olcu     en kucuk kenar  2.5.8   2.5.5   aralik istisnasi");
  for (const t of arayuz) {
    const kenar = Math.min(t.w, t.h);
    const carpisan = kucuk(t) ? aralikIstisnasi(t, arayuz) : [];
    const gecer258 = !kucuk(t) || carpisan.length === 0;
    const gecer255 = Math.min(t.w, t.h) >= GELISMIS;
    const not = !kucuk(t)
      ? "gerekmiyor"
      : carpisan.length === 0
        ? "uygulandi"
        : "carpisan: " + carpisan.join(", ");
    console.log(
      t.ad.padEnd(24) +
        `${t.w}x${t.h}`.padEnd(9) +
        String(kenar).padStart(14) +
        (gecer258 ? "  gecti" : "  KALDI").padEnd(8) +
        (gecer255 ? "  gecti" : "  KALDI").padEnd(8) +
        "  " + not,
    );
  }
  const kalan = arayuz.filter((t) => kucuk(t) && aralikIstisnasi(t, arayuz).length > 0);
  console.log(`2.5.8 ihlali: ${kalan.length} hedef`);
}

denetle("mevcut arac cubugu ve satir denetimleri:", ARAYUZ);

// Duzeltme: satir ici denetimler 24x24'e cikarilir ve aralari 8 px acilir.
const DUZELTILMIS = ARAYUZ.map((t) => {
  if (t.ad === "button#isaretle[K-118]") return { ...t, w: 24, h: 24, x: 696, y: 78 };
  if (t.ad === "button#odunc[K-118]") return { ...t, w: 24, h: 24, x: 728, y: 78 };
  return t;
});

console.log("");
denetle("satir ici denetimler 24x24, aralik 8 px:", DUZELTILMIS);

// Hedefi buyutmeden cozmek: kutu ayni kalir, aralik acilir.
const YALNIZ_ARALIK = ARAYUZ.map((t) =>
  t.ad === "button#odunc[K-118]" ? { ...t, x: 726 } : t,
);

console.log("");
denetle("kutular 20x20 kaldi, yalniz aralik 6 px acildi:", YALNIZ_ARALIK);
```

```
mevcut arac cubugu ve satir denetimleri:
hedef                    olcu     en kucuk kenar  2.5.8   2.5.5   aralik istisnasi
button#ara              88x44                44  gecti   gecti   gerekmiyor
button#suzgecleri-sil   140x44               44  gecti   gecti   gerekmiyor
button#gorunum-liste    24x24                24  gecti   KALDI   gerekmiyor
button#gorunum-izgara   24x24                24  gecti   KALDI   gerekmiyor
a#kayit[K-118]          220x20               20  gecti   KALDI   uygulandi
button#isaretle[K-118]  20x20                20  KALDI   KALDI   carpisan: button#odunc[K-118]
button#odunc[K-118]     20x20                20  KALDI   KALDI   carpisan: button#isaretle[K-118]
2.5.8 ihlali: 2 hedef

satir ici denetimler 24x24, aralik 8 px:
hedef                    olcu     en kucuk kenar  2.5.8   2.5.5   aralik istisnasi
button#ara              88x44                44  gecti   gecti   gerekmiyor
button#suzgecleri-sil   140x44               44  gecti   gecti   gerekmiyor
button#gorunum-liste    24x24                24  gecti   KALDI   gerekmiyor
button#gorunum-izgara   24x24                24  gecti   KALDI   gerekmiyor
a#kayit[K-118]          220x20               20  gecti   KALDI   uygulandi
button#isaretle[K-118]  24x24                24  gecti   KALDI   gerekmiyor
button#odunc[K-118]     24x24                24  gecti   KALDI   gerekmiyor
2.5.8 ihlali: 0 hedef

kutular 20x20 kaldi, yalniz aralik 6 px acildi:
hedef                    olcu     en kucuk kenar  2.5.8   2.5.5   aralik istisnasi
button#ara              88x44                44  gecti   gecti   gerekmiyor
button#suzgecleri-sil   140x44               44  gecti   gecti   gerekmiyor
button#gorunum-liste    24x24                24  gecti   KALDI   gerekmiyor
button#gorunum-izgara   24x24                24  gecti   KALDI   gerekmiyor
a#kayit[K-118]          220x20               20  gecti   KALDI   uygulandi
button#isaretle[K-118]  20x20                20  gecti   KALDI   uygulandi
button#odunc[K-118]     20x20                20  gecti   KALDI   uygulandi
2.5.8 ihlali: 0 hedef
```

Üç koşum üç ayrı bilgi veriyor.

Birinci koşumda araç çubuğu ölçütü karşılıyor; sonuç tablosunun satır içi denetimleri
karşılamıyor. İki düğmenin merkezleri arasındaki uzaklık 20 piksel; iki dairenin kesişmemesi için
gereken 24. Kayıt bağlantısı da 20 piksel yüksekliğinde ama aralık istisnasını sağlıyor, çünkü
çevresinde başka hedef yok.

İkinci koşum, doğrudan çözümü uyguluyor: kutular 24×24'e çıkarıldığında istisnaya hiç
gerek kalmıyor. Üçüncü koşum ise ölçütün yanlış okunmasını düzeltiyor — kutuların
büyütülmesi zorunlu değildir. Yalnızca aralığın 6 piksele çıkarılması, merkezleri 26
piksele taşıyarak ölçütü karşılatıyor. Yoğun bir sonuç tablosunda satır yüksekliğini
korumak gerektiğinde kullanılabilecek yol budur.

2.5.5 sütunu bütün simge düğmelerinde "KALDI" gösteriyor. Bu bir ihlal değil: 44 piksel
ölçüsü AAA düzeyindedir. Sütunun belirtimde bulunması, dokunmatik kullanım hedefleniyorsa
hangi düğmelerin büyütülmesi gerektiğini önceden gösterir.

#### Hiyerarşi Davranış Farkı Değildir

Katalog araç çubuğunda üç görsel ağırlık kullanılır: dolgulu birincil düğme ("Ara"),
çerçeveli ikincil düğme ("Süzgeçleri temizle") ve dolgusuz düz düğme (görünüm
değiştiriciler). Bu ayrım tümüyle görseldir; üçünün rolü, adı, klavye sözleşmesi ve hedef
boyutu ölçütü aynıdır.

Belirtimde yazılması gereken tek kural, bir görünümde **en çok bir birincil düğme**
bulunmasıdır. İki birincil düğme, görsel hiyerarşinin taşıdığı "önerilen eylem budur"
bilgisini siler. Bu kural bir uygunluk ölçütünden gelmez; Arayüz Tasarımının Temelleri
kursundaki karşıtlık ve vurgu kararının bileşen düzeyindeki karşılığıdır.

#### Sık Yapılan Hata

En sık üretilen üç kusur ve saptama yolları şunlardır.

**Adsız simge düğmesi.** Alternatif metni boş bir simge taşıyan düğme ağaçta adsız görünür.
Saptaması araçsızdır: sayfadaki her düğmenin erişilebilir adı listelenir, boş olan aranır.
Otomatik denetleyicilerin varlık kuralı bunu yakalar.

**Görünen metinle adın ayrışması.** Düğmenin üzerinde "Ödünç al" yazarken `aria-label`
bildirimi "Ekle" diyorsa, sesle arayüz kullanan kişinin söylediği ad hiçbir ögeye uymaz;
2.5.3 Addaki Etiket ölçütü bunu yasaklar. Saptaması, görünen metnin erişilebilir adın içinde
geçip geçmediğinin denetlenmesidir.

**Devre dışı düğmenin gerekçesiz kalması.** Rafta olmayan bir kayıt için ödünç alma
düğmesi `disabled` yazılarak kapatıldığında düğme odak dışına çıkar ve kullanıcı nedeni
okuyamaz. Saptaması bir klavye turudur: sekme tuşuyla gezilen sayfada bulunamayan ama
ekranda görünen her denetim bu durumdadır. Belirtimin kararı, nedenin okunması gerekiyorsa
düğmenin `aria-disabled` ile bildirilmesi ve nedenin `aria-describedby` ile bağlanmasıdır.

#### Özet

- Bileşen belirtimi altı bölümden oluşur: sorun ve kullanılmayacağı yer, yerel karşılık,
  rol–ad–durum, klavye sözleşmesi, ölçülebilir kısıtlar, sık yapılan hata ve saptaması.
- Düğme eylem başlatır; adres değişiyorsa bileşen düğme değildir. `type` özniteliği
  yazılmadığında form içindeki düğme gönderim düğmesi olur.
- Ad düğme içeriğinden hesaplanır; simge düğmelerinde ad düğmeye yazılır. `disabled` ögeyi
  odak dışına çıkarır, `aria-disabled` yalnızca bildirir ve odakta bırakır.
- Giriş tuşu basılışta, boşluk tuşu bırakılışta tetikler; elle yazılan bir düğmede bu fark
  yeniden üretilmelidir.
- 2.5.8 her hedeften 24×24 CSS pikseli ister; aralık istisnası, kutuyu büyütmeden
  merkezler arası uzaklığı 24 pikselin üstüne çıkararak da karşılanabilir.
- Birincil, ikincil ve düz düğme ayrımı yalnızca görseldir; bir görünümde en çok bir
  birincil düğme bulunur.

#### Sonraki Adım

Düğme, kullanıcıdan hiçbir şey almadan çalışan bir denetimdir: adı sabittir, değeri yoktur
ve tek bildirdiği şey basılıp basılmadığıdır. Katalog arayüzünün asıl veri toplayan
bileşeni ise arama alanıdır ve orada ad tek başına yetmez — alanın bir etiketi, bir biçim
ipucu, bir yardım metni ve gerektiğinde bir hata iletisi vardır. Bunların dördü de ekranda
alanın çevresinde durur, ancak erişilebilirlik ağacında birbirinden ayrı bildirimlerdir ve
duyurulma sıraları farklıdır. Sonraki ders metin girdisini bu dört metnin düzeni üzerinden
belirtir.

### Metin Girdileri

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/metin-girdileri

Etiket, yardım metni, hata iletisi ve yer tutucunun erişilebilirlik ağacındaki ayrı yerleri; ad ve ek açıklamanın hesabı, hata bağının denetimi ve alanın klavye sözleşmesi.

Düğme belirtiminde ad tek bir kaynaktan geliyordu ve değişmiyordu. Katalogun arama alanı ile
ödünç alma formunun üye numarası alanı bu bakımdan farklıdır: ekranda alanın çevresinde dört
ayrı metin durabilir — etiket, yardım metni, hata iletisi ve alanın içindeki yer tutucu.
Dördü de göze aynı uzaklıkta görünür.

Erişilebilirlik ağacında ise dördü aynı yerde değildir. Biri alanın **adı** olur, biri **ek
açıklaması**, biri her ikisinin dışında kalır ve biri de yanlış yazıldığında diğerlerinin
üstüne biner. Bu ders o dağılımı hesaplar.

#### Alanın Çözdüğü Sorun

Metin girdisi, kullanıcının **önceden sayılamayan** bir değer yazdığı denetimdir: bir arama
dizgisi, bir üye numarası, görevliye iletilecek bir not. Ölçüt sayılabilirliktir. Ödünç alma
formundaki "ödünç süresi" alanı üç değer alabiliyorsa metin girdisi değildir; seçenekler
sayılabildiğinde seçim denetimi ya da açılır liste kullanılır.

İkinci sınır biçimdir. Tarih, saat, renk ve dosya gibi değerlerin serbest metin olarak
istenmesi, kullanıcıya biçimi tahmin ettirir ve doğrulama yükünü tümüyle koda bırakır. Bu
değerler için tanımlı girdi türleri vardır.

#### Yerel Öge Önce

Karşılık `input` ögesidir; çok satırlı metin için `textarea`. `type` özniteliği alanın hem
rolünü hem doğrulamasını belirler: `search` arama kutusu rolünü, `email` posta adresi
denetimini, `number` alt–üst sınırlı sayı alanı rolünü, `date` tarih seçimini getirir.

Türün seçilmesi üç şeyi birden yapar. Erişilebilirlik ağacındaki rol değişir; tarayıcının
yerleşik kısıt doğrulaması devreye girer; dokunmatik aygıtlarda alana uygun tuş takımı
açılır. Üçü de ayrıca yazılmaz.

Bir alanın hangi kişisel veriyi topladığı ayrıca bildirilebilir. `autocomplete` özniteliği
üye adı, adres ve posta adresi gibi alanlarda değerin **amacını** program yoluyla
belirtir; 1.3.5 Girdi Amacını Belirleme ölçütünün karşılığı budur ve kullanıcının kendi
verisini otomatik doldurmasını da sağlar.

#### Dört Metnin Ağaçtaki Yeri

**Etiket** alanın adıdır ve kalıcıdır. `label` ögesiyle yazılır; `for` özniteliği alanın
kimliğine bağlanır ya da alan etiketin içine alınır.

**Yardım metni** biçim ya da kısıt bilgisidir: "Kart üzerindeki 8 haneli numara". Alanın
adı değildir, ek açıklamasıdır ve `aria-describedby` ile bağlanır. Ekranda alanın
**altında değil üstünde** durması yeğlenir; alanın altındaki metin, alan doldurulurken
açılan tuş takımının altında kalabilir.

**Hata iletisi** de bir ek açıklamadır ve aynı düzenekle bağlanır. Yardım metninin yerine
değil, **yanına** yazılır.

**Yer tutucu** alanın içinde duran örnek değerdir ve alan doldurulduğunda kaybolur. Ad
hesabında en son sıradadır: başka kaynak yoksa ada dönüşür. Bu, onun etiket yerine
kullanılabileceği anlamına gelmez.

```js
// girdi.mjs — metin girdisinde ad, ek aciklama ve durum bildirimlerinin hesabi

// Belgede metin tasiyan dugumler (label ve aria-describedby hedefleri)
const BELGE = {
  "uye-yardim": "Kart üzerindeki 8 haneli numara.",
  "uye-hata": "Üye numarası 8 hane olmalı.",
  "iade-yardim": "En çok 21 gün sonrası seçilebilir.",
  "label:ara": "Katalogda ara",
  "label:uye-no": "Üye numarası",
  "label:iade": "İade tarihi",
};

// Ad sirasi: aria-labelledby -> aria-label -> label ogesi -> title -> placeholder
function ad(a) {
  if (a["aria-labelledby"]) {
    const p = a["aria-labelledby"].split(" ").map((k) => BELGE[k]).filter(Boolean);
    if (p.length > 0) return [p.join(" "), "aria-labelledby"];
  }
  if (a["aria-label"]) return [a["aria-label"], "aria-label"];
  if (BELGE["label:" + a.id]) return [BELGE["label:" + a.id], "label ogesi"];
  if (a.title) return [a.title, "title (son care)"];
  if (a.placeholder) return [a.placeholder, "placeholder (son care)"];
  return ["", "ad yok"];
}

// Aciklama sirasi: aria-describedby -> title -> placeholder (ada gitmemisse)
function aciklama(a, adKaynagi) {
  if (a["aria-describedby"]) {
    const p = a["aria-describedby"].split(" ").map((k) => BELGE[k]).filter(Boolean);
    if (p.length > 0) return [p.join(" "), "aria-describedby"];
  }
  if (a.title && adKaynagi !== "title (son care)") return [a.title, "title"];
  if (a.placeholder && adKaynagi !== "placeholder (son care)") return [a.placeholder, "placeholder"];
  return ["", "aciklama yok"];
}

function durumlar(a) {
  const d = [];
  if (a.required || a["aria-required"] === "true") d.push("zorunlu");
  if (a["aria-invalid"] === "true") d.push("gecersiz");
  if (a.readonly) d.push("salt okunur");
  if (a.disabled) d.push("devre disi");
  return d.length ? d.join(", ") : "-";
}

// --- Katalog arama alani ve odunc alma formu alanlari -----------------------
const ALANLAR = [
  ["etiket + yer tutucu",
   { id: "ara", type: "search", placeholder: "başlık, yazar ya da ISBN" }],
  ["yalniz yer tutucu",
   { id: "ara-2", type: "search", placeholder: "başlık, yazar ya da ISBN" }],
  ["etiket + yardim metni",
   { id: "uye-no", required: true, "aria-describedby": "uye-yardim" }],
  ["hata yardimin yerine",
   { id: "uye-no", required: true, "aria-invalid": "true", "aria-describedby": "uye-hata" }],
  ["hata yardima eklendi",
   { id: "uye-no", required: true, "aria-invalid": "true",
     "aria-describedby": "uye-yardim uye-hata" }],
  ["hata bagi kopuk",
   { id: "uye-no", required: true, "aria-invalid": "true",
     "aria-describedby": "uye-yardim uye-hata-2" }],
  ["tarih, etiket + yardim",
   { id: "iade", type: "date", "aria-describedby": "iade-yardim" }],
  ["yalniz title",
   { id: "not", title: "Görevliye iletilecek not" }],
  ["aria-label etiketi bastiriyor",
   { id: "uye-no", "aria-label": "Numara" }],
];

const G = 31;   // alan sutunu genisligi

console.log("alan".padEnd(G) + "erisilebilir ad".padEnd(28) + "ad kaynagi".padEnd(24) + "durum");
for (const [baslik, a] of ALANLAR) {
  const [n, k] = ad(a);
  console.log(baslik.padEnd(G) + (n === "" ? "(bos)" : '"' + n + '"').padEnd(28) +
    k.padEnd(24) + durumlar(a));
}

console.log("\n" + "alan".padEnd(G) + "ek aciklama".padEnd(64) + "kaynak");
for (const [baslik, a] of ALANLAR) {
  const [, k] = ad(a);
  const [c, ck] = aciklama(a, k);
  console.log(baslik.padEnd(G) + (c === "" ? "(bos)" : '"' + c + '"').padEnd(64) + ck);
}

// --- Kural denetimi ---------------------------------------------------------
const GORUNEN = { ara: "Katalogda ara", "uye-no": "Üye numarası", iade: "İade tarihi" };

console.log("\nkural denetimi:");
let bulgu = 0;
for (const [baslik, a] of ALANLAR) {
  const [n, k] = ad(a);
  const [c] = aciklama(a, k);
  const b = [];
  if (n === "") b.push("alan adsiz");
  if (k.includes("son care")) b.push("ad son care kaynaktan geliyor, kalici etiket yok");
  const gorunen = GORUNEN[a.id];
  if (gorunen && n && !n.toLocaleLowerCase("tr").includes(gorunen.toLocaleLowerCase("tr")))
    b.push(`gorunen etiket ("${gorunen}") erisilebilir adin icinde yok`);
  if (a["aria-describedby"]) {
    const kopuk = a["aria-describedby"].split(" ").filter((x) => !BELGE[x]);
    if (kopuk.length) b.push("kopuk basvuru: " + kopuk.join(", "));
  }
  if (a["aria-invalid"] === "true" && !c.includes(BELGE["uye-hata"]))
    b.push("gecersiz alan, hata iletisi aciklamaya girmiyor");
  if (a["aria-invalid"] === "true" && a["aria-describedby"] &&
      !a["aria-describedby"].split(" ").includes("uye-yardim"))
    b.push("hata bildirimi yardim metnini bastirdi");
  for (const x of b) { bulgu++; console.log("  " + baslik.padEnd(G) + x); }
}
console.log(`\n${ALANLAR.length} alanda ${bulgu} bulgu`);
```

```
alan                           erisilebilir ad             ad kaynagi              durum
etiket + yer tutucu            "Katalogda ara"             label ogesi             -
yalniz yer tutucu              "başlık, yazar ya da ISBN"  placeholder (son care)  -
etiket + yardim metni          "Üye numarası"              label ogesi             zorunlu
hata yardimin yerine           "Üye numarası"              label ogesi             zorunlu, gecersiz
hata yardima eklendi           "Üye numarası"              label ogesi             zorunlu, gecersiz
hata bagi kopuk                "Üye numarası"              label ogesi             zorunlu, gecersiz
tarih, etiket + yardim         "İade tarihi"               label ogesi             -
yalniz title                   "Görevliye iletilecek not"  title (son care)        -
aria-label etiketi bastiriyor  "Numara"                    aria-label              -

alan                           ek aciklama                                                     kaynak
etiket + yer tutucu            "başlık, yazar ya da ISBN"                                      placeholder
yalniz yer tutucu              (bos)                                                           aciklama yok
etiket + yardim metni          "Kart üzerindeki 8 haneli numara."                              aria-describedby
hata yardimin yerine           "Üye numarası 8 hane olmalı."                                   aria-describedby
hata yardima eklendi           "Kart üzerindeki 8 haneli numara. Üye numarası 8 hane olmalı."  aria-describedby
hata bagi kopuk                "Kart üzerindeki 8 haneli numara."                              aria-describedby
tarih, etiket + yardim         "En çok 21 gün sonrası seçilebilir."                            aria-describedby
yalniz title                   (bos)                                                           aciklama yok
aria-label etiketi bastiriyor  (bos)                                                           aciklama yok

kural denetimi:
  yalniz yer tutucu              ad son care kaynaktan geliyor, kalici etiket yok
  hata yardimin yerine           hata bildirimi yardim metnini bastirdi
  hata bagi kopuk                kopuk basvuru: uye-hata-2
  hata bagi kopuk                gecersiz alan, hata iletisi aciklamaya girmiyor
  yalniz title                   ad son care kaynaktan geliyor, kalici etiket yok
  aria-label etiketi bastiriyor  gorunen etiket ("Üye numarası") erisilebilir adin icinde yok

9 alanda 6 bulgu
```

#### Bulguların Okunması

**Yer tutucu ada dönüşür ama ada dönüşmesi yetmez.** İkinci satırda arama alanının adı
"başlık, yazar ya da ISBN" oluyor. Hesap bunu kabul ediyor, otomatik denetleyici de bir
varlık kuralı ihlali görmüyor. Kusur başka yerdedir: kullanıcı alana yazmaya başladığında
ad ekrandan silinir ve alanın ne istediği görünmez kalır. Birinci satır doğru düzeni
gösteriyor — etiket adı, yer tutucu ek açıklamayı taşıyor ve ikisi de kendi işini yapıyor.

**Hata iletisi yardım metnini bastırabilir.** Dördüncü satırda `aria-describedby` değeri
yardım metninin kimliğiyle **değiştirilmiş**. Alanın açıklaması artık yalnızca hata iletisi:
kullanıcı hatayı duyuyor ama "8 haneli" bilgisini kaybediyor. Beşinci satır doğru işlemi
gösteriyor — kimlik listesine eklenir, listede iki kimlik birden durur ve açıklama iki
metnin birleşimi olur. Bu, `aria-describedby` özniteliğinin **boşlukla ayrılmış bir kimlik
listesi** olmasının doğrudan sonucudur.

**Kopuk başvuru sessizce düşer.** Altıncı satırda hata düğümünün kimliği yanlış yazılmış.
Hesap o kimliği atlıyor ve açıklama yalnızca yardım metninden geliyor: alan geçersiz olarak
bildiriliyor, ama nedeni hiçbir yerde duyurulmuyor. Bu, denetimin en değerli bulgusudur,
çünkü ekranda hata iletisi görünür durumdadır ve gözle bakan biri kusuru göremez.

**Ad bildirimi görünen etiketi bastırır.** Son satırda alanın `label` ögesi "Üye numarası"
diyor, `aria-label` bildirimi "Numara". 2.5.3 Addaki Etiket ölçütü, görünen etiket metninin
erişilebilir adın içinde geçmesini ister; burada geçmiyor.

#### Klavye Sözleşmesi

| Tuş | Davranış |
|---|---|
| `Tab` | Odağı alana getirir; alan tek bir sekme durağıdır. |
| Yazı tuşları | Değeri değiştirir. |
| `Home` / `End` | İmleci satırın başına ve sonuna taşır. |
| Ok tuşları | İmleci metin içinde hareket ettirir. |
| `Enter` | Tek metin alanı bulunan bir formda örtük gönderim yapar. |

Sözleşmenin asıl kısıtı, alanın **hangi tuşları yakalamayacağıdır**. Ok tuşları, `Home`,
`End` ve `Backspace` metin düzenlemeye aittir; alanı içeren bir bileşen bu tuşları kendi
gezinmesi için kullanamaz. Bu kısıt açılır liste ve menü kalıplarında bağlayıcıdır: arama
alanına bağlı bir öneri listesi ok tuşlarını kullanacaksa, imlecin metin içinde hareket
etmesi gereken durumlarda tuşu alana bırakmak zorundadır.

Ayrıca alanın odağını program yoluyla ele geçirmek — sayfa yüklenince arama alanına
odaklanmak — klavye kullanıcısını sayfanın başındaki gezinmeden koparır. Odak, kullanıcının
başlattığı bir eylemden sonra taşınır.

#### Ölçülebilir Kısıtlar

**3.3.2 Etiketler veya Yönergeler**, kullanıcıdan girdi isteyen her alanda etiketin ya da
yönergenin sağlanmasını ister. Yer tutucunun etiket yerine geçmemesinin ölçüt karşılığı budur.

**1.4.3 Kontrast (En Az)** yer tutucu metnini de kapsar: yer tutucu, alan yüzeyiyle en az 4.5:1
kontrast oranı taşımalıdır. Yer tutucunun soluklaştırılması yaygın bir tasarım tercihidir ve
ölçütü doğrudan kaldırır.

**1.4.11 Metin Dışı Kontrast**, alanın kenarlığını kapsar: alanın nerede başlayıp
bittiğini gösteren sınır, sayfa yüzeyinden en az 3:1 ile ayrılmalıdır. Yalnızca alt
çizgiyle kurulan alanlarda bu ölçü tek kenar üzerinden hesaplanır.

**1.4.4 Metnin Yeniden Boyutlandırılması**, metnin yüzde iki yüze kadar büyütülebilmesini ister;
sabit yükseklikli bir alan kutusu bu ölçekte metni keser.

**3.3.1 Hata Tanımlama**, hatanın metinle bildirilmesini ister. Kırmızı kenarlık tek başına bir
bildirim değildir; `aria-invalid` durumu ve bağlı hata iletisi bildirimin kendisidir.

#### Sık Yapılan Hata

**Etiketin görsel olarak kaldırılıp yer tutucuya taşınması.** Saptaması, denetimin ikinci
satırıdır: adın kaynağı "son çare" olarak işaretlenen her alan bu durumdadır. Etiket
gerçekten görünmemeliyse — sonuç tablosunun üstündeki tek başına duran arama alanı gibi —
ögenin kendisi belgede kalır ve yalnızca ekrandan gizlenir; ağaçtan silinmez.

**Hata iletisinin yalnızca ekranda gösterilmesi.** Alanın altına eklenen bir metin düğümü,
`aria-describedby` bağı kurulmadıkça ağaçta alana bağlı değildir. Saptaması, geçersiz olarak
işaretlenen her alanın açıklamasının hata metnini içerip içermediğinin denetlenmesidir;
denetimin son iki kuralı bunu yapar.

**Zorunluluğun yalnızca yıldız işaretiyle bildirilmesi.** Etiketin yanındaki yıldız görsel
bir imdir; ağaçta zorunluluk `required` özniteliğinden gelir. Yıldızın ne anlama geldiği
ayrıca yazılmalı, öznitelik ise her koşulda bulunmalıdır.

#### Özet

- Metin girdisi, değerin önceden sayılamadığı durumlarda kullanılır; sayılabilen değerler
  için seçim denetimi ya da açılır liste seçilir.
- `type` özniteliği rolü, yerleşik doğrulamayı ve dokunmatik tuş takımını birlikte
  belirler; `autocomplete` alanın girdi amacını program yoluyla bildirir.
- Etiket ada, yardım metni ve hata iletisi ek açıklamaya gider; `aria-describedby` bir
  kimlik listesidir ve hata iletisi yardım metninin yerine değil yanına eklenir.
- Yer tutucu ad hesabının son sırasındadır; ada dönüşmesi onu etiket yapmaz, çünkü alan
  doldurulduğunda ekrandan silinir.
- Kopuk bir kimlik başvurusu sessizce düşer ve alan gerekçesiz biçimde geçersiz görünür;
  denetim bunu ekranda görünen hata iletisine rağmen yakalar.
- Ok tuşları, `Home` ve `End` metin düzenlemeye aittir; alanı içeren bileşen bu tuşları
  kendi gezinmesi için kullanamaz.

#### Sonraki Adım

Metin girdisi tek bir değer taşır ve o değerin adı alanın adıdır. Ödünç alma formundaki
"Bildirim yöntemi" seçenekleri ise üç ayrı denetimden oluşur ve üçünün ortak bir sorusu
vardır: "Bildirim nasıl gönderilsin?" Bu soru hiçbir denetimin adı değildir, **grubun**
adıdır. Sonraki ders onay kutusu, radyo düğmesi ve anahtarı ele alır: grubun nasıl
adlandırıldığını, üç denetimin hangi durum bildirimini taşıdığını ve neden yalnızca birinin
klavye sözleşmesinde ok tuşları bulunduğunu belirtir.

### Seçim Denetimleri

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/secim-denetimleri

Onay kutusu, seçenek düğmesi ve anahtarın çözdüğü ayrı sorunlar; grubun adlandırılması, karma durumun bildirimi ve iki farklı klavye sözleşmesinin sınanması.

Metin girdisinde adın kaynağı alanın kendi etiketiydi. Ödünç alma formundaki "Bildirim
yöntemi" bölümü ise üç ayrı denetimden oluşur — "E-posta", "SMS", "Bildirim yok" — ve
kullanıcının yanıtladığı soru bu üç adın hiçbirinde yazmaz. Soru grubun kendisine aittir.

Bu ders üç seçim denetimini belirtir: onay kutusu, seçenek düğmesi ve anahtar. Üçünün
görünümü birbirine yakındır, çözdükleri sorunlar ve klavye sözleşmeleri ise ayrıdır. Ayrımın
yanlış kurulması, arayüzün klavyeyle çalışmayı sürdürdüğü ama beklenenden farklı davrandığı
bir sonuç üretir.

#### Üç Denetim, Üç Ayrı Sorun

**Onay kutusu** bağımsız bir açık/kapalı seçimdir. Katalogun süzgeç panelinde "Yalnız rafta
olanlar", "Yalnız e-kitaplar" ve "Süreli yayınlar" birbirinden bağımsızdır; hiçbiri diğerini
kapatmaz. Sıfır tanesi seçili olabilir ya da hepsi seçili olabilir.

**Seçenek düğmesi** birbirini dışlayan bir seçimdir ve her zaman bir grup içinde bulunur.
Ödünç süresi 14, 21 ya da 28 gündür; biri seçildiğinde diğeri kapanır. Tek başına bir
seçenek düğmesi anlamsızdır — kullanıcı onu seçtikten sonra seçimden vazgeçemez.

**Anahtar** açık/kapalı bir **ayardır** ve değiştirildiği anda etkili olur. Kullanıcı hesabındaki
"İade hatırlatması gönder" ayarı böyledir: değiştirilir, kaydedilir ve sayfada bir gönderim
düğmesi beklemez.

Ayrım buradan çıkar: bir gönderim düğmesiyle onaylanacak bir form içinde anahtar kullanılmaz; onay
kutusu kullanılır. Anahtarın verdiği söz "şimdi etkili oldu"dur; formun gönderilmesini bekleyen
bir anahtar bu sözü tutmaz.

#### Yerel Öge Önce

Onay kutusunun ve seçenek düğmesinin yerel karşılıkları vardır: `input` ögesinin `checkbox`
ve `radio` türleri. İkisi de rolü, işaretlilik durumunu, klavye sözleşmesini ve gönderim
listesine katılmayı birlikte getirir. Seçenek düğmelerini bir gruba bağlayan şey `name`
özniteliğinin aynı olmasıdır; grup, işaretlemedeki komşuluktan değil bu addan doğar.

Anahtarın yerel karşılığı **yoktur**. Karşılanma yolu, `role="switch"` bildirimi taşıyan bir
düğmedir; durumu `aria-checked` ile yazılır. Onay kutusuna `role="switch"` yazmak da
tanımlıdır ve o durumda ögenin yerel işaretlilik durumu ağaca anahtar durumu olarak geçer.

#### Grubun Adı

Grubun bir adı olmadığında kullanıcı üç seçeneği duyar, sorusunu duymaz. Yerel karşılık
`fieldset` ve `legend` ögeleridir: `fieldset` ögesi ağaçta grup rolü üretir, `legend`
ögesinin metni grubun adı olur.

```html
<fieldset>
  <legend>Bildirim yöntemi</legend>
  <p><label><input type="radio" name="bildirim" value="eposta" checked> E-posta</label></p>
  <p><label><input type="radio" name="bildirim" value="sms"> SMS</label></p>
  <p><label><input type="radio" name="bildirim" value="yok"> Bildirim yok</label></p>
</fieldset>

<fieldset>
  <legend>Süzgeçler</legend>
  <p><label><input type="checkbox" name="raf" value="rafta"> Yalnız rafta olanlar</label></p>
  <p><label><input type="checkbox" name="tur" value="e-kitap"> Yalnız e-kitaplar</label></p>
</fieldset>
```

İki ayrıntı bağlayıcıdır. Birincisi, her denetimin kendi etiketinin `label` ögesiyle
kurulmasıdır; etiketin denetimi içine alması (örtük bağ) `for` ve `id` çifti yazmadan aynı
sonucu verir ve etiket metnini de tıklanabilir alana katar. İkincisi, seçenek düğmelerinin
`name` değerinin aynı olmasıdır; farklı yazıldığında üç ayrı tek seçenekli grup oluşur ve
kullanıcı üçünü birden seçebilir.

Grup ögesi kullanılamadığında karşılık `role="group"` ya da seçenek düğmeleri için
`role="radiogroup"` bildirimidir; grubun adı `aria-labelledby` ile başlık ögesine bağlanır.
Bu yol yalnız `fieldset` ögesinin düzen kısıtları sorun çıkardığında seçilir.

#### Rol, Ad ve Durum

Üç denetimin de adı kendi etiketinden gelir; grubun adı, denetimin adının yerine geçmez,
onun bağlamı olur.

Durum bildirimi işaretliliktir. Yerel ögelerde `checked` özelliğinden ağaca geçer; elle
kurulan bir denetimde `aria-checked` değeriyle yazılır ve her değişimde güncellenir.

Onay kutusunun üçüncü bir durumu vardır: **karma durum** (`mixed`). Süzgeç panelinde
"Hepsini seç" kutusu, alt kutuların bir bölümü seçiliyken bu durumdadır. Yerel ögede
karma durum bir öznitelikle değil, `indeterminate` özelliğiyle kurulur ve ağaca karma
olarak bildirilir. Karma durum bir üçüncü seçenek değildir: kullanıcı ona tıkladığında
denetim ya tümünü seçer ya tümünü bırakır, karmaya geri dönmez.

Anahtarda karma durum yoktur. Ayrıca anahtarın durumu **metinle de** bildirilir mi sorusu
tasarım kararıdır: "Açık"/"Kapalı" metni denetimin adının parçası değildir, çünkü ad
değişmez; durum yalnızca `aria-checked` üzerinden bildirilir ve metin bir ek gösterge olur.

#### İki Klavye Sözleşmesi

Seçenek grubu:

| Tuş | Davranış |
|---|---|
| `Tab` | Odağı gruba getirir; grubun tamamı **tek** sekme durağıdır ve odak seçili düğmeye gelir. |
| Ok tuşları | Odağı ve seçimi birlikte taşır; uçlarda başa döner. |
| `Boşluk` | Odaktaki düğmeyi seçer. |
| `Tab` (grup içindeyken) | Grubun tamamından çıkar, sonraki durağa geçer. |

Onay kutusu ve anahtar:

| Tuş | Davranış |
|---|---|
| `Tab` | Her denetim ayrı bir sekme durağıdır. |
| `Boşluk` | Durumu değiştirir. |
| Ok tuşları | Etkisizdir; odak ve seçim değişmez. |

İki sözleşmenin farkı, seçenek grubunun **dönen sekme durağı** düzenini kullanmasıdır: grupta
yalnızca bir öge sekme sırasındadır ve kalanlarına ok tuşlarıyla ulaşılır. On beş seçenekli bir
grup böylece on beş değil bir durak tutar. Onay kutuları bağımsız oldukları için bu düzeni
kullanamaz; kullanıcı her birine ayrı ayrı ulaşmak zorundadır.

Aşağıdaki sınama iki sözleşmeyi durum makinesi olarak kurar ve tuş dizileriyle denetler.

```js
// secim.test.mjs — secenek grubu ile onay kutusu grubunun klavye sozlesmesi
// Kosum: node --test secim.test.mjs
import { test } from "node:test";
import assert from "node:assert/strict";

const OGELER = ["E-posta", "SMS", "Bildirim yok"];
const ILERI = new Set(["ArrowDown", "ArrowRight"]);
const GERI = new Set(["ArrowUp", "ArrowLeft"]);

// odak: null ise odak grubun disinda
const secenek = (secili = null) => ({ tur: "secenek", ogeler: OGELER, secili, odak: null });
const onay = () => ({ tur: "onay", ogeler: OGELER, secili: new Set(), odak: null });

// Grubun kapladigi sekme duragi sayisi
function sekmeDuragi(d) {
  return d.tur === "secenek" ? 1 : d.ogeler.length;
}

function tus(d, t) {
  const n = d.ogeler.length;
  if (d.tur === "secenek") {
    if (d.odak === null) return t === "Tab" ? { ...d, odak: d.secili ?? 0 } : d;
    if (t === "Tab") return { ...d, odak: null };
    if (ILERI.has(t)) { const i = (d.odak + 1) % n; return { ...d, odak: i, secili: i }; }
    if (GERI.has(t)) { const i = (d.odak + n - 1) % n; return { ...d, odak: i, secili: i }; }
    if (t === "Space") return { ...d, secili: d.odak };
    return d;
  }
  if (d.odak === null) return t === "Tab" ? { ...d, odak: 0 } : d;
  if (t === "Tab") return d.odak === n - 1 ? { ...d, odak: null } : { ...d, odak: d.odak + 1 };
  if (t === "Space") {
    const s = new Set(d.secili);
    if (s.has(d.odak)) s.delete(d.odak); else s.add(d.odak);
    return { ...d, secili: s };
  }
  return d;   // ok tuslari onay kutusu grubunu etkilemez
}

const uygula = (d, dizi) => dizi.reduce(tus, d);

test("secenek grubu tek sekme duragi, onay kutulari ogesi kadar durak tutar", () => {
  assert.equal(sekmeDuragi(secenek()), 1);
  assert.equal(sekmeDuragi(onay()), 3);
});

test("gruba giriste odak secili ogeye gelir", () => {
  assert.equal(uygula(secenek(2), ["Tab"]).odak, 2);
});

test("hic secim yokken giriste odak ilk ogede olur ve secim yapilmaz", () => {
  const d = uygula(secenek(), ["Tab"]);
  assert.equal(d.odak, 0);
  assert.equal(d.secili, null);
});

test("ok tusu odagi ve secimi birlikte tasir, sonda basa doner", () => {
  const d = uygula(secenek(0), ["Tab", "ArrowDown", "ArrowDown", "ArrowDown"]);
  assert.equal(d.odak, 0);
  assert.equal(d.secili, 0);
  const g = uygula(secenek(0), ["Tab", "ArrowUp"]);
  assert.equal(g.secili, 2);
});

test("Tab grubun tamamindan tek basista cikar", () => {
  const d = uygula(secenek(1), ["Tab", "ArrowDown", "Tab"]);
  assert.equal(d.odak, null);
  assert.equal(d.secili, 2);
});

test("onay kutusunda ok tusu hicbir sey yapmaz", () => {
  const d = uygula(onay(), ["Tab", "ArrowDown", "ArrowRight"]);
  assert.equal(d.odak, 0);
  assert.equal(d.secili.size, 0);
});

test("onay kutulari birbirinden bagimsiz acilip kapanir", () => {
  const d = uygula(onay(), ["Tab", "Space", "Tab", "Tab", "Space"]);
  assert.deepEqual([...d.secili].sort(), [0, 2]);
  const k = uygula(d, ["Space"]);
  assert.deepEqual([...k.secili], [0]);
});

test("bosluk tusu secenek grubunda odaktaki ogeyi secer", () => {
  const d = uygula(secenek(0), ["Tab", "Space"]);
  assert.equal(d.secili, 0);
});
```

```
✔ secenek grubu tek sekme duragi, onay kutulari ogesi kadar durak tutar (0.361166ms)
✔ gruba giriste odak secili ogeye gelir (0.08225ms)
✔ hic secim yokken giriste odak ilk ogede olur ve secim yapilmaz (0.045375ms)
✔ ok tusu odagi ve secimi birlikte tasir, sonda basa doner (0.04925ms)
✔ Tab grubun tamamindan tek basista cikar (0.339708ms)
✔ onay kutusunda ok tusu hicbir sey yapmaz (0.072458ms)
✔ onay kutulari birbirinden bagimsiz acilip kapanir (0.360833ms)
✔ bosluk tusu secenek grubunda odaktaki ogeyi secer (0.046833ms)
ℹ tests 8
ℹ suites 0
ℹ pass 8
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 35.587
```

Parantez içindeki süreler ve toplam süre koşumdan koşuma değişir; sınamanın sonucu
değişmez.

Üçüncü sınama sözleşmenin en çok atlanan yerini yakalıyor: hiçbir seçenek işaretli
değilken gruba girildiğinde odak ilk ögeye gelir ama **seçim yapılmaz**. Odağın seçimi
sürüklediği kural yalnızca ok tuşları için geçerlidir; giriş anı bunun dışındadır. Elle
yazılan gruplarda bu ayrım gözden kaçtığında, kullanıcı sekme tuşuna bastığı için istemediği
bir ödünç süresini seçmiş olur.

Beşinci sınama, dönen sekme durağının doğrudan sonucudur: grup içinde ok tuşuyla iki adım
gidilmiş olsa da `Tab` tek basışta gruptan çıkar.

#### Ölçülebilir Kısıtlar

**Hedef boyutu.** Onay kutusunun görsel kutusu genelde 16 ile 20 piksel arasındadır ve tek
başına 2.5.8 ölçütünü karşılamaz. Etiket metninin denetimle birlikte tıklanabilir olması —
`label` ögesi bunu kendiliğinden sağlar — hedefi metnin genişliğine kadar büyütür. Süzgeç
listesindeki kutuların dikey aralığı da 2.5.8'in aralık istisnasını sağlayacak kadar
açılmalıdır.

**1.4.11 Metin Dışı Kontrast.** Kutunun ve düğmenin sınırı, sayfa yüzeyinden en az 3:1 ile
ayrılmalıdır. Aynı ölçü işaretlilik göstergesi için de geçerlidir: işaret, kutunun içindeki
dolgudan ayırt edilebilmelidir.

**1.4.1 Rengin Tek Taşıyıcı Olmaması.** İşaretli durum yalnızca dolgu rengiyle
bildirilemez; bir onay imi, bir nokta ya da anahtarın kaydırılmış konumu ikinci kanaldır.

**3.2.2 Girdide Bağlam Değişikliği.** Bir onay kutusunun değiştirilmesi formu kendiliğinden
göndermemeli ya da sayfayı değiştirmemelidir. Süzgeç panelinde bir kutu işaretlendiğinde
sonuç tablosunun tazelenmesi kabul edilebilir bir davranıştır, çünkü bağlam değişmez; sayfa
adresinin değişmesi ve odağın kaybolması kabul edilemez.

#### Sık Yapılan Hata

**Onay kutusunun birbirini dışlayan seçim için kullanılması.** Saptaması davranıştandır:
kullanıcı ikinci kutuyu işaretlediğinde birincisi kendiliğinden kapanıyorsa, bileşen yanlış
seçilmiştir. Kullanıcı için bu, kapanma nedeninin hiçbir yerde bildirilmemesi anlamına gelir.

**Grubun adsız bırakılması.** Saptaması bir varlık kuralıyla yapılır: seçenek düğmesi
taşıyan her grubun bir adı olmalıdır. Ekranda grubun üstünde duran bir başlık metni, bir
bağ kurulmadıkça ad değildir.

**Anahtarın gönderim düğmesi bekleyen bir forma konması.** Saptaması, anahtarın
değiştirilmesinden sonra bir kaydetme adımı gerekip gerekmediğinin sorulmasıdır. Gerekiyorsa
bileşen onay kutusuna çevrilir.

**Karma durumun `false` olarak bildirilmesi.** "Hepsini seç" kutusu, alt kutuların bir
bölümü seçiliyken kapalı görünüyorsa kullanıcıya yanlış bilgi verilir. Saptaması, alt
kutuların sayımıyla üst kutunun bildirilen durumunun karşılaştırılmasıdır.

#### Özet

- Onay kutusu bağımsız açık/kapalı seçim, seçenek düğmesi birbirini dışlayan seçim, anahtar
  ise değiştirildiği anda etkili olan bir ayardır; gönderim bekleyen formda anahtar
  kullanılmaz.
- Onay kutusunun ve seçenek düğmesinin yerel karşılığı vardır; anahtarın yoktur ve
  `role="switch"` ile kurulur. Seçenek grubunu `name` özniteliğinin aynılığı oluşturur.
- Grubun adı `fieldset` ve `legend` ile verilir; grup adı denetimin adının yerine geçmez,
  bağlamı olur.
- Seçenek grubu dönen sekme durağı kullanır: grup tek durak tutar, ok tuşları odağı ve
  seçimi birlikte taşır; onay kutuları ise ayrı duraklardır ve ok tuşlarına yanıt vermez.
- Gruba ilk girişte hiçbir seçenek seçili değilse odak ilk ögeye gelir ama seçim yapılmaz;
  bu, elle yazılan gruplarda en çok atlanan kuraldır.
- Karma durum bir üçüncü seçenek değil, alt seçimlerin özetidir; kullanıcı tıkladığında ya
  tümü seçilir ya tümü bırakılır.

#### Sonraki Adım

Seçenek grubu, seçenekler sayılabilir ve az sayıda olduğunda çalışır. Katalogun süzgeç
panelinde "Konu" alanı ise iki yüz elli değer taşır ve hepsi ekranda gösterilemez. Bu,
seçim işini kapalı bir listeye devretmeyi gerektirir: kullanıcı listeyi açar, gezinir,
yazarak daraltır ve bir değer seçer. Sonraki ders bu bileşeni iki ayrı kalıp olarak
belirtir — yerel seçim ögesi ile elle kurulan birleşik giriş alanı — ve ikincisinin neden
en çok bildirim gerektiren kalıp olduğunu gösterir.

### Açılır Listeler

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/acilir-listeler

Yerel seçim ögesiyle elle kurulan birleşik giriş alanının karşılaştırılması; etkin seçenek bildiriminin odaktan ayrılması, klavye sözleşmesinin sınanması ve ulaşılan durumlarda değişmezlerin denetimi.

Seçenek grubu, seçenekler ekrana sığdığında çalışır. Katalogun süzgeç panelindeki "Konu"
alanı iki yüz elliyi aşkın değer taşır; hepsi ekranda gösterilemez ve gösterilse de
gezilemez. Bu, seçim işini kapanabilen bir listeye devretmeyi gerektirir.

Kalıbın iki gerçekleştirimi vardır ve aralarındaki fark tek bir satırla özetlenemez: biri
tek bir ögeyle gelir, diğeri on ayrı bildirimi elle yazmayı gerektirir. Bu ders ikisini de
belirtir ve ikincisinin bedelini sayar.

#### Kalıbın Çözdüğü Sorun

Açılır liste, **sayılabilir ama kalabalık** bir seçenek kümesinden tek bir değer
seçtirir. İki sınırı vardır. Alt sınırda, beşten az seçenek varsa liste gereksizdir: dört
ödünç süresi seçeneği bir seçenek grubuyla ekranda durur ve kullanıcı hepsini bir bakışta
görür. Üst sınırda, seçenekler kullanıcının önceden bilmediği bir kümeden geliyorsa —
kataloğun tamamındaki yazar adları gibi — liste değil arama gerekir.

Ayrıca liste, birden çok değerin seçileceği yerlerde de doğru bileşen değildir. Çok seçimli
açılır liste kalıbı kullanıcıya seçtiklerinin tamamını aynı anda göstermez; onun yerine onay
kutusu grubu ya da seçilenleri ayrı gösteren bir kalıp kullanılır.

#### Yerel Öge Önce

Karşılık `select` ögesidir. Getirdikleri şunlardır: birleşik giriş alanı rolü, `label`
ögesinden hesaplanan ad, açık/kapalı durumunun bildirimi, seçili seçeneğin değer olarak
bildirimi, `optgroup` ile gruplama, klavye sözleşmesinin tamamı ve yazarak arama.

Bir bedeli vardır ve belirtimde yazılmalıdır: açılan seçenek listesinin görünümü sunum
katmanından denetlenemez. Liste, sayfanın çizim düzleminin dışında oluşturulur; yazı tipi,
renk, satır yüksekliği ve seçenek içine konacak ek bilgi tasarım kararına açık değildir.

Katalogun "Konu" alanında bu bedel ödenebilir. Sonuç sıralama ölçütü, ödünç süresi ve sayfa
büyüklüğü seçicilerinin tamamı yerel ögeyle karşılanır. Elle kurulan bir bileşen yalnızca üç
gereksinimden biri varsa seçilir: seçenek satırında ek bilgi gösterilmesi (kayıt sayısı, alt
başlık), yazarak süzme, ya da seçeneklerin sunucudan aramayla getirilmesi.

#### Özel Bileşenin Bedeli

Elle kurulan bileşende yeniden üretilmesi gereken bildirimler şunlardır: rol
(`role="combobox"`), açık/kapalı durumu (`aria-expanded`), denetlenen listenin kimliği
(`aria-controls`), liste rolü (`role="listbox"`), seçenek rolleri (`role="option"`), etkin
seçeneğin bildirimi (`aria-activedescendant`), seçili seçeneğin bildirimi
(`aria-selected`), otomatik tamamlama biçimi (`aria-autocomplete`), klavye sözleşmesinin
tamamı ve süzme sonucunun duyurulması.

```html
<label for="konu">Konu</label>
<input id="konu" role="combobox" aria-expanded="true" aria-controls="konu-liste"
       aria-activedescendant="konu-2" aria-autocomplete="list" autocomplete="off">
<ul id="konu-liste" role="listbox" aria-label="Konu seçenekleri">
  <li id="konu-1" role="option" aria-selected="false">Mimarlık</li>
  <li id="konu-2" role="option" aria-selected="true">Mikrobiyoloji</li>
</ul>
<p role="status">2 konu listeleniyor</p>
```

Bu işaretlemenin ayırt edici yeri `aria-activedescendant` özniteliğidir. Klavye odağı
**alandan hiç çıkmaz**; listede gezinen şey odak değil, bir bildirimdir. Kullanıcı ok
tuşuna bastığında alandaki imleç yerinde kalır, ağaçta ise başka bir seçenek etkin olarak
duyurulur. Bu ayrım, alanın metin düzenleme davranışının korunmasını sağlar; odak gerçekten
listeye taşınsaydı yazmaya devam etmek olanaksız olurdu.

Ayrımın tasarıma yansıyan sonucu, **iki ayrı göstergenin** gerekmesidir: alanda odak halkası,
listede etkin seçenek vurgusu. İkisinden biri eksikse kullanıcı ya nerede olduğunu ya neyin
seçileceğini göremez.

#### Klavye Sözleşmesi

| Tuş | Davranış |
|---|---|
| `Tab` | Alan tek sekme durağıdır; liste açıkken de odak alandadır. Liste kapanır ve odak dışarı çıkar. |
| `Aşağı ok` | Liste kapalıysa açar ve ilk seçeneği etkinleştirir; açıksa bir sonrakine geçer. |
| `Yukarı ok` | Liste kapalıysa açar ve son seçeneği etkinleştirir; açıksa bir öncekine geçer. |
| `Enter` | Etkin seçeneği değer yapar ve listeyi kapatır. |
| `Escape` | Liste açıksa kapatır ve değeri korur; kapalıysa alanı temizler. |
| Yazı tuşları | Listeyi açar ve seçenekleri süzer. |
| `Home` / `End` | İmleci metin içinde taşır; listeye ait değildir. |

Ok tuşlarının uçlarda **başa dönmemesi**, seçenek grubunun sözleşmesinden ayrıldığı
noktadır. Seçenek grubunda dönmek kullanıcıyı kaybettirmez, çünkü grubun tamamı ekrandadır.
Uzun bir listede dönmek, kullanıcıyı bir tuş vuruşuyla listenin öbür ucuna atar ve nerede
olduğunu okumadan anlamasını olanaksız kılar.

Aşağıdaki sınama sözleşmeyi bir durum makinesi olarak kurar, tuş dizileriyle denetler ve son
sınamada ulaşılabilen bütün durumları tarayarak üç değişmezi doğrular.

```js
// acilir.test.mjs — birlesik giris alaninin klavye sozlesmesi ve degismezleri
// Kosum: node --test acilir.test.mjs
import { test } from "node:test";
import assert from "node:assert/strict";

const KONULAR = ["Mimarlık", "Mikrobiyoloji", "Müzik Tarihi", "Matematik"];
const kucuk = (s) => s.toLocaleLowerCase("tr");

const baslangic = () => ({ odakAlanda: true, acik: false, etkin: null, metin: "", deger: null });
const secenekler = (d) => KONULAR.filter((k) => kucuk(k).startsWith(kucuk(d.metin)));

function tus(d, t) {
  const s = secenekler(d);
  if (t === "ArrowDown") {
    if (!d.acik) return { ...d, acik: true, etkin: s.length ? 0 : null };
    if (d.etkin === null) return { ...d, etkin: s.length ? 0 : null };
    return { ...d, etkin: Math.min(d.etkin + 1, s.length - 1) };   // dongu yok
  }
  if (t === "ArrowUp") {
    if (!d.acik) return { ...d, acik: true, etkin: s.length ? s.length - 1 : null };
    if (d.etkin === null) return { ...d, etkin: s.length ? s.length - 1 : null };
    return { ...d, etkin: Math.max(d.etkin - 1, 0) };
  }
  if (t === "Enter") {
    if (d.acik && d.etkin !== null)
      return { ...d, acik: false, etkin: null, metin: s[d.etkin], deger: s[d.etkin] };
    return d;
  }
  if (t === "Escape") {
    if (d.acik) return { ...d, acik: false, etkin: null };          // listeyi kapat
    return { ...d, metin: "", deger: null };                        // alani temizle
  }
  if (t === "Tab") return { ...d, acik: false, etkin: null, odakAlanda: false };
  if (t.startsWith("yaz:")) return { ...d, metin: d.metin + t.slice(4), acik: true, etkin: null };
  return d;
}

const uygula = (d, dizi) => dizi.reduce(tus, d);

test("kapaliyken asagi ok listeyi acar ve ilk secenegi etkin yapar", () => {
  const d = uygula(baslangic(), ["ArrowDown"]);
  assert.equal(d.acik, true);
  assert.equal(secenekler(d)[d.etkin], "Mimarlık");
});

test("kapaliyken yukari ok listeyi acar ve son secenegi etkin yapar", () => {
  const d = uygula(baslangic(), ["ArrowUp"]);
  assert.equal(secenekler(d)[d.etkin], "Matematik");
});

test("son secenekte asagi ok basa donmez", () => {
  const d = uygula(baslangic(), ["ArrowDown", "ArrowDown", "ArrowDown", "ArrowDown", "ArrowDown"]);
  assert.equal(d.etkin, 3);
  assert.equal(secenekler(d)[d.etkin], "Matematik");
});

test("giris tusu etkin secenegi deger yapar ve listeyi kapatir", () => {
  const d = uygula(baslangic(), ["ArrowDown", "ArrowDown", "Enter"]);
  assert.equal(d.deger, "Mikrobiyoloji");
  assert.equal(d.acik, false);
  assert.equal(d.etkin, null);
});

test("yazmak listeyi acar ve suzer, etkin secenek sifirlanir", () => {
  const d = uygula(baslangic(), ["yaz:M", "yaz:i"]);
  assert.deepEqual(secenekler(d), ["Mimarlık", "Mikrobiyoloji"]);
  assert.equal(d.etkin, null);
});

test("escape once listeyi kapatir, ikinci basista alani temizler", () => {
  const bir = uygula(baslangic(), ["yaz:M", "yaz:i", "ArrowDown", "Escape"]);
  assert.equal(bir.acik, false);
  assert.equal(bir.metin, "Mi");
  const iki = uygula(bir, ["Escape"]);
  assert.equal(iki.metin, "");
  assert.equal(iki.deger, null);
});

test("sekme tusu listeyi kapatir ve odagi alandan cikarir", () => {
  const d = uygula(baslangic(), ["ArrowDown", "Tab"]);
  assert.equal(d.acik, false);
  assert.equal(d.odakAlanda, false);
});

test("eslesme yokken liste acilir ama etkin secenek olmaz", () => {
  const d = uygula(baslangic(), ["yaz:Z"]);
  assert.deepEqual(secenekler(d), []);
  const e = uygula(d, ["ArrowDown"]);
  assert.equal(e.etkin, null);
});

test("ulasilan butun durumlarda degismezler korunur ve odak alanda kalir", () => {
  const TUSLAR = ["ArrowDown", "ArrowUp", "Enter", "Escape", "yaz:M", "yaz:i", "yaz:a"];
  const anahtar = (d) => [d.acik, d.etkin, d.metin, d.deger].join("|");
  const gorulen = new Map([[anahtar(baslangic()), baslangic()]]);
  const kuyruk = [baslangic()];
  while (kuyruk.length) {
    const d = kuyruk.shift();
    for (const t of TUSLAR) {
      const y = tus(d, t);
      if (y.metin.length > 3) continue;               // tarama uzunlugu sinirli
      if (gorulen.has(anahtar(y))) continue;
      gorulen.set(anahtar(y), y);
      kuyruk.push(y);
    }
  }
  for (const d of gorulen.values()) {
    assert.equal(d.odakAlanda, true, "odak alandan cikti");
    if (!d.acik) assert.equal(d.etkin, null, "kapali listede etkin secenek kaldi");
    if (d.etkin !== null) {
      assert.ok(d.acik, "etkin secenek varken liste kapali");
      assert.ok(d.etkin < secenekler(d).length, "etkin secenek listede yok");
    }
  }
  console.log(`  taranan durum sayisi: ${gorulen.size}`);
});
```

```
  taranan durum sayisi: 91
✔ kapaliyken asagi ok listeyi acar ve ilk secenegi etkin yapar (0.406542ms)
✔ kapaliyken yukari ok listeyi acar ve son secenegi etkin yapar (0.0605ms)
✔ son secenekte asagi ok basa donmez (0.065417ms)
✔ giris tusu etkin secenegi deger yapar ve listeyi kapatir (0.327ms)
✔ yazmak listeyi acar ve suzer, etkin secenek sifirlanir (0.311833ms)
✔ escape once listeyi kapatir, ikinci basista alani temizler (0.072417ms)
✔ sekme tusu listeyi kapatir ve odagi alandan cikarir (0.045125ms)
✔ eslesme yokken liste acilir ama etkin secenek olmaz (0.05225ms)
✔ ulasilan butun durumlarda degismezler korunur ve odak alanda kalir (2.111542ms)
ℹ tests 9
ℹ suites 0
ℹ pass 9
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 36.581875
```

Süre değerleri koşumdan koşuma değişir. Taranan durum sayısı ise yazma uzunluğunun üç
karakterle sınırlanmasına bağlıdır; sınır kaldırıldığında durum uzayı sonsuzdur, çünkü
kullanıcı istediği kadar karakter yazabilir.

#### Değişmezlerin Okunması

Son sınama üç kural denetliyor ve üçü de ekranla ağacın ayrışabildiği yerlerdir.

**Kapalı listede etkin seçenek kalamaz.** `aria-activedescendant` değeri temizlenmeden
liste kapatılırsa, ağaçta var olmayan bir seçenek etkin görünür. Bu, kullanıcının listeyi
kapattıktan sonra hâlâ liste içinde olduğunu sanmasına yol açar.

**Etkin seçenek listede bulunmalıdır.** Süzme sonrası liste kısaldığında eski dizin
geçersizleşir. Sınamada bu, `yaz:` tuşlarının etkin seçeneği sıfırlamasıyla önleniyor;
sıfırlanmadığında bildirim var olmayan bir kimliğe işaret eder ve sessizce düşer.

**Odak alandan çıkmaz.** Doksan bir durumun tamamında koşul sağlanıyor. Bu, kalıbın tanımı
gereğidir: odağın listeye taşındığı bir gerçekleştirim `aria-activedescendant` kalıbı değil,
başka bir kalıptır ve o durumda yazarak süzme olanağı kaybedilir.

#### Ölçülebilir Kısıtlar

**4.1.2 Ad, Rol, Değer.** Alanın adı, birleşik giriş alanı rolü, açık/kapalı durumu ve
seçili değeri program yoluyla okunabilmelidir. Elle kurulan bileşende dördü de ayrı ayrı
yazılır.

**2.4.7 Odak Görünürlüğü.** Gerçek odak alandadır; göstergesi alanda bulunmalıdır. Etkin
seçenek vurgusu bu göstergenin yerine geçmez, ona eklenir.

**1.4.11 Metin Dışı Kontrast.** Etkin seçenek vurgusu, komşu satırlardan en az 3:1 ile
ayrılmalıdır. Yalnızca açık gri bir yüzeyle kurulan vurgu bu ölçüyü karşılamaz.

**1.4.13 Üzerine Gelme veya Odaklanmayla Beliren İçerik.** Açılan liste odakla beliren bir
içeriktir ve üç koşulu karşılamalıdır: `Escape` ile kapatılabilmeli, imleç listenin üzerine
götürülebilmeli ve kullanıcı bir eylem yapmadan kaybolmamalıdır.

**Süzme sonucunun duyurulması.** Yazarak süzülen bir listede kaç seçeneğin kaldığı, kibar
öncelikli bir durum bölgesiyle bildirilir. Bildirim her tuş vuruşunda değil, yazma durduktan
sonra gönderilir; aksi durumda duyuru kuyruğu kullanıcının yazdığını duymasını engeller.

#### Sık Yapılan Hata

**Odağın listeye taşınması.** Saptaması, liste açıkken yazmayı denemektir: yazılan karakter
alana gitmiyorsa odak kaymıştır. Bu kalıpta düzeltme, odağı geri taşımak değil,
`aria-activedescendant` bildirimine geçmektir.

**Açık/kapalı durumunun güncellenmemesi.** Liste ekranda görünürken `aria-expanded`
değerinin `false` kalması, ağacın ekranla çelişmesidir. Saptaması bir teklik ve tutarlılık
denetimidir: listenin görünürlüğüyle bildirilen durum karşılaştırılır.

**Yerel ögenin görünüş uğruna bırakılması.** Elle kurulan bileşenin bedeli on bildirimdir
ve her biri ayrı bir hata kaynağıdır. Saptaması belirtim aşamasındadır: seçenek satırında ek
bilgi, yazarak süzme ya da sunucudan getirme gereksinimlerinden hiçbiri yoksa karar yerel
ögedir.

#### Özet

- Açılır liste, sayılabilir ama ekrana sığmayan seçenek kümeleri içindir; beşten az seçenek
  için seçenek grubu, önceden bilinmeyen kümeler için arama kullanılır.
- `select` ögesi rolü, adı, durumu, değeri ve klavye sözleşmesini birlikte getirir; bedeli,
  açılan listenin görünümünün sunum katmanından denetlenememesidir.
- Elle kurulan bileşen on ayrı bildirimi yeniden üretir; yalnızca satır içi ek bilgi,
  yazarak süzme ya da sunucudan getirme gerektiğinde seçilir.
- `aria-activedescendant` kalıbında odak alandan çıkmaz; listede gezinen bildirimdir ve bu
  yüzden iki ayrı görsel gösterge gerekir.
- Ok tuşları uçlarda başa dönmez; dönme davranışı yalnızca tamamı ekranda duran seçenek
  gruplarına aittir.
- Kapalı listede etkin seçenek bildirimi kalamaz ve etkin seçenek her zaman süzülmüş listede
  bulunmalıdır; süzme sonrası bildirimin sıfırlanması bunu sağlar.

#### Sonraki Adım

Buraya kadarki dört bileşen tek başına ele alındı: bir düğme, bir alan, bir grup, bir liste.
Ödünç alma formu ise bunların hepsini bir arada taşır ve yeni sorular çıkarır. Alanlar hangi
ölçüte göre gruplanır? Form üç adıma bölündüğünde adımın değiştiği nasıl bildirilir ve odak
nereye gider? Gönderim başarısız olduğunda kullanıcı yazdıklarını kaybeder mi? Sonraki ders
formu bir bütün olarak belirtir ve tek tek doğru olan bileşenlerin bir arada yanlış
çalışabildiği yerleri gösterir.

### Form Kalıpları

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/form-kaliplari

Alan gruplamanın ölçütü, çok adımlı formda odak ve duyuru yönetimi, kaydetme ile geri alma davranışı ve iki düzenin kural denetimiyle karşılaştırılması.

Önceki dört ders bileşenleri tek tek belirtti. Ödünç alma formu ise bir düğme, üç alan ve bir
seçenek grubu taşır; her biri kendi başına doğru kurulmuş olsa bile formun bütünü çalışmayabilir.
Bir alanın hangi gruba ait olduğu, üç adıma bölünmüş bir formda adımın değiştiğinin nasıl
bildirileceği ve gönderimin geri alınıp alınamayacağı bileşen belirtimlerinde yazmaz.

Bu ders formu bir bütün olarak belirtir. Ölçülen şey, tek tek doğru bileşenlerin bir arada
üretebildiği kusurlardır.

#### Bölünme Ölçütü

Bir form, alan sayısı çok olduğu için bölünmez. Bölünme ölçütü, alanların **birbirinden
bağımsız karar kümeleri** oluşturmasıdır: ödünç alma formunda kayıt seçimi, üye bilgisi ve
süre kararı ayrı kümelerdir; her biri kendi başına doğrulanabilir ve kullanıcı birini
tamamlamadan diğerine geçmez.

Ölçüt sağlanmıyorsa bölmek zarar verir. Beş alanlı bir düzeltme bildirimi formunu üç sayfaya
yaymak, kullanıcının tamamını bir arada görmesini engeller ve her geçişte odak yönetimi
gerektiren bir yük ekler. Kısa formlar tek görünümde kalır.

#### Alan Gruplama

Gruplamanın ölçütü, alanların **ortak bir soruya** yanıt vermesidir. "İade tarihi" ile
"Ödünç süresi" ortak bir soruya yanıt verir; "Üye numarası" ile "Ödünç süresi" vermez.
Ekranda birbirine yakın durmak gruplama gerekçesi değildir.

Ortak sorusu olan alanlar `fieldset` ve `legend` ile sarılır; grup adı sorunun kendisidir.
Yalnız bir alan taşıyan bir grup kurulmaz — o alanın etiketi zaten sorudur ve ek bir grup adı
duyuruyu iki kat uzatır.

```html
<h2 id="adim-2" tabindex="-1">Üye bilgisi</h2>
<p id="adim-durum" role="status">Adım 2 / 3</p>

<form action="/odunc/adim-2" method="post">
  <fieldset>
    <legend>Bildirim yöntemi</legend>
    <p><label><input type="radio" name="bildirim" value="eposta" checked> E-posta</label></p>
    <p><label><input type="radio" name="bildirim" value="yok"> Bildirim yok</label></p>
  </fieldset>

  <p><label for="uye-no">Üye numarası</label>
     <input id="uye-no" name="uye-no" required inputmode="numeric"
            aria-describedby="uye-yardim">
     <span id="uye-yardim">Kart üzerindeki 8 haneli numara.</span></p>

  <p><button type="submit">İleri</button>
     <button type="button" name="geri">Geri</button></p>
</form>
```

Düğmelerin sırası ve türü belirtimin parçasıdır. "İleri" düğmesi formun **ilk gönderim
düğmesidir**; bir metin alanındayken giriş tuşuna basan kullanıcı bu düğmeyi çalıştırır.
"Geri" düğmesi `type="button"` taşır; taşımazsa örtük gönderimde formu geriye götürür ve
kullanıcı bir adım kaybeder.

#### Adım Değişimi Sayfa Değişimi Değildir

Sayfa yenilenerek yapılan bir geçişte odak belgenin başına döner ve kullanıcı yeni belgeye
baştan girer. Sayfa yenilenmeden yapılan bir geçişte hiçbir şey kendiliğinden olmaz: ekranda
içerik değişir, odak eski düğmede kalır, ekranı görmeyen kullanıcı için hiçbir şey olmamış
gibi görünür.

Belirtim üç şey ister: odak yeni adımın başlığına taşınır, geçiş kibar öncelikli bir durum
bölgesiyle duyurulur, adım göstergesi kaçıncı adımda olunduğunu metinle söyler. Başlığın
`tabindex="-1"` taşıması, sekme sırasına girmeden odak alabilmesi içindir.

Aşağıdaki betik aynı senaryoyu iki düzende koşturur ve dört kuralı denetler.

```js
// form.mjs — cok adimli formda odak yonetimi, duyuru ve deger korumasinin denetimi

const ADIMLAR = [
  { ad: "Kayıt onayı", baslik: "adim-1", alanlar: [
    { id: "kayit", etiket: "Kayıt numarası", zorunlu: true,
      kural: (v) => /^K-\d{3}$/.test(v), ileti: "Kayıt numarası K-000 biçiminde olmalı." },
  ] },
  { ad: "Üye bilgisi", baslik: "adim-2", alanlar: [
    { id: "uye-no", etiket: "Üye numarası", zorunlu: true,
      kural: (v) => /^\d{8}$/.test(v), ileti: "Üye numarası 8 hane olmalı." },
    { id: "eposta", etiket: "Bildirim adresi", zorunlu: false,
      kural: (v) => v === "" || v.includes("@"), ileti: "Geçerli bir e-posta adresi yazın." },
  ] },
  { ad: "Süre ve onay", baslik: "adim-3", alanlar: [
    { id: "sure", etiket: "Ödünç süresi", zorunlu: true,
      kural: (v) => ["14", "21", "28"].includes(v), ileti: "Bir ödünç süresi seçin." },
  ] },
];

const baslangic = () => ({
  adim: 0, degerler: {}, hatalar: [], odak: "input#kayit", duyuru: "", bitti: false,
});

function dogrula(adim, degerler) {
  const h = [];
  for (const a of ADIMLAR[adim].alanlar) {
    const v = degerler[a.id] ?? "";
    if (a.zorunlu && v === "") h.push([a.id, a.etiket, "Bu alan zorunludur."]);
    else if (v !== "" && !a.kural(v)) h.push([a.id, a.etiket, a.ileti]);
  }
  return h;
}

// duzen: odagiTasi, gecisiDuyur, degerleriKoru
function makine(duzen) {
  const odak = (hedef, eski) => (duzen.odagiTasi ? hedef : eski);
  const duyur = (m) => (duzen.gecisiDuyur ? m : "");
  return (d, e) => {
    if (e.tur === "yaz") return { ...d, degerler: { ...d.degerler, [e.id]: e.deger } };
    if (e.tur === "geri") {
      if (d.adim === 0) return d;
      const y = d.adim - 1;
      return { ...d, adim: y, hatalar: [],
        degerler: duzen.degerleriKoru ? d.degerler : {},
        odak: odak("h2#" + ADIMLAR[y].baslik, d.odak),
        duyuru: duyur(`Adım ${y + 1} / ${ADIMLAR.length}: ${ADIMLAR[y].ad}`) };
    }
    if (e.tur === "ileri" || e.tur === "gonder") {
      const hatalar = dogrula(d.adim, d.degerler);
      if (hatalar.length > 0) {
        return { ...d, hatalar, odak: odak("div#hata-ozeti", d.odak),
          duyuru: duyur(`${hatalar.length} alan düzeltilmeli`) };
      }
      if (e.tur === "gonder" && d.adim === ADIMLAR.length - 1) {
        return { ...d, hatalar: [], bitti: true, odak: odak("h2#sonuc", d.odak),
          duyuru: duyur("Ödünç alma isteği kaydedildi") };
      }
      const y = Math.min(d.adim + 1, ADIMLAR.length - 1);
      return { ...d, adim: y, hatalar: [],
        odak: odak("h2#" + ADIMLAR[y].baslik, d.odak),
        duyuru: duyur(`Adım ${y + 1} / ${ADIMLAR.length}: ${ADIMLAR[y].ad}`) };
    }
    return d;
  };
}

const SENARYO = [
  { tur: "yaz", id: "kayit", deger: "K-118" },
  { tur: "ileri" },
  { tur: "yaz", id: "uye-no", deger: "1234" },
  { tur: "ileri" },
  { tur: "yaz", id: "uye-no", deger: "10045512" },
  { tur: "ileri" },
  { tur: "geri" },
  { tur: "ileri" },
  { tur: "yaz", id: "sure", deger: "21" },
  { tur: "gonder" },
];

const ODAKLANABILIR = new Set([
  "input#kayit", "div#hata-ozeti", "h2#adim-1", "h2#adim-2", "h2#adim-3", "h2#sonuc",
]);

function kosum(baslik, duzen, izYaz) {
  const sonraki = makine(duzen);
  let d = baslangic();
  const iz = [];
  console.log(baslik);
  if (izYaz) console.log("  eylem".padEnd(24) + "adim".padEnd(18) + "odak".padEnd(18) + "duyuru");
  for (const e of SENARYO) {
    const onceki = d;
    d = sonraki(d, e);
    iz.push({ e, onceki, d });
    if (!izYaz) continue;
    const etiket = e.tur === "yaz" ? `yaz ${e.id}` : e.tur;
    const adimAdi = d.bitti ? "sonuc" : `${d.adim + 1}/${ADIMLAR.length} ${ADIMLAR[d.adim].ad}`;
    console.log("  " + etiket.padEnd(22) + adimAdi.padEnd(18) + d.odak.padEnd(18) +
      (d.duyuru || "-"));
  }

  const bulgular = [];
  for (const { e, onceki, d: s } of iz) {
    if (e.tur === "yaz") continue;
    if (s.odak === onceki.odak && s.adim !== onceki.adim)
      bulgular.push(`${e.tur}: adim degisti, odak tasinmadi`);
    if (s.hatalar.length > 0 && s.odak !== "div#hata-ozeti")
      bulgular.push(`${e.tur}: hata var, odak ozete tasinmadi`);
    if (!ODAKLANABILIR.has(s.odak))
      bulgular.push(`${e.tur}: odak hedefi odaklanabilir degil (${s.odak})`);
    if (s.duyuru === "") bulgular.push(`${e.tur}: gecis duyurulmadi`);
    for (const k of Object.keys(onceki.degerler)) {
      if (onceki.degerler[k] !== s.degerler[k])
        bulgular.push(`${e.tur}: ${k} alaninin degeri kayboldu`);
    }
  }
  console.log(`  bulgu: ${bulgular.length}`);
  for (const b of bulgular) console.log("    " + b);
  return iz;
}

kosum("kusurlu duzen (odak tasinmiyor, gecis duyurulmuyor, geri gidiste degerler siliniyor):",
  { odagiTasi: false, gecisiDuyur: false, degerleriKoru: false }, false);

console.log("");
const iz = kosum("duzeltilmis duzen:",
  { odagiTasi: true, gecisiDuyur: true, degerleriKoru: true }, true);

const hatali = iz.find(({ d }) => d.hatalar.length > 0).d;
console.log("\nhatali gecisteki ozet baglantilari:");
for (const [id, etiket, ileti] of hatali.hatalar) {
  console.log(`  <a href="#${id}">${etiket}: ${ileti}</a>`);
}
```

```
kusurlu duzen (odak tasinmiyor, gecis duyurulmuyor, geri gidiste degerler siliniyor):
  bulgu: 14
    ileri: adim degisti, odak tasinmadi
    ileri: gecis duyurulmadi
    ileri: hata var, odak ozete tasinmadi
    ileri: gecis duyurulmadi
    ileri: adim degisti, odak tasinmadi
    ileri: gecis duyurulmadi
    geri: adim degisti, odak tasinmadi
    geri: gecis duyurulmadi
    geri: kayit alaninin degeri kayboldu
    geri: uye-no alaninin degeri kayboldu
    ileri: hata var, odak ozete tasinmadi
    ileri: gecis duyurulmadi
    gonder: hata var, odak ozete tasinmadi
    gonder: gecis duyurulmadi

duzeltilmis duzen:
  eylem                 adim              odak              duyuru
  yaz kayit             1/3 Kayıt onayı   input#kayit       -
  ileri                 2/3 Üye bilgisi   h2#adim-2         Adım 2 / 3: Üye bilgisi
  yaz uye-no            2/3 Üye bilgisi   h2#adim-2         Adım 2 / 3: Üye bilgisi
  ileri                 2/3 Üye bilgisi   div#hata-ozeti    1 alan düzeltilmeli
  yaz uye-no            2/3 Üye bilgisi   div#hata-ozeti    1 alan düzeltilmeli
  ileri                 3/3 Süre ve onay  h2#adim-3         Adım 3 / 3: Süre ve onay
  geri                  2/3 Üye bilgisi   h2#adim-2         Adım 2 / 3: Üye bilgisi
  ileri                 3/3 Süre ve onay  h2#adim-3         Adım 3 / 3: Süre ve onay
  yaz sure              3/3 Süre ve onay  h2#adim-3         Adım 3 / 3: Süre ve onay
  gonder                sonuc             h2#sonuc          Ödünç alma isteği kaydedildi
  bulgu: 0

hatali gecisteki ozet baglantilari:
  <a href="#uye-no">Üye numarası: Üye numarası 8 hane olmalı.</a>
```

Kusurlu düzen on dört bulgu üretiyor ve bunların ikisi zincirleme kusurdur: geri gidişte
değerler silindiği için sonraki iki geçiş de doğrulamadan geçemiyor. Ekranda görülen tek şey
"formun geri dönünce boşalması"dır; kalan on iki bulgu ekranda hiç görünmez.

Düzeltilmiş koşumun izinde iki satır dikkat gerektirir. Dördüncü satırda doğrulama
başarısız oluyor ve odak hata özetine gidiyor; adım numarası değişmiyor. Yedinci satırda
geri gidiş de bir adım değişimidir ve ileri gidişle **aynı** odak ve duyuru işlemini
gerektirir; geri gidişin sessiz bırakılması sık atlanan bir durumdur.

#### Klavye Sözleşmesi

| Tuş | Davranış |
|---|---|
| `Tab` / `Shift+Tab` | Alanlar arasında kaynak sırasıyla gezer. |
| `Enter` (tek satırlık alanda) | Formun ilk gönderim düğmesini çalıştırır. |
| `Enter` (çok satırlı alanda) | Satır sonu ekler; form göndermez. |
| `Enter` (düğmede) | Odaktaki düğmeyi çalıştırır. |

Sözleşmenin kısıtı, örtük gönderimin **hangi düğmeyi** çalıştıracağıdır. Formun ilk gönderim
düğmesi "Geri" ise, alanda giriş tuşuna basan kullanıcı bir adım geriye düşer. Gönderim
niteliği taşımayan her düğme `type="button"` yazılır.

#### Kaydetme ve Geri Alma

Ödünç alma isteği bir kayıt üretir; kullanıcının kendi verisi üzerinde bağlayıcı bir işlemdir.
3.3.4 Hata Önleme ölçütü bu tür gönderimlerde üç yoldan **en az birini** ister: gönderim geri
alınabilir olmalı, girilen veri denetlenip düzeltilebilmeli, ya da gönderim öncesinde
onaylanmalıdır.

Üç adımlı formda ikinci yol kendiliğinden kurulur: son adım, girilen bütün değerleri özet
olarak gösterir ve her satırın yanında ilgili adıma dönen bir bağlantı taşır. Özetin salt
metin olması yetmez; kullanıcı düzeltmek istediği alana bir eylemle ulaşabilmelidir.

İki ek kural belirtimde yazılır. Kaydedilmemiş değişiklik varken sayfadan ayrılmak uyarıyla
karşılanır; uyarı bir kip pencere olduğunda odak yönetimi gerektirir ve bu, sonraki konunun
sorumluluğudur. Oturum zaman aşımı varsa 2.2.1 Zamanlamanın Ayarlanabilirliği devreye girer:
kullanıcı zaman sınırını kapatabilmeli, ayarlayabilmeli ya da uzatabilmelidir. Verinin yeniden
kimlik doğrulamadan sonra korunması ayrı bir ölçütün (2.2.5) konusudur. Yirmi dakikada dolan bir
oturumun uzun bir formu sessizce silmesi bu ölçütü kaldırır.

#### Ölçülebilir Kısıtlar

**3.3.2 Etiketler ve Yönergeler** her alanda etiket ya da yönerge ister; **3.3.1 Hata
Belirleme** hatalı alanın metinle belirtilmesini; **3.3.3 Hata Önerisi** biliniyorsa
düzeltme önerisinin verilmesini ister. Denetimdeki "Üye numarası 8 hane olmalı" iletisi bu
üçüncü ölçütün karşılığıdır: hatayı bildirmekle kalmaz, doğru biçimi söyler.

**1.3.5 Girdi Amacını Belirleme**, kullanıcının kendi bilgisini toplayan alanlarda amacın
program yoluyla bildirilmesini ister; ödünç alma formunda ad ve bildirim adresi alanları bu
kapsamdadır.

**2.4.6 Başlıklar ve Etiketler** adım başlıklarının içeriği gerçekten tanımlamasını ister:
"Adım 2" bir başlık değildir, "Üye bilgisi" başlıktır ve adım numarası ayrı bir metinle
verilir.

#### Sık Yapılan Hata

**Adım göstergesinin yalnızca görsel olması.** Dolan bir çubuk ya da renklenen üç nokta,
ekranı görmeyen kullanıcıya hiçbir şey söylemez. Saptaması, göstergenin metin karşılığının
ağaçta bulunup bulunmadığının denetlenmesidir.

**Hata özetinin bulunduğu yerde bırakılması.** Özet oluşturulup odak taşınmadığında kullanıcı
gönderimin başarısız olduğunu öğrenemez. Saptaması denetimin ikinci kuralıdır: hata varken
odağın özete taşınıp taşınmadığı sınanır.

**Geri gidişin sessiz bırakılması.** İleri gidişte kurulan odak ve duyuru düzeni geri gidişte
de gereklidir. Saptaması, iki yönün aynı kural kümesinden geçirilmesidir.

**Tek alanlı grup kurulması.** Bir alanı `fieldset` içine almak, alanın adını grup adıyla
birlikte iki kez duyurur. Saptaması, grup içindeki alan sayısının sayılmasıdır.

#### Özet

- Form, alan sayısına göre değil bağımsız karar kümelerine göre bölünür; kısa formlar tek
  görünümde kalır.
- Gruplamanın ölçütü alanların ortak bir soruya yanıt vermesidir; tek alanlı grup kurulmaz.
- Adım değişimi üç işlem gerektirir: odağın yeni adım başlığına taşınması, geçişin kibar
  öncelikle duyurulması ve adım göstergesinin metinle verilmesi. Geri gidiş de bir adım
  değişimidir.
- Formun ilk gönderim düğmesi örtük gönderimde çalışır; gönderim niteliği taşımayan
  düğmeler `type="button"` yazılır.
- 3.3.4 bağlayıcı gönderimlerde geri alma, düzeltme ya da onaylama yollarından en az birini
  ister; son adımdaki düzeltilebilir özet ikinci yolu kurar.
- Denetimin ürettiği bulguların çoğu ekranda görünmez; odak ve duyuru kusurları yalnızca
  kural sınamasıyla ortaya çıkar.

#### Sonraki Adım

Bu konudaki bütün bileşenler bir eylemi çalıştırdı: aramayı koşturmak, süzgeci uygulamak,
formu göndermek. Sonuç tablosundaki kayıt başlıkları ise farklı bir iş yapar — kullanıcıyı
başka bir kaynağa götürür. İkisi ekranda birbirine benzetilebilir, ancak klavye
sözleşmeleri, ağaçtaki rolleri ve kullanıcının onlardan beklediği şey ayrıdır. Sonraki ders
bu ayrımı kurala bağlar ve karışmanın hangi somut kırılmaları ürettiğini gösterir.

### Bağlantı ve Düğme Ayrımı

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/baglanti-ve-dugme-ayrimi

Kaynak değişimine dayanan karar kuralı, iki bileşenin klavye sözleşmelerinin karşılaştırılması, yanlış eşlemenin ürettiği kayıpların sayımı ve bağlantı metni ölçütleri.

Ödünç alma formundaki her denetim bir eylem çalıştırıyordu. Sonuç tablosundaki kayıt
başlıkları ise başka bir iş yapar: kullanıcıyı kayıt ayrıntısı sayfasına götürür. İkisi
sunum katmanında birbirinin yerine geçebilecek kadar benzetilebilir — bir bağlantı kutulu ve
dolgulu çizilebilir, bir düğme altı çizili metin gibi görünebilir.

Ağaçta ise ikisi ayrıdır ve bu ayrım yalnızca bir adlandırma tercihi değildir: iki bileşenin
klavye sözleşmesi, tarayıcının sunduğu ek olanaklar ve kullanıcının onlardan beklediği şey
farklıdır. Bu ders ayrımı bir karar kuralına bağlar ve ihlalin ne kaybettirdiğini sayar.

#### Karar Kuralı

Kural tek cümledir: **kullanıcı başka bir kaynağa gidiyorsa bağlantı, gitmiyorsa düğmedir.**
Ölçü, çalıştırma sonrasında adres çubuğundaki adresin değişip değişmediğidir; indirme de
kaynağa erişme sayılır.

Kuralın işlettiği bazı durumlar sezgiye aykırı görünür. Süzgeç seçimlerini adrese yazan bir
denetim — "Yalnız rafta olanlar" durumunu paylaşılabilir bir adres hâline getiren düzen —
bağlantıdır, çünkü ürettiği şey başkasına gönderilebilen bir adrestir. Sayfanın içindeki bir
bölüme atlayan denetim de bağlantıdır; hedefi belge parçasıdır ve tarayıcı geçmişine
girer. Buna karşılık sonuç tablosunu yenileyen ama adresi değiştirmeyen bir süzgeç
uygulaması düğmedir.

#### Yerel Öge Önce

Bağlantının karşılığı `href` özniteliği taşıyan `a` ögesidir; düğmenin karşılığı `button`
ögesidir. `href` taşımayan bir `a` ögesi ağaçta anlamsız bir düğümdür: rol üretmez, odak
almaz, tuş işlemez.

Bağlantı, düğmenin sağlamadığı dört olanağı getirir: adresin kopyalanması, bağlam menüsüyle
yeni sekmede açılması, orta tuşla açılması ve yardımcı teknolojinin ürettiği bağlantı
listesinde görünmesi. Bunların hiçbiri koda yazılabilecek şeyler değildir; tarayıcının
bağlantı ögesine bağladığı davranışlardır.

#### Rol, Ad ve Durum

İki bileşenin de adı içeriğinden hesaplanır. Bağlantıda ek bir kısıt vardır: adın **nereye
gidildiğini** söylemesi gerekir.

Sonuç tablosunda her satırın sonunda "Ayrıntı" yazan bir bağlantı bulunması yaygın bir
düzendir. Kırk satırlık bir tabloda bu, ağaçta kırk tane aynı adlı bağlantı üretir. Bağlantı
listesini açan kullanıcı kırk özdeş satır görür ve hiçbirinin nereye gittiğini bilemez.
Çözüm, bağlantı metnini kaydın adına genişletmek ya da görünen metni gizli bir parçayla
tamamlayıp adı "Yapıların Dili ayrıntısı" hâline getirmektir.

Bağlantının kalıcı bir durum bildirimi yoktur. Bulunulan sayfayı gösteren bağlantı
`aria-current="page"` taşır; bu da bir durumdur, ama açık/kapalı gibi bir etkileşim durumu değil,
konum bildirimidir ve gezinme bileşenlerinde ele alınır.

#### İki Klavye Sözleşmesi

| Tuş / eylem | Bağlantı | Düğme |
|---|---|---|
| `Tab` | Sekme durağıdır. | Sekme durağıdır. |
| `Enter` | Bağlantıyı izler. | Düğmeyi çalıştırır. |
| `Boşluk` | Sayfayı aşağı kaydırır. | Düğmeyi çalıştırır. |
| Bağlam menüsü / orta tuş | Yeni sekmede açar, adresi kopyalar. | Karşılığı yoktur. |

Boşluk tuşundaki fark, yanlış eşlemenin en görünür sonucudur. Bağlantı gibi kurulmuş bir
eylem denetiminde boşluk tuşuna basan kullanıcı sayfayı kaydırır; düğme gibi kurulmuş bir
gezinme denetiminde ise bağlam menüsünden "yeni sekmede aç" seçeneği hiç görünmez.

#### Eşlemenin Denetimi

```js
// bag.mjs — baglanti ile dugme arasindaki kararin kurallastirilmasi ve denetimi

// Karar kurali: kaynak degisiyorsa (adres ya da indirme) baglanti, degilse dugme.
const karar = (n) => (n.adresDegisir || n.indirme ? "baglanti" : "dugme");

// Isaretlemeden uretilen davranis
function uretilen(m) {
  const baglantiMi = m.oge === "a" && m.href !== undefined;
  const dugmeMi = m.oge === "button";
  const rol = m.role ?? (baglantiMi ? "link" : dugmeMi ? "button" : "generic");
  return {
    rol,
    odaklanir: baglantiMi || dugmeMi || m.tabindex === 0,
    enter: baglantiMi || dugmeMi || m.tusIsler === true,
    bosluk: dugmeMi || m.tusIsler === true,
    yeniSekme: baglantiMi,          // baglam menusu ve orta tik yalniz adresi olanda calisir
    adresKopyalanir: baglantiMi,
    baglantiListesinde: rol === "link",
  };
}

// --- Katalog arayuzundeki denetimler ----------------------------------------
const ORNEKLER = [
  ['<a href="/kayit/K-118">Yapıların Dili</a>',
   { adresDegisir: true }, { oge: "a", href: "/kayit/K-118" }],
  ['<a href="#" tabindex="0">Süzgeci temizle</a>',
   { adresDegisir: false }, { oge: "a", href: "#", tusIsler: true }],
  ['<button>Kayıt ayrıntısı</button> (kodla gezinir)',
   { adresDegisir: true }, { oge: "button" }],
  ['<a href="/odunc/K-118" role="button">Ödünç al</a>',
   { adresDegisir: true }, { oge: "a", href: "/odunc/K-118", role: "button" }],
  ['<div tabindex="0">Ara</div>',
   { adresDegisir: false }, { oge: "div", tabindex: 0 }],
  ['<button type="submit">Ara</button>',
   { adresDegisir: false }, { oge: "button" }],
  ['<a href="/kunye/K-118.pdf" download>Künyeyi indir</a>',
   { adresDegisir: false, indirme: true }, { oge: "a", href: "/kunye/K-118.pdf" }],
  ['<span role="link" tabindex="0">Yardım</span>',
   { adresDegisir: true }, { oge: "span", role: "link", tabindex: 0 }],
];

const im = (b) => (b ? "var" : "yok");

console.log("isaretleme".padEnd(53) + "gereken".padEnd(10) + "rol".padEnd(10) +
  "Enter".padEnd(7) + "Bosluk".padEnd(8) + "yeni sekme".padEnd(12) + "bag. listesi");
for (const [kaynak, niyet, m] of ORNEKLER) {
  const u = uretilen(m);
  console.log(kaynak.padEnd(53) + karar(niyet).padEnd(10) + u.rol.padEnd(10) +
    im(u.enter).padEnd(7) + im(u.bosluk).padEnd(8) + im(u.yeniSekme).padEnd(12) +
    im(u.baglantiListesinde));
}

console.log("\nkural denetimi:");
let bulgu = 0;
for (const [kaynak, niyet, m] of ORNEKLER) {
  const g = karar(niyet);
  const u = uretilen(m);
  const b = [];
  if (g === "baglanti" && u.rol !== "link")
    b.push("gezinme dugme olarak bildirildi: adres kopyalanamaz, yeni sekmede acilamaz");
  if (g === "dugme" && u.rol === "link")
    b.push("eylem baglanti olarak bildirildi: gecmise kayit girer, hedef adres anlamsiz");
  if (!u.odaklanir) b.push("klavyeyle odaklanilamaz");
  if (!u.enter) b.push("giris tusu calismiyor");
  if (u.rol === "button" && !u.bosluk) b.push("dugme rolu bildirildi, bosluk tusu calismiyor");
  if (u.rol === "link" && m.oge !== "a") b.push("baglanti rolu bildirildi, adres yok");
  for (const x of b) { bulgu++; console.log("  " + kaynak.padEnd(53) + x); }
}
console.log(`\n${ORNEKLER.length} ornekte ${bulgu} bulgu`);

// --- Karar tablosu ----------------------------------------------------------
console.log("\nkarar tablosu:");
console.log("davranis".padEnd(40) + "bilesen");
const DAVRANISLAR = [
  ["kayit ayrinti sayfasina gider", { adresDegisir: true }],
  ["sonuc tablosunu suzer, adres degismez", { adresDegisir: false }],
  ["suzgec durumunu adrese yazar", { adresDegisir: true }],
  ["kunye dosyasini indirir", { adresDegisir: false, indirme: true }],
  ["odunc alma istegini gonderir", { adresDegisir: false }],
  ["sayfa icindeki bir bolume atlar", { adresDegisir: true }],
];
for (const [ad, n] of DAVRANISLAR) console.log(ad.padEnd(40) + karar(n));
```

```
isaretleme                                           gereken   rol       Enter  Bosluk  yeni sekme  bag. listesi
<a href="/kayit/K-118">Yapıların Dili</a>            baglanti  link      var    yok     var         var
<a href="#" tabindex="0">Süzgeci temizle</a>         dugme     link      var    var     var         var
<button>Kayıt ayrıntısı</button> (kodla gezinir)     baglanti  button    var    var     yok         yok
<a href="/odunc/K-118" role="button">Ödünç al</a>    baglanti  button    var    yok     var         yok
<div tabindex="0">Ara</div>                          dugme     generic   yok    yok     yok         yok
<button type="submit">Ara</button>                   dugme     button    var    var     yok         yok
<a href="/kunye/K-118.pdf" download>Künyeyi indir</a>baglanti  link      var    yok     var         var
<span role="link" tabindex="0">Yardım</span>         baglanti  link      yok    yok     yok         var

kural denetimi:
  <a href="#" tabindex="0">Süzgeci temizle</a>         eylem baglanti olarak bildirildi: gecmise kayit girer, hedef adres anlamsiz
  <button>Kayıt ayrıntısı</button> (kodla gezinir)     gezinme dugme olarak bildirildi: adres kopyalanamaz, yeni sekmede acilamaz
  <a href="/odunc/K-118" role="button">Ödünç al</a>    gezinme dugme olarak bildirildi: adres kopyalanamaz, yeni sekmede acilamaz
  <a href="/odunc/K-118" role="button">Ödünç al</a>    dugme rolu bildirildi, bosluk tusu calismiyor
  <div tabindex="0">Ara</div>                          giris tusu calismiyor
  <span role="link" tabindex="0">Yardım</span>         giris tusu calismiyor
  <span role="link" tabindex="0">Yardım</span>         baglanti rolu bildirildi, adres yok

8 ornekte 7 bulgu

karar tablosu:
davranis                                bilesen
kayit ayrinti sayfasina gider           baglanti
sonuc tablosunu suzer, adres degismez   dugme
suzgec durumunu adrese yazar            baglanti
kunye dosyasini indirir                 baglanti
odunc alma istegini gonderir            dugme
sayfa icindeki bir bolume atlar         baglanti
```

#### Bulguların Okunması

**Boş adresli bağlantı iki kez yanlıştır.** İkinci satırdaki süzgeç temizleme denetimi bir
eylemdir ama bağlantı olarak bildirilmiş. İki sonucu var: tarayıcı geçmişine anlamsız bir
kayıt girer ve kullanıcı geri düğmesine bastığında hiçbir şey olmaz; bağlantı listesinde
hedefi olmayan bir satır belirir. Kullanıcının bağlam menüsünden yeni sekmede açması ise
boş bir sayfa getirir.

**Rol bildirimi bağlantıyı düğme yapmaz.** Dördüncü satır bunu gösteriyor. `role="button"`
yazıldığında ağaçtaki rol değişiyor, ancak öge hâlâ bir bağlantıdır: boşluk tuşu
çalışmıyor, yeni sekmede açılma olanağı duruyor. Ekranda düğme, ağaçta düğme, davranışta
bağlantı olan bir bileşen ortaya çıkıyor ve üçü birbiriyle çelişiyor.

**Rol bildirimi davranış da eklemez.** Son satırdaki `role="link"` taşıyan `span` ögesi
odaklanabilir, çünkü `tabindex` yazılmış; ama giriş tuşuna yanıt vermiyor ve gidilecek bir
adres taşımıyor. Bağlantı listesinde görünüyor, seçildiğinde hiçbir yere gitmiyor.

**Düğmeyle yapılan gezinme sessizce kayıp üretir.** Üçüncü satırda ekranda hiçbir kusur görünmez:
düğme odak alır, giriş ve boşluk tuşlarıyla çalışır, sayfa açılır. Kaybolan şey, bağlantının
getirdiği dört olanaktır. Kullanıcı kayıt ayrıntısını yeni sekmede açamaz, orta tuşla açamaz,
adresini kopyalayamaz ve bağlantı listesinde bulamaz.

#### Ölçülebilir Kısıtlar

**2.4.4 Bağlantı Amacı (Bağlam İçinde)**, bağlantının amacının kendi metninden ya da
bulunduğu bağlamdan anlaşılmasını ister; **2.4.9 Bağlantı Amacı (Yalnız Bağlantıdan)** aynı
şeyi yalnız metinden ister ve AAA düzeyindedir. Tablodaki "Ayrıntı" bağlantıları birinciyi
tablo bağlamı sayesinde karşılar, ikinciyi karşılamaz.

**1.4.1 Rengin Tek Taşıyıcı Olmaması**, metin içindeki bağlantıların çevresindeki metinden
yalnız renkle ayrılmamasını ister. Altını çizmek bu koşulu doğrudan karşılar. Renk ayrımı
yeterli sayılacaksa iki koşul birden gerekir: bağlantı renginin çevre metinle en az 3:1
kontrast oranı taşıması ve odak ya da üzerine gelme durumunda ikinci bir görsel ayrımın
belirmesi.

**2.5.8 Hedef Boyutu**, metin içindeki bağlantıları satır içi istisnasıyla kapsam dışında
bırakır; bir paragrafın ortasındaki bağlantının yüksekliği satır yüksekliğine bağlıdır ve
büyütülmesi metni bozar. Sonuç tablosundaki bağlantılar ise satır içi değildir ve ölçüte
girer.

**3.2.5 İstek Üzerine Değişiklik**, yeni bir pencere açılmasını kullanıcının isteğine
bağlar. Yeni sekmede açan bir bağlantı varsa, bu davranış adının parçası olarak
bildirilmelidir; ekranda beliren küçük bir simge, alternatif metni yoksa hiçbir şey
bildirmez.

#### Sık Yapılan Hata

**Adressiz bağlantı.** Saptaması araçsızdır: `href` taşımayan ya da değeri `#` olan her `a`
ögesi bir bulgudur. Otomatik denetleyicilerin varlık kuralı bunu yakalar.

**Aynı adlı çok sayıda bağlantı.** Saptaması bir teklik denetimidir: sayfadaki bağlantı
adları toplanır, aynı ada sahip olup farklı adreslere giden çiftler listelenir.

**Görünüşün kararı belirlemesi.** "Bu bir düğme gibi görünsün" gerekçesiyle bağlantı
kurulması ya da tersi. Saptaması karar tablosudur: bileşenin ne yaptığı sorulur, görünüşü
sorulmaz. Görsel ağırlık her iki bileşene de bağımsız olarak verilebilir.

#### Özet

- Karar kuralı tek cümledir: kullanıcı başka bir kaynağa gidiyorsa bağlantı, gitmiyorsa
  düğme; indirme de kaynağa erişme sayılır.
- Bağlantı, adresin kopyalanması, yeni sekmede açılması, orta tuşla açılması ve bağlantı
  listesinde görünmesi olanaklarını getirir; bunlar koda yazılamaz.
- Giriş tuşu ikisinde de çalışır, boşluk tuşu yalnız düğmede çalıştırır; bağlantıda sayfayı
  kaydırır.
- Rol bildirimi ne davranışı değiştirir ne de ekler; `role="button"` taşıyan bir bağlantı
  ekranda, ağaçta ve davranışta birbiriyle çelişen bir bileşen üretir.
- Bağlantı adı nereye gidildiğini söylemelidir; tabloda yinelenen "Ayrıntı" metinleri
  2.4.9 ölçütünü karşılamaz.
- Metin içindeki bağlantılar 2.5.8 hedef boyutu ölçütünün satır içi istisnasına girer, tablo
  satırındaki bağlantılar girmez.

#### Sonraki Adım

Bu konu tek tek denetimleri belirtti; her birinin sınırı kendi kutusuydu. Sonuç tablosunun
kart görünümünde ise sınır belirsizleşir: bir kart görsel, başlık, yazar, yıl ve iki denetim
taşır ve tasarımcı kartın **tamamının** tıklanabilir olmasını ister. O istek, tek bir
bağlantının içine ikinci bir denetim koymayı gerektirir ve kalıp burada kırılır. Sonraki
ders kartı belirtir: içerik gruplamanın nasıl bildirileceğini, tıklanabilirlik sorununun üç
çözümünü ve her birinin sekme durağı sayısına ne yaptığını hesaplar.

## Kapsayıcı ve Gezinme Bileşenleri

Kart, sekme, akordeon, menü, kırıntı yolu, sayfalama, tablo ve döngüsel içerik kalıplarının klavye sözleşmesi, odak yönetimi ve durum bildirimiyle belirtilmesi.

### Kartlar

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/kartlar

Kartın bir bileşen değil bir gruplama düzeni olduğu; üç tıklanabilirlik kalıbının sekme durağı, erişilebilir ad ve iç içe yerleşim bakımından hesaplanması.

Önceki konu tek tek denetimleri belirtti ve her denetimin sınırı kendi kutusuydu. Sonuç
listesinin kart görünümünde bu sınır belirsizleşir. Bir kart görsel, başlık, yazar, yıl,
durum rozeti ve iki denetim taşır; tasarım kararı ise kartın **tamamının** tıklanabilir
olmasıdır.

İstek makuldür: kullanıcı kartın herhangi bir yerine tıkladığında kayıt ayrıntısına
gitmelidir. Ancak kartın içinde bir düğme de vardır ve iki denetim iç içe geçemez. Bu ders
kartı belirtir ve tıklanabilirlik sorununun üç çözümünü sayarak karşılaştırır.

#### Kart Bir Bileşen Değil, Bir Düzendir

Kartın çözdüğü sorun, **türü farklı bilgilerin** bir arada taranabilir bir birim olarak
sunulmasıdır: bir görsel, bir başlık, iki üstveri satırı ve bir durum. Görsel gruplamanın
ölçütü Arayüz Tasarımının Temelleri kursundaki ortak bölge ilkesidir; bir yüzey, içindekileri
tek bir birim olarak okutur.

Kart kullanılmayacağı yer de bundan çıkar: bilgiler türdeş ve karşılaştırılabilir olduğunda
kart değil tablo kullanılır. Kırk kaydın yayın yılına göre karşılaştırılması gerekiyorsa
kart görünümü her değeri farklı bir dikey konuma yerleştirir ve karşılaştırmayı olanaksız
kılar.

#### Yerel Öge Önce

Kartın bir öge karşılığı yoktur, çünkü bir denetim değildir. İşaretlemesi iki karardan
oluşur.

Birincisi, kart listesinin bir **liste** olarak yazılmasıdır. `ul` ve `li` ögeleri ağaca
liste rolünü ve öge sayısını bildirir; kullanıcı listeye girdiğinde kaç kayıt olduğunu
öğrenir. `div` yığınıyla kurulan bir ızgarada bu sayı hiçbir yerde bulunmaz.

İkincisi, kartın içinde bir **başlık ögesinin** bulunmasıdır. Başlık, kaydı ana hatta
yerleştirir ve başlık listesinden gezinen kullanıcıya kırk kaydı tek tek okumadan geçme
olanağı verir. Kart bağımsız olarak dağıtılabilir bir birimse `article` ögesiyle sarılır ve
`aria-labelledby` ile başlığa bağlanarak ağaçta adlandırılmış bir bölge olur.

#### Tıklanabilirlik Sorununun Üç Çözümü

**A. Kartın tamamı bir bağlantıdır.** Görsel de, başlık da, özet de bağlantının içindedir.
İçindeki düğme de öyle.

**B. Yalnız başlık bağlantıdır, yüzey ona yayılır.** Bağlantı sözde bir elemanla kartın
tamamını kaplar; düğme sunum katmanında bu kaplamanın üstüne çıkarılır.

**C. Her parça ayrı bağlantıdır.** Görsel, başlık ve "Ayrıntı" bağlantısı aynı hedefe gider.

```js
// kart.mjs — kart kaliplarinda sekme duragi sayisi, erisilebilir ad ve ic ice yerlesim

const KART = {
  baslik: "Yapıların Dili",
  yazar: "T. Aksoy",
  yil: "2019",
  durum: "Rafta",
  ozet: "Yapı çözümlemesine giriş niteliğinde bir el kitabı.",
};

// Dugum: { oge, oz, cocuklar }  — metin dugumleri dizgi olarak durur
const etkilesimli = (d) =>
  (d.oge === "a" && d.oz?.href !== undefined) || d.oge === "button";

function odakDuraklari(d, yol = []) {
  if (typeof d === "string") return [];
  const bu = etkilesimli(d) ? [[...yol, d.oge + (d.oz?.href ? "[href]" : "")].join(" > ")] : [];
  const alt = (d.cocuklar ?? []).flatMap((c) => odakDuraklari(c, etkilesimli(d) ? [...yol, d.oge] : yol));
  return [...bu, ...alt];
}

function metin(d) {
  if (typeof d === "string") return d;
  const o = d.oz ?? {};
  if (o["aria-hidden"]) return "";
  if (d.oge === "img") return o.alt ?? "";
  return (d.cocuklar ?? []).map(metin).join(" ").replace(/\s+/g, " ").trim();
}

// Etkilesimli ogenin erisilebilir adi: aria-label > icerik metni
function ad(d) {
  const o = d.oz ?? {};
  if (o["aria-label"]) return o["aria-label"];
  return metin(d);
}

// Ic ice etkilesimli oge sayisi (bir baglantinin icindeki dugme/baglanti)
function icIce(d, icerideyiz = false) {
  if (typeof d === "string") return 0;
  const bu = icerideyiz && etkilesimli(d) ? 1 : 0;
  return bu + (d.cocuklar ?? []).reduce(
    (t, c) => t + icIce(c, icerideyiz || etkilesimli(d)), 0);
}

const govde = () => [
  { oge: "img", oz: { alt: "" } },
  { oge: "h3", cocuklar: [KART.baslik] },
  { oge: "p", cocuklar: [KART.yazar + ", " + KART.yil] },
  { oge: "p", cocuklar: [KART.ozet] },
  { oge: "span", cocuklar: [KART.durum] },
];

// A: kartin tamami tek baglanti; icinde ayrica bir dugme var
const A = {
  oge: "article",
  cocuklar: [
    { oge: "a", oz: { href: "/kayit/K-118" },
      cocuklar: [...govde(), { oge: "button", cocuklar: ["Ödünç al"] }] },
  ],
};

// B: yalnizca baslik baglanti; dugme kardes; kart yuzeyi baglantiya yayiliyor
const B = {
  oge: "article",
  cocuklar: [
    { oge: "img", oz: { alt: "" } },
    { oge: "h3", cocuklar: [{ oge: "a", oz: { href: "/kayit/K-118" }, cocuklar: [KART.baslik] }] },
    { oge: "p", cocuklar: [KART.yazar + ", " + KART.yil] },
    { oge: "p", cocuklar: [KART.ozet] },
    { oge: "span", cocuklar: [KART.durum] },
    { oge: "button", cocuklar: ["Ödünç al"] },
  ],
};

// C: gorsel, baslik ve "Ayrinti" ayri ayri baglanti
const C = {
  oge: "article",
  cocuklar: [
    { oge: "a", oz: { href: "/kayit/K-118" }, cocuklar: [{ oge: "img", oz: { alt: "" } }] },
    { oge: "h3", cocuklar: [{ oge: "a", oz: { href: "/kayit/K-118" }, cocuklar: [KART.baslik] }] },
    { oge: "p", cocuklar: [KART.yazar + ", " + KART.yil] },
    { oge: "span", cocuklar: [KART.durum] },
    { oge: "a", oz: { href: "/kayit/K-118" }, cocuklar: ["Ayrıntı"] },
    { oge: "button", cocuklar: ["Ödünç al"] },
  ],
};

const KALIPLAR = [
  ["A: kartin tamami baglanti", A],
  ["B: baslik baglanti + yayilan yuzey", B],
  ["C: her parca ayri baglanti", C],
];

const SAYFADAKI_KART = 40;

console.log("kalip".padEnd(36) + "durak".padEnd(8) + "40 kartta".padEnd(11) +
  "ic ice".padEnd(8) + "ilk duragin adi (karakter)");
for (const [baslik, k] of KALIPLAR) {
  const duraklar = odakDuraklari(k);
  const ilk = (k.cocuklar ?? []).flatMap(function bul(d) {
    if (typeof d === "string") return [];
    return etkilesimli(d) ? [d] : (d.cocuklar ?? []).flatMap(bul);
  })[0];
  const a = ad(ilk);
  console.log(baslik.padEnd(36) + String(duraklar.length).padEnd(8) +
    String(duraklar.length * SAYFADAKI_KART).padEnd(11) +
    String(icIce(k)).padEnd(8) + `"${a.slice(0, 34)}${a.length > 34 ? "…" : ""}" (${a.length})`);
}

console.log("\nsekme duraklari:");
for (const [baslik, k] of KALIPLAR) {
  console.log("  " + baslik);
  for (const d of odakDuraklari(k)) console.log("    " + d);
}

console.log("\nkural denetimi:");
let bulgu = 0;
for (const [baslik, k] of KALIPLAR) {
  const duraklar = odakDuraklari(k);
  const ogeler = (k.cocuklar ?? []).flatMap(function bul(d) {
    if (typeof d === "string") return [];
    return etkilesimli(d) ? [d, ...(d.cocuklar ?? []).flatMap(bul)] : (d.cocuklar ?? []).flatMap(bul);
  });
  const adlar = ogeler.map(ad);
  const b = [];
  if (icIce(k) > 0) b.push(`etkilesimli oge baska bir etkilesimli ogenin icinde (${icIce(k)} adet)`);
  for (const a of adlar) {
    if (a === "") b.push("adsiz durak: icerigi bos alternatif metinli gorsel");
    else if (a.length > 60) b.push(`erisilebilir ad ${a.length} karakter: gezinme listesinde okunamaz`);
  }
  const hedefler = new Map();
  for (const o of ogeler) {
    const h = o.oz?.href;
    if (!h) continue;
    if (!hedefler.has(h)) hedefler.set(h, new Set());
    hedefler.get(h).add(ad(o));
  }
  for (const [h, kume] of hedefler) {
    if (kume.size > 1) b.push(`ayni hedefe (${h}) farkli adlarla ${kume.size} baglanti gidiyor`);
  }
  if (duraklar.length * SAYFADAKI_KART > 100)
    b.push(`40 kartta ${duraklar.length * SAYFADAKI_KART} durak: liste sekme tusuyla gecilemez`);
  for (const x of b) { bulgu++; console.log("  " + baslik.padEnd(36) + x); }
}
console.log(`\n${KALIPLAR.length} kalipta ${bulgu} bulgu`);
```

```
kalip                               durak   40 kartta  ic ice  ilk duragin adi (karakter)
A: kartin tamami baglanti           2       80         1       "Yapıların Dili T. Aksoy, 2019 Yapı…" (96)
B: baslik baglanti + yayilan yuzey  2       80         0       "Yapıların Dili" (14)
C: her parca ayri baglanti          4       160        0       "" (0)

sekme duraklari:
  A: kartin tamami baglanti
    a[href]
    a > button
  B: baslik baglanti + yayilan yuzey
    a[href]
    button
  C: her parca ayri baglanti
    a[href]
    a[href]
    a[href]
    button

kural denetimi:
  A: kartin tamami baglanti           etkilesimli oge baska bir etkilesimli ogenin icinde (1 adet)
  A: kartin tamami baglanti           erisilebilir ad 96 karakter: gezinme listesinde okunamaz
  C: her parca ayri baglanti          adsiz durak: icerigi bos alternatif metinli gorsel
  C: her parca ayri baglanti          ayni hedefe (/kayit/K-118) farkli adlarla 3 baglanti gidiyor
  C: her parca ayri baglanti          40 kartta 160 durak: liste sekme tusuyla gecilemez

3 kalipta 5 bulgu
```

#### Bulguların Okunması

**A kalıbı iki ayrı kural kırar.** Bağlantının içindeki düğme, ağaçta bir etkileşimli ögenin
içinde ikinci bir etkileşimli öge olarak durur; bu yerleşim HTML içerik modelince açıkça
yasaklanmıştır — tarayıcı yine de ayrıştırır, ama yardımcı teknolojinin ona nasıl davranacağı
güvence altında değildir. İkincisi ölçülebilir: bağlantının adı doksan altı karakter uzunluğunda.
Kullanıcı bağlantı listesini açtığında her satır bir paragraf uzunluğundadır ve liste taranamaz
hâle gelir.

**C kalıbı sayıyla düşer.** Kart başına dört durak, kırk kartlık bir listede yüz altmış
durak eder. Klavyeyle gezen bir kullanıcının listeyi geçmesi yüz altmış sekme vuruşu
gerektirir. Aynı kalıp iki bulgu daha üretiyor: alternatif metni boş görselden oluşan
bağlantı adsız kalıyor ve aynı hedefe üç farklı adla üç bağlantı gidiyor.

**B kalıbı denetimden temiz çıkıyor.** Kart başına iki durak, tek ve kısa bir bağlantı adı,
iç içe yerleşim yok. Bunun bedeli sunum katmanındadır ve belirtimde yazılmalıdır: yayılan
yüzey kartın metnini fare ile seçilemez hâle getirir. Düzeltmesi, kaplamanın işaret
olaylarını yalnızca boş alanlarda yakalamasıdır.

Sekme durağı listesi bir ayrıntıyı daha gösteriyor. B ve C kalıplarında düğme kartın kendi
düzeyindedir; A kalıbında `a > button` yolunda, yani bağlantının içindedir. Yol bilgisi,
işaretlemeye bakmadan iç içe yerleşimi saptamanın yoludur.

#### Klavye Sözleşmesi

Kartın kendi klavye sözleşmesi **yoktur**. Kart odak almaz, tuş dinlemez ve sekme sırasında
görünmez. Geçerli olan sözleşmeler, içindeki bağlantı ve düğmenin sözleşmeleridir.

Bu, belirtimin bir kararıdır: karta `tabindex` yazıp tıklama dinleyicisi bağlamak, sekme
durağı sayısını kart başına bir artırır ve odak alan bir kabuk üretir. O kabuğun rolü
yoktur, adı belirsizdir ve giriş tuşuna verdiği yanıt elle yazılmak zorundadır.

Kart listesi uzunsa sekme durağı sayısı ayrı bir kararla düşürülür: listeden önce bir atlama
bağlantısı konur ve kullanıcıya listeyi geçme yolu verilir. Kart içindeki ikincil denetimleri
gizleyip yalnız üzerine gelindiğinde göstermek bu sayıyı düşürmez, çünkü gizlenen denetim
klavyeyle ulaşılamaz hâle gelir.

#### Ölçülebilir Kısıtlar

**2.4.7 Odak Görünürlüğü.** Kartın kenarında taşan içeriği kırpan bir sunum kararı, kart
kenarına yakın bir denetimin odak halkasını da kırpar. Gösterge var olur, görünmez.
Saptaması bir klavye turudur.

**1.4.11 Metin Dışı Kontrast.** Kartı sayfa yüzeyinden ayıran sınır, bilgi taşıyorsa 3:1
ölçüsüne girer. Kartlar birbirinden yalnız gölgeyle ayrılıyorsa ve gölge düşük karşıtlıksa,
grup sınırı görülemez.

**1.4.1 Rengin Tek Taşıyıcı Olmaması.** "Rafta", "Ödünçte", "Ayırtılmış" durum rozetleri
yalnız renkle ayrılamaz; her rozet metnini taşır.

**1.4.10 Yeniden Akış.** Kart ızgarası, 320 CSS pikseli genişliğe indirildiğinde iki yönlü
kaydırma gerektirmemelidir. Sabit genişlikli kart kutuları bu ölçütü doğrudan kaldırır.

**2.5.8 Hedef Boyutu.** Karttaki ikincil denetimler — işaretleme ve paylaşma düğmeleri — 24
piksel ölçüsüne ya da aralık istisnasına uymalıdır.

#### Sık Yapılan Hata

**Kartın başlıksız bırakılması.** Kalın yazılmış bir metin başlık değildir. Saptaması, kart
listesindeki başlık düzeylerinin sayılmasıdır: kırk kart varsa ana hatta kırk başlık
bulunmalıdır.

**Görselin kayıt adıyla adlandırılması.** Kartın görseli, yanında zaten başlık dururken
alternatif metin taşırsa aynı bilgi iki kez duyurulur. Kararı: kart içindeki kapak görseli
sunumsaldır ve alternatif metni boştur.

**Kart listesinin liste olarak yazılmaması.** Saptaması, listenin ağaçtaki öge sayısının
ekrandaki kart sayısıyla karşılaştırılmasıdır; sayı yoksa yapı yoktur.

#### Özet

- Kart bir denetim değil, türü farklı bilgileri tek birim olarak sunan bir gruplama
  düzenidir; türdeş ve karşılaştırılabilir veri için tablo kullanılır.
- Kart listesi liste ögeleriyle yazılır ve her kart bir başlık ögesi taşır; bu ikisi öge
  sayısını ve ana hattı ağaca bildirir.
- Kartın tamamını bağlantıya almak iç içe etkileşimli öge üretir ve bağlantı adını okunamaz
  uzunluğa çıkarır.
- Her parçayı ayrı bağlantı yapmak sekme durağı sayısını kart başına dörde çıkarır; kırk
  kartlık listede yüz altmış durak eder.
- Yalnız başlığın bağlantı olduğu ve yüzeyin ona yayıldığı kalıp üç ölçütten de temiz çıkar;
  bedeli metnin seçilebilirliğidir ve bu, kaplamanın olay yakalama alanıyla çözülür.
- Kartın kendi klavye sözleşmesi yoktur; karta odak vermek rolsüz ve adsız bir kabuk üretir.

#### Sonraki Adım

Kart, içindeki denetimlerin toplamıydı; kendisi bir davranış taşımıyordu. Kayıt ayrıntısı
sayfasında ise durum değişir: künye, mevcut kopyalar ve ödünç geçmişi üç ayrı bölümdür ve
aynı anda yalnız biri gösterilir. Bu, kapsayıcının kendisinin bir davranış taşıdığı ilk
kalıptır — hangi bölümün görüneceğine o karar verir. Sonraki ders sekme kalıbını belirtir:
sekme başlıklarıyla panellerin nasıl ilişkilendirildiğini, dönen sekme durağının burada
neden zorunlu olduğunu ve seçimin kendiliğinden mi yoksa elle mi etkinleştirileceği
kararını ele alır.

### Sekmeler

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/sekmeler

Sekme başlıklarıyla panellerin ilişkilendirilmesi, dönen sekme durağının zorunluluğu, kendiliğinden ve elle etkinleştirme kararı ve seçim değişmezinin sınanması.

Kart, içindeki denetimlerin toplamıydı ve kendisi bir davranış taşımıyordu. Kayıt ayrıntısı
sayfası ise farklıdır: künye, mevcut kopyalar ve ödünç geçmişi üç ayrı bölümdür, aynı anda
yalnız biri gösterilir ve hangisinin gösterileceğine kapsayıcının kendisi karar verir.

Bu kalıbın iki gereksinimi vardır ve ikisi de kendiliğinden karşılanmaz. Kullanıcı üç
başlığın var olduğunu ve hangisinin etkin olduğunu bilmelidir; klavyeyle üçü arasında
gezinebilmelidir. Bu ders sekme kalıbını belirtir.

#### Kalıbın Çözdüğü Sorun

Sekme, **eşdeğer ve birbirini dışlayan** içerik bölümlerinin aynı yeri paylaşmasını sağlar.
Üç koşulun tamamı gerekir: bölümler aynı düzeydedir, aynı anda yalnız biri anlamlıdır ve
kullanıcı aralarında sık geçiş yapar.

Koşullar sağlanmıyorsa kalıp yanlıştır. Sıralı okunması gereken bölümler — ödünç alma
formunun üç adımı — sekme değildir; kullanıcı ikinci adıma geçmeden birincisini tamamlamak
zorundadır. Aynı anda okunması gereken bölümler de sekme değildir: künyeyi okurken mevcut
kopya sayısına bakmak gerekiyorsa, ikisi ayrı sekmelere konamaz.

İçerik uzunluğu da bir ölçüttür. Beşten fazla sekme, başlıkların taşmasına ve kaydırmalı bir
başlık şeridine yol açar; o noktada kalıp bir gezinme listesine dönüşmüştür ve sekme olarak
kurulmamalıdır.

#### Yerel Öge Önce

Sekme kalıbının yerel karşılığı **yoktur**. Kalıp üç rolle kurulur: sekme şeridi
`role="tablist"`, her başlık `role="tab"`, her bölüm `role="tabpanel"`.

Başlıklar birer düğmedir; `button` ögesinin üzerine rol yazılır. Yerel öge, rol dışındaki
her şeyi getirir: odaklanabilirlik, giriş ve boşluk tuşlarıyla çalıştırılabilirlik, devre
dışı bildirimi.

```html
<div role="tablist" aria-label="Kayıt bölümleri">
  <button role="tab" id="sekme-kunye" aria-selected="true"
          aria-controls="panel-kunye" tabindex="0">Künye</button>
  <button role="tab" id="sekme-kopya" aria-selected="false"
          aria-controls="panel-kopya" tabindex="-1">Mevcut kopyalar</button>
  <button role="tab" id="sekme-gecmis" aria-selected="false"
          aria-controls="panel-gecmis" tabindex="-1">Ödünç geçmişi</button>
</div>

<div role="tabpanel" id="panel-kunye" aria-labelledby="sekme-kunye" tabindex="0">…</div>
<div role="tabpanel" id="panel-kopya" aria-labelledby="sekme-kopya" tabindex="0" hidden>…</div>
<div role="tabpanel" id="panel-gecmis" aria-labelledby="sekme-gecmis" tabindex="0" hidden>…</div>
```

Bağ **iki yönlüdür**: sekme `aria-controls` ile panele, panel `aria-labelledby` ile sekmeye
işaret eder. İkinci yön adın kaynağıdır — panelin erişilebilir adı sekmenin metnidir ve
ayrıca yazılmaz. Şeridin `aria-label` bildirimi de gereklidir; sayfada birden çok sekme
şeridi bulunabildiği için hangisinin hangisi olduğu ayırt edilebilmelidir.

Panellerin `tabindex="0"` taşıması, içinde odaklanabilir öge bulunmayan bir panele klavyeyle
ulaşılabilmesi içindir. Panel odaklanabilir bir öge — bir bağlantı ya da denetim — taşıyorsa bu
değer gereksizdir; kullanıcı zaten içeri girer.

#### Dönen Sekme Durağı Burada Zorunludur

Sekme şeridinde yalnız bir başlık sekme sırasındadır; kalanların `tabindex` değeri eksi
birdir. Seçim değiştiğinde bu değerler birlikte güncellenir.

Gerekçe sayısaldır. Üç sekmeli bir şeritte düzenin kazancı iki duraktır. Ancak kalıbın
sözleşmesi ok tuşlarını başlıklar arası gezinmeye ayırdığı için, `Tab` tuşunun şerit
içinde ilerlemesi iki gezinme yolunu üst üste bindirir ve kullanıcı hangi tuşun ne
yaptığını öğrenemez. Dönen sekme durağı, `Tab` tuşuna tek bir anlam bırakır: şeritten çık.

#### Kendiliğinden mi, Elle mi?

Ok tuşu odağı taşıdığında seçim de taşınsın mı? İki karar da tanımlıdır.

**Kendiliğinden etkinleştirmede** odak hangi başlığa gelirse o panel açılır. Kullanıcı üç
sekmeyi ok tuşuyla gezerken üç paneli de görür; tuş sayısı azdır.

**Elle etkinleştirmede** ok tuşu yalnız odağı taşır, panel giriş ya da boşluk tuşuyla açılır.
Bu iki tuş vuruşu gerektirir.

Karar ölçütü, panel içeriğinin **anında hazır olup olmadığıdır**. Ödünç geçmişi sunucudan
getiriliyorsa, kendiliğinden etkinleştirme kullanıcının gezinirken üç ayrı istek başlatmasına
yol açar; ekran her ok vuruşunda değişir ve ekranı görmeyen kullanıcı için panel içeriği
odak hâlâ şeritteyken değişmiş olur. Panel içeriği belgede hazırsa kendiliğinden
etkinleştirme yeğlenir.

```js
// sekme.test.mjs — sekme kalibinin klavye sozlesmesi ve panel iliskisi
// Kosum: node --test sekme.test.mjs
import { test } from "node:test";
import assert from "node:assert/strict";

const SEKMELER = ["Künye", "Mevcut kopyalar", "Ödünç geçmişi"];

// kip: "kendiliginden" | "elle"
const baslangic = (kip) => ({ kip, secili: 0, odak: { yer: "liste", i: 0 } });

function tus(d, t) {
  const n = SEKMELER.length;
  if (d.odak.yer !== "liste") {
    // Panel icindeyken sekme listesinin tuslari calismaz
    return t === "Shift+Tab" ? { ...d, odak: { yer: "liste", i: d.secili } } : d;
  }
  const i = d.odak.i;
  const git = (y) => ({
    ...d,
    odak: { yer: "liste", i: y },
    secili: d.kip === "kendiliginden" ? y : d.secili,
  });
  if (t === "ArrowRight") return git((i + 1) % n);
  if (t === "ArrowLeft") return git((i + n - 1) % n);
  if (t === "Home") return git(0);
  if (t === "End") return git(n - 1);
  if (t === "Enter" || t === "Space") return { ...d, secili: i };
  if (t === "Tab") return { ...d, odak: { yer: "panel", i: d.secili } };
  return d;
}

const uygula = (d, dizi) => dizi.reduce(tus, d);

// Sekme listesinin kapladigi sekme duragi sayisi (donen sekme duragi)
const listeDuragi = () => 1;

// Isaretlemenin uretecegi oznitelikler
function oznitelikler(d) {
  return SEKMELER.map((ad, i) => ({
    ad,
    role: "tab",
    "aria-selected": d.secili === i,
    "aria-controls": "panel-" + i,
    tabindex: d.odak.yer === "liste" && d.odak.i === i ? 0 : -1,
    panelGizli: d.secili !== i,
  }));
}

test("sekme listesi donen sekme duragiyla tek durak tutar", () => {
  assert.equal(listeDuragi(), 1);
  const oz = oznitelikler(baslangic("elle"));
  assert.equal(oz.filter((o) => o.tabindex === 0).length, 1);
});

test("kendiliginden kipte ok tusu odagi ve secimi birlikte tasir", () => {
  const d = uygula(baslangic("kendiliginden"), ["ArrowRight"]);
  assert.equal(d.odak.i, 1);
  assert.equal(d.secili, 1);
});

test("elle kipte ok tusu yalniz odagi tasir, giris tusu secer", () => {
  const a = uygula(baslangic("elle"), ["ArrowRight", "ArrowRight"]);
  assert.equal(a.odak.i, 2);
  assert.equal(a.secili, 0);
  const b = uygula(a, ["Enter"]);
  assert.equal(b.secili, 2);
});

test("ok tuslari uclarda basa doner", () => {
  const sag = uygula(baslangic("elle"), ["ArrowLeft"]);
  assert.equal(sag.odak.i, 2);
  const sol = uygula(baslangic("elle"), ["ArrowRight", "ArrowRight", "ArrowRight"]);
  assert.equal(sol.odak.i, 0);
});

test("Home ve End ilk ve son sekmeye gider", () => {
  const d = uygula(baslangic("elle"), ["ArrowRight", "Home"]);
  assert.equal(d.odak.i, 0);
  assert.equal(uygula(baslangic("elle"), ["End"]).odak.i, 2);
});

test("Tab sekme listesinden secili panele gecer, panelde ok tusu calismaz", () => {
  const d = uygula(baslangic("elle"), ["ArrowRight", "Enter", "Tab"]);
  assert.deepEqual(d.odak, { yer: "panel", i: 1 });
  const e = uygula(d, ["ArrowRight", "Home"]);
  assert.deepEqual(e.odak, { yer: "panel", i: 1 });
  assert.equal(e.secili, 1);
});

test("her durumda tek sekme secili ve yalniz onun paneli gorunur", () => {
  const TUSLAR = ["ArrowRight", "ArrowLeft", "Home", "End", "Enter", "Tab", "Shift+Tab"];
  const anahtar = (d) => [d.kip, d.secili, d.odak.yer, d.odak.i].join("|");
  let sayi = 0;
  for (const kip of ["kendiliginden", "elle"]) {
    const b = baslangic(kip);
    const gorulen = new Map([[anahtar(b), b]]);
    const kuyruk = [b];
    while (kuyruk.length) {
      const d = kuyruk.shift();
      for (const t of TUSLAR) {
        const y = tus(d, t);
        if (gorulen.has(anahtar(y))) continue;
        gorulen.set(anahtar(y), y);
        kuyruk.push(y);
      }
    }
    for (const d of gorulen.values()) {
      const oz = oznitelikler(d);
      assert.equal(oz.filter((o) => o["aria-selected"]).length, 1, "birden cok secili sekme");
      assert.equal(oz.filter((o) => !o.panelGizli).length, 1, "birden cok gorunur panel");
      const gorunur = oz.find((o) => !o.panelGizli);
      const secili = oz.find((o) => o["aria-selected"]);
      assert.equal(gorunur.ad, secili.ad, "gorunur panel secili sekmeye ait degil");
    }
    sayi += gorulen.size;
  }
  console.log(`  taranan durum sayisi: ${sayi}`);
});
```

```
  taranan durum sayisi: 18
✔ sekme listesi donen sekme duragiyla tek durak tutar (0.35ms)
✔ kendiliginden kipte ok tusu odagi ve secimi birlikte tasir (0.080042ms)
✔ elle kipte ok tusu yalniz odagi tasir, giris tusu secer (0.045292ms)
✔ ok tuslari uclarda basa doner (0.0495ms)
✔ Home ve End ilk ve son sekmeye gider (0.304459ms)
✔ Tab sekme listesinden secili panele gecer, panelde ok tusu calismaz (0.293292ms)
✔ her durumda tek sekme secili ve yalniz onun paneli gorunur (0.408875ms)
ℹ tests 7
ℹ suites 0
ℹ pass 7
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 34.610458
```

Süre değerleri koşumdan koşuma değişir.

Son sınama on sekiz durumun tamamında üç değişmezi doğruluyor: tek sekme seçili, tek panel
görünür ve görünen panel seçili sekmeye ait. Üçüncüsü en çok kırılan koşuldur; seçim
bildirimiyle görünürlük ayrı kod yollarında güncellendiğinde ağaç bir sekmeyi seçili
gösterirken ekran başka bir paneli açık tutabilir.

Altıncı sınama, panelin içinde ok tuşlarının şeridi etkilememesini denetliyor. Bu, tek
karakterli kısayol kuralının bu kalıptaki karşılığıdır: sekme sözleşmesinin tuşları yalnız
şerit odaktayken etkindir. Panelde bir tablo varsa ok tuşları o tabloya aittir.

#### Klavye Sözleşmesi

| Tuş | Davranış |
|---|---|
| `Tab` | Odağı şeride getirir; şerit tek duraktır. Şeritteyken seçili panele geçer. |
| `Sağ ok` / `Sol ok` | Odağı komşu sekmeye taşır; uçlarda başa döner. |
| `Home` / `End` | Odağı ilk ve son sekmeye taşır. |
| `Enter` / `Boşluk` | Odaktaki sekmeyi seçer (elle etkinleştirme kipinde). |

Şerit dikey çizildiğinde yukarı ve aşağı ok tuşları da aynı işi görür ve şeride
`aria-orientation="vertical"` yazılır.

#### Ölçülebilir Kısıtlar

**4.1.2 Ad, Rol, Değer.** Her sekmenin adı, rolü ve seçili olup olmadığı program yoluyla
okunabilmelidir; `aria-selected` bildirimi seçim her değiştiğinde güncellenir.

**1.4.1 Rengin Tek Taşıyıcı Olmaması.** Etkin sekme yalnız metin rengiyle ayrılamaz; bir
alt çizgi, bir dolgu ya da bir kalınlık farkı ikinci kanaldır.

**1.4.11 Metin Dışı Kontrast.** Etkin sekmeyi gösteren çizgi, komşu yüzeyden en az 3:1 ile
ayrılmalıdır.

**2.4.7 Odak Görünürlüğü.** Odak göstergesi şeridin taşan kenarında kırpılmamalıdır; şerit
yatay kaydırma taşıyorsa odaklanan sekme görünür alana getirilmelidir.

**2.5.8 Hedef Boyutu.** Sekme başlıkları 24 piksel ölçüsüne girer; dar şeritlerde başlıklar
arası aralık istisnayı sağlayacak kadar açık olmalıdır.

**1.4.10 Yeniden Akış.** Dar ekranda şerit yatay kaydırmaya bırakılırsa, kaydırma
kullanıcının fark edebileceği bir gösterge taşımalıdır; taşmayan bir şerit için başlıkların
alt alta dizilmesi de bir çözümdür.

#### Sık Yapılan Hata

**Panelin gizlenmeden bırakılması.** Seçili olmayan paneller yalnız sunum katmanında
gizlendiğinde — ekran dışına taşınarak ya da saydamlaştırılarak — ağaçta durmayı sürdürürler.
Kullanıcı üç panelin içeriğini de arka arkaya okur. Saptaması: görünür panel sayısıyla
seçili sekme sayısının karşılaştırılması.

**Sekmelerin bağlantı olarak kurulması.** Sekme bir adres değiştirmiyorsa düğmedir. Adres
değiştiriyorsa — her bölümün paylaşılabilir bir adresi varsa — kalıp sekme değil, gezinme
listesidir ve `aria-current` ile bildirilir.

**Şeridin adsız bırakılması.** Saptaması bir varlık kuralıdır: `role="tablist"` taşıyan her
ögenin bir adı bulunmalıdır.

**İki gezinme yolunun üst üste binmesi.** `Tab` tuşunun şerit içinde ilerlediği bir
gerçekleştirimde ok tuşları da aynı işi yapar. Saptaması, şeritteki `tabindex` değerlerinin
sayılmasıdır: sıfır değerini taşıyan öge sayısı birden büyükse dönen sekme durağı
kurulmamıştır.

#### Özet

- Sekme kalıbı, eşdeğer ve birbirini dışlayan bölümler için kullanılır; sıralı ya da aynı
  anda okunması gereken bölümlerde kullanılmaz.
- Kalıbın yerel karşılığı yoktur; şerit, başlık ve panel rolleri yazılır ve bağ iki
  yönlüdür — panelin adı sekmeden gelir.
- Dönen sekme durağı zorunludur: `Tab` tuşuna tek bir anlam bırakır, ok tuşlarını şerit
  içindeki gezinmeye ayırır.
- Kendiliğinden etkinleştirme panel içeriği belgede hazırsa yeğlenir; sunucudan getirilen
  içerikte elle etkinleştirme seçilir.
- Üç değişmez her durumda korunmalıdır: tek sekme seçili, tek panel görünür ve görünen panel
  seçili sekmeye ait.
- Sekme sözleşmesinin tuşları yalnız şerit odaktayken etkindir; panel içindeki tablo ya da
  liste kendi ok tuşu davranışını sürdürür.

#### Sonraki Adım

Sekme kalıbı bir kısıt taşıyordu: aynı anda yalnız bir bölüm görünür. Kayıt ayrıntısı
sayfasının alt kısmındaki "Baskılar", "Konu başlıkları" ve "Benzer kayıtlar" bölümleri için
bu kısıt gereksizdir — kullanıcı üçünü birden açık tutmak isteyebilir, hiçbirini açmadan da
sayfayı okuyabilir. Bu, aynı gruplama sorununun kısıtsız çözümüdür ve tek bir durum
bildirimine dayanır: bölüm şu anda genişletilmiş mi? Sonraki ders akordeonu belirtir ve o
tek bildirimin güncellenmemesinin neden en sık üretilen kusur olduğunu gösterir.

### Akordeon

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/akordeon

Aşamalı gösterimin kalıbı, yerel açılıp kapanan panel ögesiyle elle kurulan akordeonun karşılaştırılması, genişleme durumunun izlenmesi ve yapısal kural denetimi.

Sekme kalıbı bir kısıt taşıyordu: aynı anda yalnız bir bölüm görünür. Kayıt ayrıntısı
sayfasının alt kısmındaki "Baskılar", "Konu başlıkları" ve "Benzer kayıtlar" bölümleri için
bu kısıt gereksizdir. Kullanıcı üçünü birden açık tutmak isteyebilir; hiçbirini açmadan da
sayfayı okuyup ayrılabilir.

Kısıtsız çözüm akordeondur ve tek bir durum bildirimine dayanır: bölüm şu anda genişletilmiş
mi? Bu ders o tek bildirimi belirtir ve güncellenmemesinin neden en sık üretilen kusur
olduğunu gösterir.

#### Kalıbın Çözdüğü Sorun

Akordeon, **ikincil içeriğin** sayfayı uzatmasını engeller. Ölçüt ikincillıktir: kullanıcıların
çoğu o bölümü açmadan işini tamamlıyorsa bölüm katlanabilir.

Ölçüt sağlanmadığında kalıp zarar verir. Kaydın raf konumu, ödünç alınabilirlik durumu ve
iade tarihi zorunlu bilgilerdir; katlandıklarında kullanıcı bir tuş vuruşu daha yapmak
zorunda kalır ve bilginin var olduğunu göremez.

İki ek sınır vardır. Kapalı bir bölümün içeriği belgede gizlidir; kullanıcının sayfa içinde
arayarak bulmasını gerektiren bilgi akordeona konmaz. İkincisi, iki ya da üç kısa
paragraflık içeriği katlamak kazanç sağlamaz — açma eylemi, kazanılan yerden daha pahalıdır.

#### Yerel Öge Önce

Bu kalıbın yerel karşılığı **vardır**: `details` ve `summary` ögeleri. `summary` ögesi
açma denetimidir, `details` ögesi kapsayıcıdır ve açık olup olmadığı `open` özniteliğinden
okunur.

Getirdikleri şunlardır: açma ve kapama davranışı, denetimin odaklanabilirliği, giriş ve
boşluk tuşlarıyla çalıştırılabilirlik ve genişleme durumunun ağaca bildirimi. Hiçbiri
ayrıca yazılmaz. `details` ögelerine ortak bir ad verildiğinde aynı anda yalnız birinin
açık kalması da tanımlıdır; bu davranış gerekiyorsa yetenek saptamasıyla sınanır ve
karşılanmadığında elle kurulur.

Bedeli iki yerdedir: açma imleyicisinin görünümünün sunum katmanından denetlenmesi
sınırlıdır ve açılma hareketine geçiş uygulanması kolay değildir. Katalogun kayıt ayrıntısı
sayfasında bu bedel ödenebilir.

#### Elle Kurulan Akordeon

Yerel ögenin karşılamadığı bir gereksinim varsa kalıp bir düğmeyle kurulur.

```html
<h3>
  <button aria-expanded="true" aria-controls="panel-baski" id="baslik-baski">
    Baskılar
  </button>
</h3>
<div id="panel-baski" role="region" aria-labelledby="baslik-baski">…</div>

<h3>
  <button aria-expanded="false" aria-controls="panel-konu" id="baslik-konu">
    Konu başlıkları
  </button>
</h3>
<div id="panel-konu" role="region" aria-labelledby="baslik-konu" hidden>…</div>
```

Üç karar bağlayıcıdır. Düğme bir **başlık ögesinin içindedir**; başlık düğmenin içine değil,
düğme başlığın içine yazılır. Böylece bölüm adı ana hatta girer ve kullanıcı başlık
listesinden bölümler arasında gezinebilir. Başlık düzeyi, çevresindeki ana hatta göre
seçilir; akordeonun kendisi bir düzey dayatmaz.

`aria-expanded` durumu düğmeye yazılır, panele değil: genişleten şey düğmedir. Panelin
`role="region"` bildirimi ve `aria-labelledby` bağı, bölümün yer imi listesinde
bulunabilmesini sağlar; bölüm sayısı çoksa bu liste kalabalıklaşır ve bildirim
uygulanmayabilir.

Açma imleyicisi olarak kullanılan ok simgesi sunumsaldır; durumu simge değil,
`aria-expanded` bildirir.

#### Genişleme Durumunun İzlenmesi

```js
// akordeon.mjs — genisleme durumunun izlenmesi ve yapisal kural denetimi

const BOLUMLER = ["Baskılar", "Konu başlıkları", "Benzer kayıtlar"];

// kip: "coklu" (bagimsiz) | "tekli" (ayni anda bir bolum)
const baslangic = (kip) => ({ kip, acik: new Set() });

function calistir(d, i) {
  const a = new Set(d.acik);
  if (a.has(i)) a.delete(i);
  else if (d.kip === "tekli") { a.clear(); a.add(i); }
  else a.add(i);
  return { ...d, acik: a };
}

// Isaretlemenin uretecegi durum: dugmede aria-expanded, panelde hidden
const uretilen = (d) =>
  BOLUMLER.map((ad, i) => ({ ad, "aria-expanded": d.acik.has(i), panelGorunur: d.acik.has(i) }));

function iz(kip, dizi) {
  let d = baslangic(kip);
  console.log(`\n${kip} kip:`);
  console.log("  calistirilan".padEnd(22) + "aria-expanded".padEnd(24) + "gorunur panel");
  console.log("  " + "(baslangic)".padEnd(20) +
    uretilen(d).map((o) => (o["aria-expanded"] ? "1" : "0")).join(" ").padEnd(24) +
    uretilen(d).filter((o) => o.panelGorunur).length);
  for (const i of dizi) {
    d = calistir(d, i);
    const u = uretilen(d);
    console.log("  " + BOLUMLER[i].padEnd(20) +
      u.map((o) => (o["aria-expanded"] ? "1" : "0")).join(" ").padEnd(24) +
      u.filter((o) => o.panelGorunur).length);
  }
  return d;
}

iz("coklu", [0, 1, 2, 1]);
const tekli = iz("tekli", [0, 1, 2, 2]);

// Degismez: bildirilen durum ile gorunurluk her zaman ortusur; tekli kipte en cok bir panel
console.log("\ndegismez taramasi:");
let durum = 0, kirilma = 0;
for (const kip of ["coklu", "tekli"]) {
  const gorulen = new Set();
  const kuyruk = [baslangic(kip)];
  const anahtar = (d) => [...d.acik].sort().join(",");
  gorulen.add(anahtar(kuyruk[0]));
  while (kuyruk.length) {
    const d = kuyruk.shift();
    durum++;
    const u = uretilen(d);
    if (u.some((o) => o["aria-expanded"] !== o.panelGorunur)) kirilma++;
    if (kip === "tekli" && u.filter((o) => o.panelGorunur).length > 1) kirilma++;
    for (let i = 0; i < BOLUMLER.length; i++) {
      const y = calistir(d, i);
      if (gorulen.has(anahtar(y))) continue;
      gorulen.add(anahtar(y));
      kuyruk.push(y);
    }
  }
}
console.log(`  taranan durum: ${durum}, degismez kirilmasi: ${kirilma}`);
console.log(`  tekli kipte ulasilan durum sayisi: ${BOLUMLER.length + 1}`);

// --- Yapisal kural denetimi -------------------------------------------------
// Her kayit: baslik dugmesinin sarildigi oge, aria-controls hedefi, panel gizliligi
const ISARETLEME = [
  { ad: "Baskılar", sarmalayan: "h3", controls: "panel-baski",
    genisletilmis: true, panelGizli: false, paneller: ["panel-baski"] },
  { ad: "Konu başlıkları", sarmalayan: "div", controls: "panel-konu",
    genisletilmis: false, panelGizli: true, paneller: ["panel-konu"] },
  { ad: "Benzer kayıtlar", sarmalayan: "h3", controls: "panel-benzer-2",
    genisletilmis: true, panelGizli: true, paneller: ["panel-benzer"] },
];
const BASLIK_OGELERI = new Set(["h1", "h2", "h3", "h4", "h5", "h6"]);
const VAR_OLAN = new Set(["panel-baski", "panel-konu", "panel-benzer"]);

console.log("\nyapisal kural denetimi:");
let bulgu = 0;
for (const k of ISARETLEME) {
  const b = [];
  if (!BASLIK_OGELERI.has(k.sarmalayan))
    b.push(`baslik dugmesi bir baslik ogesi icinde degil (${k.sarmalayan})`);
  if (!VAR_OLAN.has(k.controls))
    b.push(`aria-controls var olmayan bir kimlige isaret ediyor (${k.controls})`);
  if (k.genisletilmis === k.panelGizli)
    b.push(`aria-expanded=${k.genisletilmis} ama panel ${k.panelGizli ? "gizli" : "gorunur"}`);
  for (const x of b) { bulgu++; console.log("  " + k.ad.padEnd(20) + x); }
}
console.log(`\n${ISARETLEME.length} bolumde ${bulgu} bulgu`);
```

```
coklu kip:
  calistirilan        aria-expanded           gorunur panel
  (baslangic)         0 0 0                   0
  Baskılar            1 0 0                   1
  Konu başlıkları     1 1 0                   2
  Benzer kayıtlar     1 1 1                   3
  Konu başlıkları     1 0 1                   2

tekli kip:
  calistirilan        aria-expanded           gorunur panel
  (baslangic)         0 0 0                   0
  Baskılar            1 0 0                   1
  Konu başlıkları     0 1 0                   1
  Benzer kayıtlar     0 0 1                   1
  Benzer kayıtlar     0 0 0                   0

degismez taramasi:
  taranan durum: 12, degismez kirilmasi: 0
  tekli kipte ulasilan durum sayisi: 4

yapisal kural denetimi:
  Konu başlıkları     baslik dugmesi bir baslik ogesi icinde degil (div)
  Benzer kayıtlar     aria-controls var olmayan bir kimlige isaret ediyor (panel-benzer-2)
  Benzer kayıtlar     aria-expanded=true ama panel gizli

3 bolumde 3 bulgu
```

#### Bulguların Okunması

İki izin karşılaştırılması, iki kipin **durum uzayı** farkını gösteriyor. Çoklu kipte üç
bölüm sekiz duruma ulaşır: her bölüm bağımsız olarak açık ya da kapalıdır. Tekli kipte
ulaşılan durum sayısı dörttür — üç bölüm ya da hiçbiri. Tarama toplamı on iki, ikisinin
toplamıdır.

Tekli kipin izindeki üçüncü satır, kalıbın sık atlanan sonucunu gösteriyor: kullanıcı ikinci
bölümü açtığında birincisi **kendiliğinden kapanıyor**. Ekranda bu, sayfanın kaydığı ve
okunan yerin kaybolduğu anlamına gelir. Kapanma bir duyuru üretmez; kullanıcı yalnız
açtığını duyar. Bu yüzden tekli kip, yalnız bölüm sayısı çok ve içerikler uzun olduğunda
seçilir.

Yapısal denetim üç bulgu üretiyor ve üçü de ekranda görünmez.

**Başlık ögesi eksikliği.** İkinci bölümün açma düğmesi bir `div` içinde. Ekranda hiçbir
fark yoktur; ana hatta ise o bölüm bulunmaz ve başlık listesinden gezinen kullanıcı üç
bölümden yalnız ikisini görür.

**Kopuk denetim başvurusu.** Üçüncü bölümün `aria-controls` değeri var olmayan bir kimliğe
işaret ediyor; bildirim sessizce düşer.

**Bildirim ile görünürlüğün ayrışması.** Aynı bölüm genişletilmiş olarak bildiriliyor ama
paneli gizli. Bu, kalıbın en pahalı kusurudur: kullanıcıya bölümün açık olduğu söylenir,
açıldığında hiçbir içerik bulunmaz ve kullanıcı hatanın kendisinde olduğunu düşünür.

#### Klavye Sözleşmesi

| Tuş | Davranış |
|---|---|
| `Tab` | Her başlık düğmesi ayrı bir sekme durağıdır. |
| `Enter` / `Boşluk` | Bölümü açar ya da kapatır. |
| Ok tuşları | İsteğe bağlıdır; kurulursa başlıklar arasında gezinir, bölüm açmaz. |

Sekme kalıbından ayrıldığı nokta buradadır. Sekme şeridi tek bir seçim taşıdığı için dönen
sekme durağı kullanır; akordeonun başlıkları ise bağımsız düğmelerdir ve her biri kendi
durağını tutar. Dönen sekme durağı burada uygulanırsa kullanıcı `Tab` tuşuyla ikinci bölüme
hiç ulaşamaz.

Bir bölüm açıldığında odak **düğmede kalır**. Panele taşınmaz: kullanıcı açtığı bölümü
okumadan bir sonrakini açmak isteyebilir ve odağın kaçması bunu engeller. Panelin içine
girmek `Tab` tuşuyla yapılır.

#### Ölçülebilir Kısıtlar

**4.1.2 Ad, Rol, Değer.** Genişleme durumu program yoluyla okunabilmeli ve her değişimde
güncellenmelidir.

**2.5.8 Hedef Boyutu.** Başlık satırının tamamı tıklanabilir alandır; yalnızca ok simgesinin
tıklanabilir olduğu bir gerçekleştirim hem hedefi 24 pikselin altına düşürür hem de
kullanıcının başlık metnine tıklama beklentisini karşılamaz.

**1.4.11 Metin Dışı Kontrast.** Bölümleri birbirinden ayıran çizgi bilgi taşır ve 3:1
ölçüsüne girer.

**2.4.6 Başlıklar ve Etiketler.** Başlık metni bölümün içeriğini tanımlamalıdır; "Ayrıntı"
ya da "Diğer" gibi metinler bu ölçütü karşılamaz.

**2.3.3 Etkileşimle Tetiklenen Hareket.** Açılma hareketine geçiş uygulanıyorsa, hareket
azaltma tercihi bildirildiğinde geçiş kaldırılmalıdır; ölçüt AAA düzeyindedir, ancak uzun
listelerde art arda açılan bölümlerin hareketi rahatsızlık üretir.

#### Sık Yapılan Hata

**Durum bildiriminin panele yazılması.** `aria-expanded` özniteliği paneli genişleten
denetime yazılır. Panele yazıldığında düğme durumsuz kalır ve kullanıcı düğmeye
odaklandığında bölümün açık mı kapalı mı olduğunu öğrenemez. Saptaması: `role` değeri düğme
olan her ögede bildirimin bulunup bulunmadığının denetlenmesi.

**Başlık düğmesinin bağlantı olarak kurulması.** Bölüm açmak bir eylemdir ve adres
değiştirmez; bileşen düğmedir.

**Kapalı panelin ağaçta bırakılması.** Panel yalnız sunum katmanında gizlendiğinde ağaçta
kalır; kullanıcı kapalı bölümlerin içeriğini de okur ve durum bildirimiyle çelişir.
Saptaması, denetimin üçüncü kuralıdır.

**Bütün bölümlerin açık başlatılması.** Aşamalı gösterim, sayfayı kısaltmak içindir; hepsi
açık başlayan bir akordeon yalnız fazladan bir tuş katmanı ekler. Karar: sayfa yüklendiğinde
hangi bölümün açık olacağı belirtimde yazılır.

#### Özet

- Akordeon ikincil içeriği katlar; zorunlu bilgi, sayfa içinde aranması gereken içerik ve
  çok kısa bölümler katlanmaz.
- Kalıbın yerel karşılığı vardır ve açma davranışını, odaklanabilirliği, tuş sözleşmesini ve
  durum bildirimini birlikte getirir.
- Elle kurulduğunda düğme bir başlık ögesinin içine yazılır; başlık düğmenin içine değil.
  Bu, bölümü ana hatta sokar.
- `aria-expanded` bildirimi paneli genişleten düğmeye yazılır ve görünürlükle her durumda
  örtüşmelidir.
- Akordeonun her başlığı ayrı bir sekme durağıdır; dönen sekme durağı yalnız sekme
  kalıbına aittir.
- Tekli kipte bir bölümün açılması diğerini sessizce kapatır ve durum uzayını sekizden
  dörde indirir; bu davranış yalnız uzun içerikli çok bölümlü listelerde seçilir.

#### Sonraki Adım

Buraya kadarki üç kalıpta açılan içerik sayfanın akışı içinde kaldı: panel bir yer kapladı,
altındaki içeriği aşağı itti ve odak kendi başına ilerledi. Katalogun üst çubuğundaki
"Hesabım" denetimi ise farklı davranır — açtığı liste sayfanın **üstünde** durur, altındaki
içeriği örter ve dışına tıklandığında kapanır. Bu, odağın kuşatılmasını ve kapanışta bir
dönüş noktasına götürülmesini gerektiren ilk kalıptır. Sonraki ders menüleri ve açılır
panelleri belirtir; ikisinin ayrımını ve odak tuzağının hangi noktada oluştuğunu ele alır.

### Menüler ve Açılır Paneller

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/menuler-ve-acilir-paneller

Menü rolüyle açılır panelin ayrımı, üç kurulumun odak kaçağı ve kapanma davranışı bakımından taranması, odak dönüş noktasının ve aç-kapa döngüsünün doğrulanması.

Sekme ve akordeon kalıplarında açılan içerik sayfanın akışı içinde kaldı: bir yer kapladı,
altındaki içeriği aşağı itti ve odak kendi başına ilerledi. Katalogun üst çubuğundaki
"Hesabım" denetimi farklı davranır — açtığı liste sayfanın üstünde durur, altındaki içeriği
örter ve dışına tıklandığında kapanır.

Bu, kapanmanın bir davranış olarak belirtilmesi gereken ilk kalıptır. Kapanma birden çok
yolla olur ve her yolun ardından odağın nereye gideceği ayrı bir karardır. Ders iki kalıbı
birlikte ele alır, çünkü ekranda birbirine benzemelerine karşın sözleşmeleri ayrıdır.

#### İki Kalıp, İki Sözleşme

**Açılır panel**, bir düğmenin gösterip gizlediği sıradan içeriktir. İçinde bağlantı,
düğme, alan, hatta bir form bulunabilir. Akordeonla aynı bildirimi kullanır —
`aria-expanded` — ve tek farkı içeriğin sayfanın üstünde durmasıdır.

**Menü**, uygulama benzeri komut listeleridir ve kendi rol kümesiyle kurulur: `role="menu"`,
`role="menuitem"`, `role="menuitemcheckbox"`. Bu rollerin bir sözleşmesi vardır ve
sözleşme kısıtlayıcıdır: menü içinde yalnız menü ögeleri bulunur, gezinme ok tuşlarıyla
yapılır ve menünün tamamı tek bir sekme durağıdır.

Karar ölçütü içeriktir. "Hesabım" denetiminin açtığı liste — "Ödünçlerim", "Ayarlar",
"Çıkış" — sıradan bağlantılardan oluşur ve bir gezinme listesidir; kalıbı **açılır paneldir**.
Menü rolü, sonuç tablosundaki "Dışa aktar" düğmesinin açtığı komut listesi gibi, ögeleri
gezinme değil eylem olan listelerde kullanılır.

Yanlış seçim sessiz bir maliyet üretir. Bağlantılardan oluşan bir listeye menü rolü
yazıldığında bağlantılar ağaçta menü ögesi olarak bildirilir; kullanıcının bağlantı
listesinden onlara ulaşması olanaksızlaşır ve "yeni sekmede aç" beklentisi karşılanmaz.

#### Yerel Öge Önce

İkisinin de yerel karşılığı yoktur. Açan denetim `button` ögesidir; açılır panelde `aria-expanded`
ve `aria-controls` yazılır; menüde ayrıca `aria-haspopup="menu"` bildirilir.

```html
<button id="hesabim" aria-expanded="false" aria-controls="hesap-paneli">Hesabım</button>
<div id="hesap-paneli" hidden>
  <ul>
    <li><a href="/hesap/oduncler">Ödünçlerim</a></li>
    <li><a href="/hesap/ayarlar">Ayarlar</a></li>
  </ul>
  <form action="/cikis" method="post"><button type="submit">Çıkış</button></form>
</div>
```

Panelin içeriği sıradan işaretlemedir; liste liste, form form olarak kalır. Bu, kalıbın
menüye yeğlenmesinin gerekçesidir: hiçbir yapı anlamı elle yeniden üretilmez.

#### Kapanma Yolları ve Odak Dönüşü

Kapanma dört yolla olur: açan düğmenin yeniden çalıştırılması, `Escape` tuşu, panelin
dışına tıklanması ve panel içinde bir ögenin seçilmesi.

Odağın nereye gideceği kapanma yoluna göre değişir ve kural şudur: **kullanıcının başlattığı
kapanışta odak açan düğmeye döner.** `Escape` tuşuyla ve düğmenin yeniden çalıştırılmasıyla
kapanışta bu geçerlidir. Panel içindeki bir bağlantı seçildiğinde ise gezinme olur ve odak
yeni sayfaya gider; dönüş gereksizdir.

Dışarı tıklama özel bir durumdur. Tıklayan kullanıcı odağı zaten tıkladığı yere taşımıştır;
odağı açan düğmeye döndürmek onu tıkladığı yerden geri çeker. Kural: dışarı tıklamada odak
yalnız **hiçbir yere yerleşmediyse** açan düğmeye taşınır.

```js
// menu.mjs — acilir panelde odak kusatmasi, kapanma ve odak donus noktasinin denetimi

const ARKA = ["a#katalog", "button#hesabim", "input#ara"];
const ICERIK = ["a#odunclerim", "a#ayarlar", "button#cikis"];
const TETIK = "button#hesabim";
const TUSLAR = ["Tab", "Shift+Tab", "ArrowDown", "ArrowUp", "Escape", "Enter", "DisariTik"];

// tur: "panel" (acilir panel) | "menu" (menu rolu) | "kusurlu"
function makine(tur) {
  const arkaGit = (i, yon) => ARKA[(i + yon + ARKA.length) % ARKA.length];
  return (d, t) => {
    if (!d.acik) {
      const i = ARKA.indexOf(d.odak);
      if (t === "Tab") return { acik: false, odak: arkaGit(i, 1) };
      if (t === "Shift+Tab") return { acik: false, odak: arkaGit(i, -1) };
      if ((t === "Enter" || t === "ArrowDown") && d.odak === TETIK)
        return { acik: true, odak: ICERIK[0] };
      return d;
    }
    const j = ICERIK.indexOf(d.odak);
    if (tur === "menu") {
      if (t === "ArrowDown") return { acik: true, odak: ICERIK[(j + 1) % ICERIK.length] };
      if (t === "ArrowUp")
        return { acik: true, odak: ICERIK[(j + ICERIK.length - 1) % ICERIK.length] };
      if (t === "Tab") return { acik: false, odak: arkaGit(ARKA.indexOf(TETIK), 1) };
      if (t === "Shift+Tab") return { acik: false, odak: TETIK };
      if (t === "Escape") return { acik: false, odak: TETIK };
      if (t === "DisariTik") return { acik: false, odak: TETIK };
      return d;
    }
    if (tur === "panel") {
      if (t === "Tab")
        return j === ICERIK.length - 1
          ? { acik: false, odak: arkaGit(ARKA.indexOf(TETIK), 1) }
          : { acik: true, odak: ICERIK[j + 1] };
      if (t === "Shift+Tab")
        return j === 0 ? { acik: true, odak: TETIK } : { acik: true, odak: ICERIK[j - 1] };
      if (t === "Escape") return { acik: false, odak: TETIK };
      if (t === "DisariTik") return { acik: false, odak: TETIK };
      return d;
    }
    // kusurlu: escape yok, disari tikta odak kayboluyor, Tab arka plana kaciyor
    if (t === "Tab")
      return j === ICERIK.length - 1
        ? { acik: true, odak: ARKA[0] }
        : { acik: true, odak: ICERIK[j + 1] };
    if (t === "Shift+Tab") return { acik: true, odak: ICERIK[Math.max(j - 1, 0)] };
    if (t === "DisariTik") return { acik: false, odak: "(belge koku)" };
    return d;
  };
}

const anahtar = (d) => (d.acik ? "acik" : "kapali") + "|" + d.odak;

function denetle(tur) {
  const sonraki = makine(tur);
  const bas = { acik: false, odak: TETIK };
  const gorulen = new Map([[anahtar(bas), bas]]);
  const kuyruk = [bas];
  const kapanislar = [];       // [oncekiDurum, tus, sonrakiDurum]
  while (kuyruk.length) {
    const d = kuyruk.shift();
    for (const t of TUSLAR) {
      const y = sonraki(d, t);
      if (d.acik && !y.acik) kapanislar.push([d, t, y]);
      if (gorulen.has(anahtar(y))) continue;
      gorulen.set(anahtar(y), y);
      kuyruk.push(y);
    }
  }
  const durumlar = [...gorulen.values()];
  const escapeIle = kapanislar.filter(([, t]) => t === "Escape");
  return {
    durum: durumlar.length,
    kacak: durumlar.some((d) => d.acik && ARKA.includes(d.odak) && d.odak !== TETIK),
    escapeCikisi: escapeIle.length > 0,
    escapeDonusu: escapeIle.length > 0 && escapeIle.every(([, , y]) => y.odak === TETIK),
    odakKaybi: kapanislar.some(([, , y]) => !ARKA.includes(y.odak) && y.odak !== TETIK),
    dongu: anahtar(sonraki(sonraki(bas, "Enter"), "Escape")) === anahtar(bas),
    icDurak: tur === "menu" ? 1 : ICERIK.length,
  };
}

const im = (b) => (b ? "var" : "yok");
console.log("kurulum".padEnd(10) + "durum".padEnd(8) + "ic durak".padEnd(10) +
  "arka plana kacak".padEnd(18) + "Escape cikisi".padEnd(15) + "tetige donus".padEnd(14) +
  "odak kaybi".padEnd(12) + "ac-kapa dongusu");
for (const tur of ["panel", "menu", "kusurlu"]) {
  const s = denetle(tur);
  console.log(tur.padEnd(10) + String(s.durum).padStart(5).padEnd(8) +
    String(s.icDurak).padStart(4).padEnd(10) +
    im(s.kacak).padEnd(18) + im(s.escapeCikisi).padEnd(15) +
    im(s.escapeDonusu).padEnd(14) + im(s.odakKaybi).padEnd(12) +
    (s.dongu ? "kapali" : "ACIK"));
}

// Dogru kurulumda odak izi
console.log("\nacilir panel kurulumunda odak izi:");
let d = { acik: false, odak: TETIK };
const sonraki = makine("panel");
console.log("  " + "baslangic".padEnd(12) + "kapali".padEnd(9) + "odak: " + d.odak);
for (const t of ["Enter", "Tab", "Tab", "Escape", "Tab"]) {
  d = sonraki(d, t);
  console.log("  " + t.padEnd(12) + (d.acik ? "acik" : "kapali").padEnd(9) + "odak: " + d.odak);
}
```

```
kurulum   durum   ic durak  arka plana kacak  Escape cikisi  tetige donus  odak kaybi  ac-kapa dongusu
panel         8      3      yok               var            var           yok         kapali
menu          6      1      yok               var            var           yok         kapali
kusurlu       8      3      var               yok            yok           var         ACIK

acilir panel kurulumunda odak izi:
  baslangic   kapali   odak: button#hesabim
  Enter       acik     odak: a#odunclerim
  Tab         acik     odak: a#ayarlar
  Tab         acik     odak: button#cikis
  Escape      kapali   odak: button#hesabim
  Tab         kapali   odak: input#ara
```

#### Bulguların Okunması

**İç durak sütunu iki kalıbı ayırıyor.** Açılır panelde içerik doğal sekme sırasındadır ve üç
durak tutar; menüde dönen sekme durağı vardır ve tamamı tek durak sayılır. Bu, işaretlemeye
bakmadan hangi kalıbın kurulduğunu söyleyen ölçüdür.

**Aç-kapa döngüsü kapalı olmalıdır.** Son sütun, düğmeye odaklanmış bir kullanıcının paneli
açıp `Escape` ile kapattığında **birebir aynı duruma** dönüp dönmediğini sınıyor. Doğru iki
kurulumda döngü kapalı; kusurlu kurulumda açık, çünkü `Escape` tuşu hiçbir şey yapmıyor.
Döngünün kapalı olması, kullanıcının paneli yanlışlıkla açtığında kaybettiği hiçbir şey
olmadığı anlamına gelir.

**Kusurlu kurulum üç kusuru birden taşıyor.** Panel açıkken `Tab` tuşu arka plandaki
denetimlere kaçıyor: kullanıcı görmediği, panelin altında kalan ögelere odaklanır.
`Escape` tuşuyla çıkış yolu yok — bu, 2.1.2 ölçütünün konusudur, ancak burada tam bir
tuzak değildir, çünkü `Tab` ile kaçış vardır. Dışarı tıklamada odak belgenin köküne
düşüyor ve kullanıcı dolaşıma en baştan başlıyor.

**Menü altı, panel sekiz duruma ulaşıyor.** Fark, panelin açıkken açan düğmeye geri
dönebilmesinden gelir: `Shift+Tab` tuşuyla ilk ögeden düğmeye çıkılır ve panel açık kalır.
Menüde aynı tuş menüyü kapatır. İkisi de tanımlıdır; belirtimde hangisinin seçildiği
yazılır.

#### Klavye Sözleşmesi

Açılır panel:

| Tuş | Davranış |
|---|---|
| `Enter` / `Boşluk` (düğmede) | Paneli açar; odak ilk ögeye taşınır. |
| `Aşağı ok` (düğmede) | Paneli açar ve odağı ilk ögeye taşır. |
| `Tab` | Panel ögeleri arasında ilerler; son ögeden sonra paneli kapatır. |
| `Escape` | Paneli kapatır; odak açan düğmeye döner. |

Menü:

| Tuş | Davranış |
|---|---|
| `Enter` / `Boşluk` / `Aşağı ok` (düğmede) | Menüyü açar; odak ilk ögeye taşınır. |
| `Aşağı ok` / `Yukarı ok` | Ögeler arasında gezer; uçlarda başa döner. |
| `Home` / `End` | İlk ve son ögeye gider. |
| `Escape` | Menüyü kapatır; odak açan düğmeye döner. |
| `Tab` | Menüyü kapatır ve odağı sonraki sayfa durağına taşır. |

Menü kalıbında `Tab` tuşunun menüyü kapatması bir kısıt değil, tanımın parçasıdır: menü
tek durak olduğu için içinde ilerleyecek bir sekme sırası yoktur.

#### Ölçülebilir Kısıtlar

**2.1.2 Klavye Tuzağı Yok.** Panel açıkken klavyeyle çıkılabilmelidir. `Escape` tuşu bu
çıkışın en kısa yoludur ve her iki kalıpta da zorunlu sayılır.

**1.4.13 Üzerine Gelme veya Odaklanmayla Beliren İçerik.** Panel imleçle açılıyorsa üç koşul
gerekir: imleç ya da odak taşınmadan bir düzenekle (kalıpların çoğunda `Escape`) kapatılabilmeli,
imleç panelin üzerine götürülebilmeli ve kullanıcı bir eylem yapmadan kaybolmamalıdır. Üzerine
gelindiğinde açılıp imleç kaydığında kapanan menüler bu ölçütü kaldırır; ayrıca dokunmatik girdide
üzerine gelme durumu hiç oluşmadığı için kalıp tıklamayla da açılabilmelidir.

**2.4.7 Odak Görünürlüğü.** Panel içindeki ögelerin odak göstergesi, panelin kendi yüzeyi
üzerinde de yeterli karşıtlık taşımalıdır; sayfa yüzeyine göre ayarlanmış tek renkli bir
halka panel yüzeyinde kaybolabilir.

**1.4.11 Metin Dışı Kontrast.** Panelin sınırı, altındaki içerikle karışmayacak biçimde 3:1
ölçüsünü karşılamalıdır; panel yalnız gölgeyle ayrılıyorsa sınır görünmez.

**2.5.8 Hedef Boyutu.** Panel ögeleri 24 piksel ölçüsüne girer; dar açılır listelerde satır
yüksekliği bu ölçüyü belirler.

#### Sık Yapılan Hata

**Bağlantı listesine menü rolü yazılması.** Saptaması bir yapı denetimidir: `role="menu"`
taşıyan ögenin altında menü ögesi olmayan bir çocuk varsa kalıp yanlış kurulmuştur.

**Kapanışta odağın bırakılması.** Panel kapandığında odak silinen bir ögenin üzerindeyse
belgenin köküne düşer. Saptaması, denetimdeki döngü sınamasıdır: aç-kapa dizisi başlangıç
durumuna dönmüyorsa odak yönetimi eksiktir.

**`Escape` tuşunun yalnız panele bağlanması.** Kullanıcı odağı açan düğmeye geri aldığında
`Escape` tuşu çalışmıyorsa, kapanmanın bir yolu eksilir. Tuş, düğme ve panelin ortak
atasına bağlanır.

**Panelin ağaçtan gizlenmemesi.** Sunum katmanında gizlenen bir panel ağaçta kalır ve
kullanıcı, kapalı olduğunu sandığı listenin ögelerini okur.

#### Özet

- Açılır panel sıradan içeriği gösterip gizler; menü, ögeleri komut olan listeler içindir ve
  kısıtlayıcı bir rol sözleşmesi taşır.
- Bağlantılardan oluşan bir listeye menü rolü yazmak, bağlantıları ağaçta menü ögesine
  çevirir ve bağlantı listesinden ulaşılmasını engeller.
- Açılır panelin içeriği doğal sekme sırasındadır; menü dönen sekme durağı kullanır ve tek
  durak tutar. Bu ölçü iki kalıbı ayırt eder.
- Kullanıcının başlattığı her kapanışta odak açan düğmeye döner; panel içinden gezinilerek
  yapılan kapanışta dönüş gerekmez.
- Aç-kapa döngüsü kapalı olmalıdır: düğmede başlayan bir aç–kapat dizisi birebir aynı duruma
  dönmelidir.
- Panel açıkken arka plandaki denetimlere odak kaçmamalı ve `Escape` her koşulda bir çıkış
  yolu bırakmalıdır.

#### Sonraki Adım

Buraya kadarki kapsayıcılar içeriği gösterip gizledi; hiçbiri kullanıcıya **nerede
olduğunu** söylemedi. Katalogda ise konum bir bilgidir: kullanıcı üç düzey aşağıda bir konu
dalındadır ve sonuç listesinin dördüncü sayfasındadır. İki bileşen bu bilgiyi taşır ve
ikisi de aynı bildirime dayanır — bulunulan yerin diğerlerinden ayrılması. Sonraki ders
kırıntı yolunu ve sayfalamayı belirtir; konumun nasıl bildirileceğini ve sayfa değişiminin
neden bir odak kararı gerektirdiğini ele alır.

### Kırıntı Yolu ve Sayfalama

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/kirinti-yolu-ve-sayfalama

Hiyerarşideki ve sıradaki konumun işaretlemede bildirilmesi, bağlantı adının hedefi taşıması, bulunulan sayfanın bağlantı olarak korunması ve sayfa değişiminde odak kararı.

Önceki dersteki kapsayıcılar içeriği gösterip gizledi; hiçbiri kullanıcıya nerede
olduğunu söylemedi. Katalogda konum bir bilgidir: kullanıcı üç düzey aşağıda bir konu
dalındadır ve sonuç listesinin dördüncü sayfasındadır. Bu iki bilgiyi taşıyan bileşenler
kırıntı yolu ile sayfalamadır ve ikisi de aynı işi yapar — bulunulan yeri diğerlerinden
ayırmak.

İkisinin ortak zorluğu da aynıdır. Görsel olarak bulunulan yer bir renk ya da kalınlık
farkıyla belirtilir; bu fark ekranı görmeyen kullanıcıya hiçbir şey bildirmez. Ayrım
işaretlemede yazılmadığında bileşen, benzer bağlantılardan oluşan bir listeye dönüşür.

#### Kalıbın Çözdüğü Sorun

Kırıntı yolu **hiyerarşideki** konumu, sayfalama **sıradaki** konumu bildirir. Kırıntı yolu
aynı zamanda bir yukarı çıkış yoludur: kullanıcı üç düzey yukarıya tek adımda döner.
Sayfalama ise bir kümenin parçalara bölündüğünü ve hangi parçanın görüldüğünü söyler.

Hiçbiri gezinmenin tek yolu değildir. Kırıntı yolu ana gezinmenin yerine geçmez; sayfalama
sonsuz kaydırmanın yerine geçmez. İkisi de bir **ek bilgi** katmanıdır ve bu yüzden ikisi de
kendi gezinme bölgesinde durur.

#### Yerel Öge Önce

Her iki bileşen de yerel ögelerle kurulur: bir gezinme bölgesi, içinde bir liste, listede
bağlantılar. Kırıntı yolunda liste **sıralıdır**, çünkü öğelerin sırası anlam taşır.
Sayfalamada sıra da anlamlıdır ama öğeler eşit düzeydedir.

Sayfada birden çok gezinme bölgesi varsa her birinin adı olmalıdır; adsız iki bölge
birbirinden ayrılamaz. Ad, bölgenin ne için olduğunu söyler: "Kırıntı yolu", "Sonuç
sayfaları". Bulunulan yer ise `aria-current` ile bildirilir — kırıntı yolunda son öge,
sayfalamada bulunulan sayfa numarası.

Aşağıdaki hesap iki bileşeni de küçük bir belge modeli üzerinde kurar ve erişilebilir adları
hesaplar.

```js
// konum.mjs — kirinti yolu ve sayfalama: erisilebilir ad, bulunulan yer bildirimi ve odak

// Kucuk bir belge modeli: dugum agaci ve erisilebilir ad hesabi.
const dugum = (etiket, ozn = {}, cocuk = []) => ({ etiket, ozn, cocuk });
const metin = (d) =>
  d.etiket === "#metin" ? d.ozn.deger : d.cocuk.map(metin).join("").replace(/\s+/g, " ").trim();
const yaz = (s) => dugum("#metin", { deger: s });

// Erisilebilir ad onceligi: aria-label > aria-labelledby > icerik metni.
function erisilebilirAd(d, belge) {
  if (d.ozn["aria-label"]) return d.ozn["aria-label"];
  if (d.ozn["aria-labelledby"]) {
    const hedef = bul(belge, d.ozn["aria-labelledby"]);
    return hedef ? metin(hedef) : "";
  }
  return metin(d);
}
function bul(d, kimlik) {
  if (d.ozn.id === kimlik) return d;
  for (const c of d.cocuk) { const s = bul(c, kimlik); if (s) return s; }
  return null;
}
const gez = function* (d) { yield d; for (const c of d.cocuk) yield* gez(c); };

// 1. Kirinti yolu: gezinme bolgesi + siralı liste + bulunulan sayfa isareti.
const kirinti = dugum("nav", { "aria-label": "Kırıntı yolu" }, [
  dugum("ol", {}, [
    dugum("li", {}, [dugum("a", { href: "/katalog" }, [yaz("Katalog")])]),
    dugum("li", {}, [dugum("a", { href: "/katalog/bilim" }, [yaz("Bilim")])]),
    dugum("li", {}, [dugum("a", { href: "/katalog/bilim/gokbilim" }, [yaz("Gökbilim")])]),
    dugum("li", {}, [dugum("span", { "aria-current": "page" }, [yaz("Kuzey Yamaç Gözlem Kayıtları")])]),
  ]),
]);

// 2. Sayfalama: gezinme bolgesi + baglantilar + bulunulan sayfa.
const sayfalama = dugum("nav", { "aria-label": "Sonuç sayfaları" }, [
  dugum("ul", {}, [
    dugum("li", {}, [dugum("a", { href: "?sayfa=3", "aria-label": "Önceki sayfa" }, [yaz("‹")])]),
    dugum("li", {}, [dugum("a", { href: "?sayfa=1" }, [yaz("1")])]),
    dugum("li", {}, [dugum("span", { "aria-hidden": "true" }, [yaz("…")])]),
    dugum("li", {}, [dugum("a", { href: "?sayfa=3" }, [yaz("3")])]),
    dugum("li", {}, [dugum("a", { href: "?sayfa=4", "aria-current": "page" }, [yaz("4")])]),
    dugum("li", {}, [dugum("a", { href: "?sayfa=5" }, [yaz("5")])]),
    dugum("li", {}, [dugum("a", { href: "?sayfa=5", "aria-label": "Sonraki sayfa" }, [yaz("›")])]),
  ]),
]);

function denetle(ad, kok) {
  console.log(`\n--- ${ad} ---`);
  console.log(`bolge adi          : ${erisilebilirAd(kok, kok) || "(yok)"}`);
  const bulunulan = [...gez(kok)].filter((d) => d.ozn["aria-current"] === "page");
  console.log(`bulunulan yer isareti: ${bulunulan.length} oge` +
    (bulunulan.length === 1 ? ` ("${metin(bulunulan[0])}")` : ""));
  const baglantilar = [...gez(kok)].filter((d) => d.etiket === "a");
  console.log("baglanti adlari    :");
  for (const b of baglantilar) {
    const ad2 = erisilebilirAd(b, kok);
    const uyari = ad2.length <= 2 && !b.ozn["aria-label"] ? "  <- ad tek karakter, hedefi bildirmiyor" : "";
    console.log(`  ${(b.ozn.href || "").padEnd(24)} ad: ${JSON.stringify(ad2).padEnd(22)}${uyari}`);
  }
  const gizli = [...gez(kok)].filter((d) => d.ozn["aria-hidden"] === "true").length;
  console.log(`agactan gizlenen susleyici oge: ${gizli}`);
  return { bulunulan: bulunulan.length, baglanti: baglantilar.length };
}

denetle("kirinti yolu", kirinti);
denetle("sayfalama", sayfalama);

// 3. Sayfa degisiminde odak: uc secenek ve sonuclari.
// Model: klavye kullanicisi 4. sayfa baglantisina basiyor, liste yeniden ciziliyor.
const SECENEKLER = [
  { ad: "odak dokunulmuyor", odak: "basilan baglanti", kayip: false, duyuru: "yok" },
  { ad: "odak listenin basina", odak: "sonuc listesi kabi", kayip: false, duyuru: "liste basligi" },
  { ad: "odak sayfa basina", odak: "belge koku", kayip: true, duyuru: "sayfa basligi" },
];
console.log("\n--- sayfa degisiminde odak karari ---");
console.log("secenek                 odagin gittigi yer       yer kaybi  duyuru");
for (const s of SECENEKLER) {
  console.log(
    s.ad.padEnd(24) + s.odak.padEnd(25) + (s.kayip ? "var" : "yok").padEnd(11) + s.duyuru
  );
}

// 4. Bulunulan sayfa baglanti olarak birakilirsa ne olur?
// Sekme sirasinda kac oge var ve kacinin hedefi ayni sayfa?
function sekmeSirasi(kok) {
  return [...gez(kok)].filter((d) => d.etiket === "a" && d.ozn.href !== undefined);
}
const s1 = sekmeSirasi(sayfalama);
const ayniHedef = s1.filter((d) => d.ozn["aria-current"] === "page").length;
console.log(`\nsayfalamada sekme duragi: ${s1.length}`);
console.log(`bulunulan sayfayi da baglanti birakan durak: ${ayniHedef}`);
console.log(`  -> bulunulan sayfa baglanti kalirsa kullanici ayni sayfaya gider; ` +
  `isaret bildirimi korunur, hedef degismez.`);

// 5. Kirinti yolunun uzunlugu: derinlik arttikca ad uzunlugu ve kirpma kurali.
const KIRP = 18;
console.log("\n--- kirinti ogelerinin kirpilmasi ---");
for (const d of [...gez(kirinti)].filter((x) => ["a", "span"].includes(x.etiket))) {
  const tam = metin(d);
  const gorunen = tam.length > KIRP ? tam.slice(0, KIRP - 1) + "…" : tam;
  console.log(
    `  gorunen: ${JSON.stringify(gorunen).padEnd(32)} erisilebilir ad: ${JSON.stringify(tam)}` +
      (gorunen !== tam ? "  <- gorunen ad kisaltildi, erisilebilir ad tam kalmali" : "")
  );
}
```

```

--- kirinti yolu ---
bolge adi          : Kırıntı yolu
bulunulan yer isareti: 1 oge ("Kuzey Yamaç Gözlem Kayıtları")
baglanti adlari    :
  /katalog                 ad: "Katalog"             
  /katalog/bilim           ad: "Bilim"               
  /katalog/bilim/gokbilim  ad: "Gökbilim"            
agactan gizlenen susleyici oge: 0

--- sayfalama ---
bolge adi          : Sonuç sayfaları
bulunulan yer isareti: 1 oge ("4")
baglanti adlari    :
  ?sayfa=3                 ad: "Önceki sayfa"        
  ?sayfa=1                 ad: "1"                     <- ad tek karakter, hedefi bildirmiyor
  ?sayfa=3                 ad: "3"                     <- ad tek karakter, hedefi bildirmiyor
  ?sayfa=4                 ad: "4"                     <- ad tek karakter, hedefi bildirmiyor
  ?sayfa=5                 ad: "5"                     <- ad tek karakter, hedefi bildirmiyor
  ?sayfa=5                 ad: "Sonraki sayfa"       
agactan gizlenen susleyici oge: 1

--- sayfa degisiminde odak karari ---
secenek                 odagin gittigi yer       yer kaybi  duyuru
odak dokunulmuyor       basilan baglanti         yok        yok
odak listenin basina    sonuc listesi kabi       yok        liste basligi
odak sayfa basina       belge koku               var        sayfa basligi

sayfalamada sekme duragi: 6
bulunulan sayfayi da baglanti birakan durak: 1
  -> bulunulan sayfa baglanti kalirsa kullanici ayni sayfaya gider; isaret bildirimi korunur, hedef degismez.

--- kirinti ogelerinin kirpilmasi ---
  gorunen: "Katalog"                        erisilebilir ad: "Katalog"
  gorunen: "Bilim"                          erisilebilir ad: "Bilim"
  gorunen: "Gökbilim"                       erisilebilir ad: "Gökbilim"
  gorunen: "Kuzey Yamaç Gözle…"             erisilebilir ad: "Kuzey Yamaç Gözlem Kayıtları"  <- gorunen ad kisaltildi, erisilebilir ad tam kalmali
```

#### Adın Hedefi Bildirmesi

Çıktının ikinci bölümü sayfalamanın en sık kusurunu gösteriyor: bağlantıların erişilebilir
adı tek karakterdir. "3" adlı bir bağlantı, bağlantı listesini gezen bir kullanıcıya hiçbir
şey söylemez. Ok işaretleri için `aria-label` yazılmışken sayı bağlantıları için
yazılmamıştır; oysa sorun ikisinde de aynıdır.

Çözüm, görünen metni değiştirmeden erişilebilir adı tamamlamaktır: bağlantının adı "3"
değil "Sayfa 3" olur. Görsel sunum sayıyı gösterir, ad sayının ne olduğunu söyler. Aynı
kural süsleyici öge için tersine işler: sayfalamadaki üç nokta bir bilgi taşımaz ve ağaçtan
gizlenir; çıktının ikinci bölümünde gizlenen öge sayısı bunu doğruluyor.

#### Bulunulan Sayfa Bağlantı Kalmalı mı

Üçüncü ve dördüncü bölüm bir tasarım kararını ölçüyor. Bulunulan sayfa bağlantı olarak
bırakılırsa sekme sırasında bir durak daha bulunur ve o durak kullanıcıyı aynı sayfaya
götürür. Bırakılmazsa durak sayısı azalır ama bulunulan sayfanın konumu klavye sırasında
görünmez olur.

Karar ölçüte bağlanabilir: bulunulan sayfa **bağlantı kalır** ve `aria-current` ile
işaretlenir. Böylece hem işaret bildirimi korunur hem de sıradaki boşluk oluşmaz; hedefin
aynı sayfa olması bir kayıp değildir, çünkü kullanıcı zaten oradadır ve işaret bunu söyler.

#### Sayfa Değiştiğinde Odak Nereye Gider

Üçüncü bölümdeki tablo üç seçeneği karşılaştırıyor. Odağa hiç dokunmamak en az müdahaledir
ama yeni içeriğin geldiğini hiçbir şey bildirmez. Odağı sayfanın başına almak yer kaybına
yol açar: kullanıcı sayfalama denetimine dönmek için baştan aşağı iner.

Tablodaki ikinci seçenek — odağı sonuç listesinin kapsayıcısına almak — iki sorunu da çözer:
kapsayıcı odaklanabilir yapılır, başlığı okunur ve kullanıcı yeni içeriğin başındadır. Sayfalama
denetimi listenin hemen altında olduğu için bir sonraki sayfaya geçmek yine kısa bir yoldur.

#### Kırıntıda Uzun Ad

Son bölüm görsel kırpmanın kuralını gösteriyor. Uzun bir kayıt adı kırıntı yolunda
kısaltılabilir; kısaltma **görsel** bir karardır ve erişilebilir adı etkilememelidir. Görünen
metin kısaltıldığında tam ad erişilebilir ad olarak korunur; aksi hâlde kullanıcı kaydın
adını asla öğrenemez.

#### Klavye Sözleşmesi

| Tuş | Davranış |
|---|---|
| `Tab` | Bir sonraki bağlantıya geçer; iki bileşen de bağlantı listesidir, ek tuş kuralı yoktur |
| `Shift+Tab` | Bir önceki bağlantıya döner |
| `Enter` | Bağlantıyı izler |

Bu bileşenlerde ok tuşu kuralı **yoktur**. Sekmeler ve menülerde ok tuşu gerekliydi, çünkü
onlar tek bir bileşik denetimdi; kırıntı yolu ve sayfalama ise bağımsız bağlantıların
listesidir ve bağlantı listesinde beklenen davranış sekmedir.

#### Ölçülebilir Kısıtlar

- Sayfalama bağlantılarının hedef boyutu, işaretçi girdisinde (fare, kalem, dokunma) en az
  24×24 piksel olmalıdır (WCAG 2.5.8); aralarındaki boşluk bu ölçüyü sağlamanın ikinci
  yoludur.
- Bulunulan sayfa yalnız renkle ayrılamaz (WCAG 1.4.1): renge kalınlık, çerçeve ya da
  başka bir kanal eşlik eder ve `aria-current` bildirimi zaten metinsel kanalı sağlar.
- Kırıntı ayıracı (`/`, `>`) süsleyicidir ve erişilebilirlik ağacından gizlenir; ayıraç
  metnin parçası hâline gelirse ad kirlenir.
- Her gezinme bölgesinin erişilebilir adı olmalıdır (WCAG 1.3.1 kapsamında yapısal bilgi).

#### Sık Yapılan Hata

En sık hata, bulunulan sayfayı yalnız görsel olarak işaretlemektir. İkincisi, sayfalamayı
düğmelerle kurup adres çubuğunu güncellememektir: kullanıcı dördüncü sayfayı paylaşamaz,
geri tuşu bir önceki sayfaya dönmez. Sayfa numarası bir **konum bilgisidir** ve konum
adreste taşınır — bu, Rota Parametreleri ve Sorgu dersinde kurulan kuralın arayüz
tarafındaki karşılığıdır.

#### Özet

- Kırıntı yolu hiyerarşideki, sayfalama sıradaki konumu bildirir; ikisi de kendi adlandırılmış
  gezinme bölgesinde durur ve bulunulan yeri `aria-current` ile işaretler.
- Erişilebilir ad hedefi bildirmelidir: "3" değil "Sayfa 3"; süsleyici ayıraç ve üç nokta
  ağaçtan gizlenir.
- Bulunulan sayfa bağlantı olarak kalır ve işaretlenir; sıradan çıkarmak konumu klavye
  kullanıcısına görünmez yapar.
- Sayfa değiştiğinde odak sonuç listesinin kapsayıcısına alınır: yer kaybı olmaz, yeni içeriğin
  geldiği duyurulur.
- Görsel kırpma erişilebilir adı kısaltmaz; tam ad ağaçta korunur.

#### Sonraki Adım

Sonuç listesi bu dersle konumunu bildirir hâle geldi, ama listenin kendisi hâlâ basit bir
kayıt dizisidir. Katalogda aynı veri bir tabloya dönüştüğünde — sütunlar, sıralama,
süzgeçler ve yüzlerce satır — konum bilgisi iki eksene çıkar: kullanıcı hem hangi satırda
hem hangi sütundadır. Sonraki ders tabloyu belirtir: başlık hücrelerinin satır ve sütunla
ilişkisini, sıralama durumunun nasıl duyurulacağını ve yoğun veride hangi kısayolların
gerektiğini ele alır.

### Tablolar

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/tablolar

İki eksenli konumun işaretlemede kurulması, satır ve sütun başlığı ilişkisi, sıralama durumunun bildirimi ile duyurusu ve yoğun veride sekme sırasının denetim altında tutulması.

Önceki ders sonuç listesinin konumunu bildirir hâle getirdi. Katalogda aynı veri çoğu
zaman bir listeden daha fazlasıdır: raf kodu, başlık, yıl ve durum sütunlara ayrılır,
sütunlar sıralanabilir ve satır sayısı yüzleri bulur. Bu yapıya tablo denir ve tabloda
konum iki eksenlidir — kullanıcı hem bir satırda hem bir sütundadır.

Görsel okuyucu bu iki ekseni bakışla birleştirir: bir hücreye bakar, yukarı doğru sütun
başlığını, sola doğru satır başlığını görür. Ekranı görmeyen kullanıcı bu birleştirmeyi
yapamaz; hücrenin hangi başlıklara bağlı olduğu **işaretlemede** yazılı olmak zorundadır.

#### Kalıbın Çözdüğü Sorun ve Kullanılmayacağı Yer

Tablo, iki boyutlu ilişkisel veriyi sunar: her satır bir varlık, her sütun o varlığın bir
niteliğidir. Bir veri tablonun içine ancak bu ilişki gerçekten varsa girer.

Düzen kurmak için tablo kullanmak bu ilişkiyi uydurur: ekranı görmeyen kullanıcıya var olmayan bir
satır–sütun yapısı bildirilir ve içerik anlamsız bir ızgaraya dönüşür. Düzen işi Düzen Sistemleri
ve Duyarlı Tasarım kursundaki ızgara modellerinin işidir; tablo ögesi yalnız veri tablosu içindir.

#### Yerel Öge Önce ve Başlık İlişkisi

Tablonun yerel karşılığı eksiksizdir: tablo ögesi, özet başlığı, başlık hücreleri ve veri
hücreleri. Başlık hücresi sütun ya da satır başlığı olduğunu kapsam bildirimiyle söyler.
Bu bildirim yazıldığında hücre–başlık ilişkisi kendiliğinden kurulur; ARIA'ya gerek kalmaz.

Aşağıdaki hesap bu ilişkiyi bir model üzerinde kuruyor ve satır başlığı işaretlenmediğinde
neyin kaybolduğunu sayıyor.

```js
// tablo.mjs — baslik-hucre iliskisi, siralama durumu ve yogun veride gezinme

// Tablo modeli: basliklar, satirlar ve her hucrenin degeri.
const TABLO = {
  ozet: "Kuzey Yamaç ölçüm kayıtları",
  sutunlar: [
    { anahtar: "kod", ad: "Raf kodu", tur: "metin", siralanabilir: true },
    { anahtar: "baslik", ad: "Başlık", tur: "metin", siralanabilir: true, satirBasligi: true },
    { anahtar: "yil", ad: "Yıl", tur: "sayi", siralanabilir: true },
    { anahtar: "durum", ad: "Durum", tur: "metin", siralanabilir: false },
  ],
  satirlar: [
    { kod: "QB-141", baslik: "Gökbilim El Kitabı", yil: 1998, durum: "rafta" },
    { kod: "QB-212", baslik: "Kuzey Gökyüzü Atlası", yil: 2004, durum: "ödünçte" },
    { kod: "QC-018", baslik: "Ölçüm Yöntemleri", yil: 2011, durum: "rafta" },
  ],
};

// 1. Baslik-hucre iliskisi: her veri hucresinin bagli oldugu basliklar.
// Kural: sutun basligi tablonun ust satirindan, satir basligi o satirin
// "satirBasligi" isaretli sutunundan gelir.
const satirBasligiSutunu = TABLO.sutunlar.find((s) => s.satirBasligi);
console.log("--- baslik-hucre iliskisi ---");
console.log("hucre                     bagli basliklar");
let baglisiz = 0;
for (const satir of TABLO.satirlar) {
  for (const sutun of TABLO.sutunlar) {
    if (sutun.satirBasligi) continue;
    const basliklar = [sutun.ad, satirBasligiSutunu ? satir[satirBasligiSutunu.anahtar] : null].filter(Boolean);
    if (basliklar.length < 2) baglisiz++;
    console.log(`  ${String(satir[sutun.anahtar]).padEnd(24)} ${basliklar.join(" / ")}`);
  }
}
console.log(`iki baslikla da eslesmeyen hucre: ${baglisiz}`);

// 2. Ayni tablo satir basligi isaretlenmeden kurulursa.
const satirBasligisiz = { ...TABLO, sutunlar: TABLO.sutunlar.map((s) => ({ ...s, satirBasligi: false })) };
const eksikEslesme = satirBasligisiz.satirlar.length * (satirBasligisiz.sutunlar.length - 0);
console.log(`\nsatir basligi isaretlenmezse: her hucre yalniz sutun adiyla eslesir`);
console.log(`  eslesme sayisi: hucre basina 1 (satir kimligi kayboluyor)`);
console.log(`  etkilenen hucre: ${eksikEslesme}`);

// 3. Siralama durumu: yalniz bir sutun siralanmis olabilir.
function siralamaDurumu(sutunAnahtari, yon) {
  return TABLO.sutunlar.map((s) => ({
    ad: s.ad,
    siralanabilir: s.siralanabilir,
    durum: !s.siralanabilir ? "-" : s.anahtar === sutunAnahtari ? yon : "none",
  }));
}
const durum = siralamaDurumu("yil", "ascending");
console.log("\n--- siralama durumu ---");
console.log("sutun        siralanabilir  aria-sort");
for (const d of durum) console.log(`  ${d.ad.padEnd(11)} ${(d.siralanabilir ? "evet" : "hayir").padEnd(14)} ${d.durum}`);
const isaretli = durum.filter((d) => d.durum !== "none" && d.durum !== "-").length;
console.log(`isaretli sutun: ${isaretli} (kural: en cok 1)`);

// 4. Siralamadan sonra duyurulacak dizgi.
const yonAdi = { ascending: "artan", descending: "azalan" };
function duyuru(sutunAd, yon, satirSayisi) {
  return `${sutunAd} sütununa göre ${yonAdi[yon]} sıralandı, ${satirSayisi} satır`;
}
console.log(`\nduyuru: "${duyuru("Yıl", "ascending", TABLO.satirlar.length)}"`);
console.log(`duyuru: "${duyuru("Başlık", "descending", TABLO.satirlar.length)}"`);

// 5. Siralamanin kendisi: veri turu karsilastirmayi belirler.
const karsilastir = (tur) =>
  tur === "sayi" ? (a, b) => a - b : (a, b) => String(a).localeCompare(String(b), "tr");
console.log("\n--- siralama sonucu (yil, artan) ---");
for (const s of [...TABLO.satirlar].sort((a, b) => karsilastir("sayi")(a.yil, b.yil))) {
  console.log(`  ${String(s.yil).padEnd(6)} ${s.baslik}`);
}
console.log("--- siralama sonucu (baslik, artan; tr harf sirasi) ---");
for (const s of [...TABLO.satirlar].sort((a, b) => karsilastir("metin")(a.baslik, b.baslik))) {
  console.log(`  ${s.baslik}`);
}

// 6. Yogun veride sekme duragi sayisi: her hucre odaklanabilir yapilirsa ne olur?
const SATIR = 200, SUTUN = 4;
const hepsiOdaklanabilir = SATIR * SUTUN;
const yalnizEylemler = SATIR * 1 + SUTUN;      // satir basina bir eylem + sutun basliklari
console.log("\n--- sekme duragi sayisi (200 satir, 4 sutun) ---");
console.log(`  her hucre odaklanabilir : ${hepsiOdaklanabilir}`);
console.log(`  yalniz eylemler + basliklar: ${yalnizEylemler}`);
console.log(`  oran: ${(hepsiOdaklanabilir / yalnizEylemler).toFixed(1)} kat`);

// 7. Sutun sayisi tutarliligi: eksik hucre denetimi.
const bozukSatir = { kod: "QC-100", baslik: "Eksik Kayıt" }; // yil ve durum yok
const denetlenen = [...TABLO.satirlar, bozukSatir];
console.log("\n--- sutun sayisi tutarliligi ---");
for (const s of denetlenen) {
  const eksik = TABLO.sutunlar.filter((c) => s[c.anahtar] === undefined).map((c) => c.ad);
  console.log(`  ${(s.kod || "-").padEnd(8)} eksik alan: ${eksik.length ? eksik.join(", ") : "yok"}`);
}
```

```
--- baslik-hucre iliskisi ---
hucre                     bagli basliklar
  QB-141                   Raf kodu / Gökbilim El Kitabı
  1998                     Yıl / Gökbilim El Kitabı
  rafta                    Durum / Gökbilim El Kitabı
  QB-212                   Raf kodu / Kuzey Gökyüzü Atlası
  2004                     Yıl / Kuzey Gökyüzü Atlası
  ödünçte                  Durum / Kuzey Gökyüzü Atlası
  QC-018                   Raf kodu / Ölçüm Yöntemleri
  2011                     Yıl / Ölçüm Yöntemleri
  rafta                    Durum / Ölçüm Yöntemleri
iki baslikla da eslesmeyen hucre: 0

satir basligi isaretlenmezse: her hucre yalniz sutun adiyla eslesir
  eslesme sayisi: hucre basina 1 (satir kimligi kayboluyor)
  etkilenen hucre: 12

--- siralama durumu ---
sutun        siralanabilir  aria-sort
  Raf kodu    evet           none
  Başlık      evet           none
  Yıl         evet           ascending
  Durum       hayir          -
isaretli sutun: 1 (kural: en cok 1)

duyuru: "Yıl sütununa göre artan sıralandı, 3 satır"
duyuru: "Başlık sütununa göre azalan sıralandı, 3 satır"

--- siralama sonucu (yil, artan) ---
  1998   Gökbilim El Kitabı
  2004   Kuzey Gökyüzü Atlası
  2011   Ölçüm Yöntemleri
--- siralama sonucu (baslik, artan; tr harf sirasi) ---
  Gökbilim El Kitabı
  Kuzey Gökyüzü Atlası
  Ölçüm Yöntemleri

--- sekme duragi sayisi (200 satir, 4 sutun) ---
  her hucre odaklanabilir : 800
  yalniz eylemler + basliklar: 204
  oran: 3.9 kat

--- sutun sayisi tutarliligi ---
  QB-141   eksik alan: yok
  QB-212   eksik alan: yok
  QC-018   eksik alan: yok
  QC-100   eksik alan: Yıl, Durum
```

#### Satır Başlığı Kimliği Taşır

Çıktının ilk bölümü her veri hücresinin iki başlığa bağlandığını gösteriyor: sütun adı ve
satırın kimliği. "1998" değeri tek başına bir sayıdır; "Yıl / Gökbilim El Kitabı" bağlamı
onu bir bilgiye çevirir.

İkinci bölüm satır başlığı işaretlenmediğinde ne olduğunu sayıyor: on iki hücrenin her biri yalnız
sütun adıyla eşleşiyor ve satır kimliği kayboluyor. Hücreler arasında gezen kullanıcı "1998" duyar
ama hangi kaydın yılı olduğunu bilemez. Her satırda bir hücre — genellikle adı ya da kimliği
taşıyan hücre — satır başlığı olarak işaretlenir.

#### Sıralama Durumu Bir Bildirimdir

Üçüncü bölüm sıralama durumunu denetliyor. Kural iki parçalıdır. Birincisi, sıralanabilir
her sütun başlığının sıralama durumunu bildirmesi gerekir; sıralanabilir olmayan sütun bu
bildirimi hiç taşımaz. İkincisi, aynı anda **en çok bir** sütun sıralanmış olabilir; iki
sütunun birden işaretli görünmesi, kullanıcıya çelişkili bir durum bildirir.

Sıralama denetiminin kendisi başlık hücresinin içindeki bir düğmedir. Başlık hücresini
tıklanabilir yapmak yerine düğme kullanmanın nedeni Bağlantı ve Düğme Ayrımı dersinde
kurulmuştu: eylem düğmedir, düğme klavyeden erişilir ve rolüyle bildirilir.

#### Sıralamadan Sonra Ne Duyurulur

Dördüncü bölüm duyuru dizgisini üretiyor. Sıralama sayfayı değiştirmez; tablonun içeriğini yeniden
düzenler, bu değişiklik görsel olarak anında görünür ama duyurulmazsa ekranı görmeyen kullanıcı
için sessizdir.

Duyuru üç bilgiyi taşır: hangi sütun, hangi yön, kaç satır. Satır sayısının bulunması
önemlidir, çünkü sıralama süzgeçle birlikte çalıştığında satır sayısı da değişebilir.
Duyuru kibar bir canlı bölgeden verilir; sıralama kullanıcının kendi eylemidir ve
kesintiye uğratmayı gerektirmez.

Beşinci bölüm sıralamanın kendisini gösteriyor: karşılaştırma veri türüne bağlıdır. Sayı
sütunu sayısal, metin sütunu dile duyarlı karşılaştırmayla sıralanır. Türkçe metinlerde
dizgi karşılaştırması yerel ayara verilmezse harf sırası bozulur — bu, Uluslararasılaştırma
API'si dersinde kurulan kuralın tablo tarafındaki karşılığıdır.

#### Yoğun Veride Gezinme

Altıncı bölüm bir sayıyı ortaya koyuyor: iki yüz satırlık bir tabloda her hücre
odaklanabilir yapılırsa sekme sırasına sekiz yüz durak girer. Yalnız eylemler ve sütun
başlıkları odaklanabilir olduğunda bu sayı iki yüz dörde iner — yaklaşık dörtte bir.

Tablo hücreleri arasında ok tuşlarıyla gezinme, ancak tablo bir **ızgara denetimi** olarak
kurulduğunda gerekir: hücreler düzenlenebiliyorsa ya da hücre seçimi bir eylemse. Salt
okunur bir veri tablosunda hücreler odaklanabilir yapılmaz; kullanıcı içeriği okuma
kipiyle gezer ve sekme sırası yalnız eylemleri taşır.

Son bölüm sütun sayısı tutarlılığını denetliyor. Eksik hücre bırakan bir satır, başlık
eşlemesini kaydırır ve okuma sırasında değerler yanlış başlıkla eşleşir. Boş bir hücre
bilinçli olarak boş bırakılabilir; **eksik** hücre bir kusurdur.

#### Klavye Sözleşmesi

| Tuş | Davranış |
|---|---|
| `Tab` | Bir sonraki eyleme geçer: sıralama düğmesi, satır içi eylem, sayfalama |
| `Enter` / `Boşluk` | Odaktaki sıralama düğmesini çalıştırır, yönü değiştirir |
| Ok tuşları | Salt okunur tabloda tablo kısayolu değildir; okuma kipinin kendi gezinmesi çalışır |

#### Ölçülebilir Kısıtlar

- Sıralanabilir her başlık hücresi sıralama durumunu bildirir ve aynı anda en çok biri
  sıralanmış olur.
- Her satırda bir hücre satır başlığı olarak işaretlenir; sütun başlıkları kapsamını
  bildirir (WCAG 1.3.1).
- Sıralama düğmesinin hedef boyutu 24×24 pikselden küçük olamaz (WCAG 2.5.8); başlık
  hücresinin tamamı tıklanabilir yapılıyorsa bu ölçü zaten sağlanır.
- Sıralama yönü yalnız bir ok simgesiyle bildirilemez; durum bildirimi metinsel kanaldır (WCAG
  4.1.2).
- Yatay kaydırma gereken geniş tablolarda kaydırılabilir kap klavyeyle erişilebilir olmalıdır.

#### Sık Yapılan Hata

En sık hata, tabloyu düzen aracı olarak kullanmaktır. İkincisi, başlık hücrelerini
biçimlendirilmiş veri hücresi olarak yazmaktır: kalın ve ortalanmış bir veri hücresi görsel
olarak başlığa benzer ama hiçbir hücreyle ilişkilendirilmez. Üçüncüsü, sıralamayı yalnız
simgeyle bildirmek ve durum bildirimini atlamaktır; bu durumda sıralamanın hangi sütunda
olduğu klavye kullanıcısına hiç ulaşmaz.

#### Özet

- Tabloda konum iki eksenlidir; hücrenin hangi satır ve sütun başlığına bağlı olduğu
  işaretlemede yazılır, bakışla kurulan ilişki varsayılamaz.
- Satır başlığı işaretlenmediğinde her hücre yalnız sütun adıyla eşleşir ve satır kimliği
  kaybolur; modelde on iki hücrenin tamamı etkileniyor.
- Sıralanabilir sütun sıralama durumunu bildirir, aynı anda en çok bir sütun işaretlidir ve
  sıralama sonrası duyuru sütun, yön ve satır sayısını taşır.
- Karşılaştırma veri türüne bağlıdır; metin sütunları dile duyarlı sıralanır.
- Salt okunur tabloda hücreler odaklanabilir yapılmaz: iki yüz satırlık tabloda bu karar
  sekme durağı sayısını sekiz yüzden iki yüz dörde indirir.

#### Sonraki Adım

Bu bileşenlerin hepsinde içerik yerinde durdu ve kullanıcı ona kendi hızıyla ulaştı. Katalog
ana sayfasındaki öne çıkan kayıtlar şeridi ise tersini yapar: içerik kendiliğinden değişir,
kullanıcı okumayı bitirmeden bir sonraki karta geçer. Sonraki ders bu kalıbı ele alır ve
otomatik geçişin maliyetini ölçütlerle yazar: hareketin durdurulabilir olması, okuma
süresiyle geçiş aralığı arasındaki ilişki ve kendiliğinden değişen içeriğin duyurulmasının
neden çoğu zaman yanlış olduğu.

### Döngüsel İçerik Gezinimi

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/donguisel-icerik-gezinimi

Otomatik geçişin okuma süresiyle ilişkisi, durdurma denetiminin koşulsuz gerekliliği, görünmeyen kartların sekme sırasından çıkarılması ve otomatik değişen içeriğin neden duyurulmadığı.

Buraya kadarki bileşenlerde içerik yerinde durdu ve kullanıcı ona kendi hızıyla ulaştı.
Katalog ana sayfasındaki öne çıkan kayıtlar şeridi bunun tersini yapar: beş kart tek bir
alanda sırayla gösterilir ve geçiş kullanıcının bir eylemi olmadan gerçekleşir.

Kalıbın gerekçesi genellikle alan tasarrufudur — beş kart bir kartın yerini kaplar. Bedeli
ise ölçülebilir: kullanıcının okuma hızı ile geçiş aralığı arasındaki ilişki, hareketin
durdurulabilirliği ve görünmeyen kartların klavye sırasında ne yaptığı. Bu ders bedeli
sayıya çevirir.

#### Kalıbın Çözdüğü Sorun ve Kullanılmayacağı Yer

Döngüsel gezinim, sıralı ve eşdeğer bir içerik kümesini sınırlı bir alanda sunar. Kullanım
koşulu iki maddedir: kartlar birbirinin **alternatifi** olmalı (biri kaçırılınca bilgi
kaybolmamalı) ve kullanıcı hepsini görmek zorunda olmamalıdır.

Bu koşul sağlanmıyorsa kalıp yanlıştır. Bir adımlar dizisi, bir form akışı ya da
kaçırılmaması gereken bir uyarı şeride konmaz. Son bölümdeki karşılaştırma bunun nedenini
sayıyla veriyor: şeritte içeriğin yüzde sekseni her an görünmez durumdadır.

#### Okuma Süresi ile Geçiş Aralığı

Aşağıdaki hesap kartların okuma süresini kelime sayısından tahmin eder ve geçiş aralığı
seçeneklerini bu süreye göre değerlendirir.

```js
// dongu.mjs — otomatik gecisli icerik seridi: okuma suresi, durdurma ve sekme sirasi

// Ana sayfadaki "one cikan kayitlar" seridi: bes kart, her karta bir metin.
const KARTLAR = [
  { baslik: "Gökbilim El Kitabı", metin: "Gözlem düzeneğinin kurulumu ve ölçüm günlüğü tutma yöntemleri.", eylem: 2 },
  { baslik: "Kuzey Gökyüzü Atlası", metin: "Kuzey yarımküre takımyıldızlarının mevsimlere göre haritaları.", eylem: 2 },
  { baslik: "Ölçüm Yöntemleri", metin: "Sıcaklık, nem ve basınç ölçümünde hata payının hesaplanması.", eylem: 1 },
  { baslik: "Yamaç Meteorolojisi", metin: "Yükseltiye bağlı sıcaklık değişiminin uzun dönem kayıtları.", eylem: 2 },
  { baslik: "Kayıt Defteri Tarihi", metin: "Gözlem defterlerinin arşivlenme biçimleri ve okunma sorunları.", eylem: 1 },
];

// 1. Okuma suresi: karakter sayisindan tahmini okuma suresi.
// Olcut: dakikada 200 kelime (yetiskin, tanidik icerik), kelime basina ortalama 6 karakter.
const KELIME_DK = 200;
const okumaSuresi = (s) => {
  const kelime = s.trim().split(/\s+/).length;
  return (60 * kelime) / KELIME_DK;
};
console.log("kart                      kelime  okuma suresi (s)");
let enUzun = 0;
for (const k of KARTLAR) {
  const t = okumaSuresi(k.baslik + " " + k.metin);
  enUzun = Math.max(enUzun, t);
  console.log(`  ${k.baslik.padEnd(24)} ${String((k.baslik + " " + k.metin).split(/\s+/).length).padStart(4)} ${t.toFixed(1).padStart(14)}`);
}
console.log(`en uzun kartin okuma suresi: ${enUzun.toFixed(1)} s`);

// 2. Gecis araligi kararlari ve sonuclari.
const ARALIKLAR = [3, 5, 8, 12];
console.log("\naralik (s)  en uzun karti okumaya yeter mi  WCAG 2.2.2 gerektirir mi");
for (const a of ARALIKLAR) {
  console.log(
    `${String(a).padStart(9)} ${(a >= enUzun ? "evet" : "HAYIR").padStart(29)} ` +
      `${(a > 5 ? "evet (5 s ustu hareket)" : "evet (otomatik baslayan hareket)").padStart(32)}`
  );
}
console.log("kural: sure ne olursa olsun durdurma denetimi zorunludur; sure yalniz");
console.log("       okuma yetismesini belirler.");

// 3. Sekme siralamasi: gizli kartlar sirada kalirsa ne olur?
const eylemToplam = KARTLAR.reduce((t, k) => t + k.eylem, 0);
const gorunen = KARTLAR[0];
console.log("\n--- sekme duragi sayisi ---");
console.log(`  butun kartlar sirada  : ${eylemToplam + 4}  (kart eylemleri + onceki/sonraki + durdur + nokta grubu)`);
console.log(`  yalniz gorunen kart   : ${gorunen.eylem + 4}`);
console.log(`  fark                  : ${eylemToplam - gorunen.eylem} durak gorunmeyen icerige gidiyor`);

// 4. Odak seridin icindeyken gecis olursa.
const SENARYO = [
  { ad: "gecis surer, odak icerde", odakKaybi: true, sonuc: "odak belge kokune duser" },
  { ad: "odakta duraklar", odakKaybi: false, sonuc: "kullanici okumasini bitirir" },
  { ad: "uzerine gelmede duraklar", odakKaybi: true, sonuc: "klavye kullanicisi korunmaz" },
];
console.log("\n--- odak seridin icindeyken otomatik gecis ---");
console.log("politika                     odak kaybi  sonuc");
for (const s of SENARYO) {
  console.log(`  ${s.ad.padEnd(28)} ${(s.odakKaybi ? "var" : "yok").padEnd(11)} ${s.sonuc}`);
}

// 5. Duyuru politikasi: otomatik gecis duyurulur mu?
function duyuruPolitikasi(otomatik) {
  return otomatik
    ? { canliBolge: "kapali", gerekce: "kullanicinin baslatmadigi degisiklik duyurulmaz" }
    : { canliBolge: "kibar", gerekce: "kullanicinin kendi eylemi sonucu gelen icerik duyurulur" };
}
console.log("\n--- duyuru politikasi ---");
for (const otomatik of [true, false]) {
  const p = duyuruPolitikasi(otomatik);
  console.log(`  gecis ${(otomatik ? "otomatik" : "elle").padEnd(9)} canli bolge: ${p.canliBolge.padEnd(7)} ${p.gerekce}`);
}

// 6. Konum bildirimi: kacinci kart goruluyor?
const konum = (i, n) => `${i + 1} / ${n}`;
console.log("\nkonum bildirimi ornekleri: " + KARTLAR.map((_, i) => konum(i, KARTLAR.length)).join("  "));

// 7. Serit yerine dizilim: ayni icerik kaydirilmadan gosterilseydi?
console.log("\n--- serit ile dizilimin karsilastirmasi ---");
console.log(`  seritte ayni anda gorunen kart : 1 / ${KARTLAR.length}`);
console.log(`  izgarada ayni anda gorunen kart: ${KARTLAR.length} / ${KARTLAR.length}`);
console.log(`  seritte gorunmeyen icerik      : %${(100 * (KARTLAR.length - 1) / KARTLAR.length).toFixed(0)}`);
```

```
kart                      kelime  okuma suresi (s)
  Gökbilim El Kitabı         11            3.3
  Kuzey Gökyüzü Atlası        9            2.7
  Ölçüm Yöntemleri           10            3.0
  Yamaç Meteorolojisi         9            2.7
  Kayıt Defteri Tarihi       10            3.0
en uzun kartin okuma suresi: 3.3 s

aralik (s)  en uzun karti okumaya yeter mi  WCAG 2.2.2 gerektirir mi
        3                         HAYIR evet (otomatik baslayan hareket)
        5                          evet evet (otomatik baslayan hareket)
        8                          evet          evet (5 s ustu hareket)
       12                          evet          evet (5 s ustu hareket)
kural: sure ne olursa olsun durdurma denetimi zorunludur; sure yalniz
       okuma yetismesini belirler.

--- sekme duragi sayisi ---
  butun kartlar sirada  : 12  (kart eylemleri + onceki/sonraki + durdur + nokta grubu)
  yalniz gorunen kart   : 6
  fark                  : 6 durak gorunmeyen icerige gidiyor

--- odak seridin icindeyken otomatik gecis ---
politika                     odak kaybi  sonuc
  gecis surer, odak icerde     var         odak belge kokune duser
  odakta duraklar              yok         kullanici okumasini bitirir
  uzerine gelmede duraklar     var         klavye kullanicisi korunmaz

--- duyuru politikasi ---
  gecis otomatik  canli bolge: kapali  kullanicinin baslatmadigi degisiklik duyurulmaz
  gecis elle      canli bolge: kibar   kullanicinin kendi eylemi sonucu gelen icerik duyurulur

konum bildirimi ornekleri: 1 / 5  2 / 5  3 / 5  4 / 5  5 / 5

--- serit ile dizilimin karsilastirmasi ---
  seritte ayni anda gorunen kart : 1 / 5
  izgarada ayni anda gorunen kart: 5 / 5
  seritte gorunmeyen icerik      : %80
```

Çıktının ilk bölümü en uzun kartın yaklaşık üç buçuk saniyede okunduğunu gösteriyor. Bu
tahmin tanıdık içerik ve akıcı okuma varsayar; ekran okuyucuyla dinleyen, düşük görme
keskinliğiyle okuyan ya da içeriğe yabancı bir kullanıcı için süre kat kat uzar. Tahmin
bu yüzden bir alt sınırdır, hedef değil.

İkinci bölüm aralık seçeneklerini iki ölçütle değerlendiriyor. Üç saniyelik aralık en uzun
kartı okumaya yetmiyor. Beş saniyenin üstündeki her aralık ise hareketi süreli hareket
kategorisine sokuyor. Tablonun asıl gösterdiği şu: **hangi aralık seçilirse seçilsin
durdurma denetimi zorunludur.** Aralık yalnız okumanın yetişip yetişmediğini belirler;
kullanıcının denetimi elinde tutması ayrı ve koşulsuz bir gerekliliktir (WCAG 2.2.2).

#### Görünmeyen Kartlar Sekme Sırasında Ne Yapar

Üçüncü bölüm sekme durağı sayısını karşılaştırıyor. Beş kartın tamamı sırada bırakılırsa on
iki durak oluşur ve bunların altısı o anda **görünmeyen** içeriğe gider. Klavye kullanıcısı
şeridi geçerken görmediği kartların bağlantılarına odaklanır; odak göstergesi ekranda
görünmeyen bir yerdedir.

Kural açıktır: görünmeyen kart klavye sırasından da erişilebilirlik ağacından da çıkarılır.
Kartların tamamına erişim, şeridin kendi gezinme denetimleriyle sağlanır — önceki, sonraki
ve konum noktaları.

#### Odak İçerideyken Geçiş

Dördüncü bölüm üç politikayı karşılaştırıyor. Geçişin sürmesi, odaktaki ögenin ortadan
kalkması demektir; odak belge köküne düşer ve kullanıcı yerini kaybeder. Bu, Kip Pencereler
dersindeki odak dönüş kuralının ihlalidir: odağın taşıyıcısı kaldırılıyorsa odağın nereye
gideceği önceden kararlaştırılmış olmalıdır.

Yalnız işaretçi üzerine gelmede duraklamak klavye kullanıcısını korumaz. Doğru politika,
**odak şeridin içindeyken geçişin durmasıdır**; kullanıcı okumasını bitirip odağı dışarı
taşıdığında döngü kaldığı yerden sürer.

#### Otomatik Değişen İçerik Duyurulur mu

Beşinci bölüm duyuru politikasını ayırıyor. Kullanıcının başlatmadığı bir değişiklik
duyurulmaz: her geçişte yeni kartın okunması, ekran okuyucu kullanıcısının o anda okuduğu
metni sürekli kesintiye uğratır. Otomatik geçişte şerit sessizdir.

Kullanıcı önceki ya da sonraki düğmesine bastığında durum tersine döner: değişikliğin
kaynağı kullanıcının kendi eylemidir ve sonucu bildirilmelidir. Altıncı bölümdeki konum
bildirimi bu duyurunun içeriğidir — kaçıncı kartın kaç karttan gösterildiği. Konum
noktaları da aynı bilgiyi taşır ve bu yüzden salt süsleyici değildir.

#### Klavye Sözleşmesi

| Tuş | Davranış |
|---|---|
| `Tab` | Şeridin denetimlerine ve **yalnız görünen** kartın eylemlerine uğrar |
| `Enter` / `Boşluk` | Odaktaki denetimi çalıştırır: önceki, sonraki, durdur, konum noktası |
| Ok tuşları | Konum noktaları grubu içinde kartlar arasında gezer (dönen sekme durağı) |

Şeridin kendisi bir sekme kümesi gibi kurulduğunda ok tuşu kuralı Sekmeler dersindeki
sözleşmeyle aynıdır; kart içeriği bir bağlantı listesiyse ok tuşu kuralı yoktur.

#### Ölçülebilir Kısıtlar

- Beş saniyeden uzun süren, otomatik başlayan hareket için duraklatma, durdurma ya da
  gizleme denetimi zorunludur (WCAG 2.2.2).
- Hareket azaltma tercihi bildirildiğinde otomatik geçiş **başlatılmaz**; Kullanıcı
  Tercihleri dersindeki kural burada da geçerlidir.
- Görünmeyen kartlar sekme sırasında ve erişilebilirlik ağacında bulunmaz.
- Konum noktalarının hedef boyutu 24×24 pikselden küçük olamaz (WCAG 2.5.8); nokta görsel
  olarak küçükse etkin alan büyütülür.
- Otomatik geçişte canlı bölge kapalıdır; kullanıcı eylemiyle gelen geçiş kibar bölgeden
  duyurulur.

#### Sık Yapılan Hata

En sık hata, durdurma denetimini hiç koymamak ya da yalnız işaretçi üzerine gelmeye
bağlamaktır. İkincisi, konum noktalarını süs sayıp ad vermemektir: "1 / 5" bilgisi hiçbir
kanalda yoksa kullanıcı kaç kart olduğunu bilemez. Üçüncüsü, şeridi kaçırılmaması gereken
içerik için kullanmaktır — kampanya duyurusu, uyarı ya da tek seferlik bilgi şeride
konduğunda görülme olasılığı beşte bire iner.

#### Özet

- Döngüsel gezinim yalnız birbirinin alternatifi olan, kaçırılması bilgi kaybı yaratmayan
  içerik için kullanılır; şeritte içeriğin yüzde sekseni her an görünmez.
- Geçiş aralığı okuma süresine göre seçilir (modelde en uzun kart 3.3 saniye), ama aralık
  ne olursa olsun durdurma denetimi koşulsuz zorunludur.
- Görünmeyen kartlar sekme sırasından çıkarılır: beş kartlık şeritte bu karar altı durağı
  görünmeyen içerikten alır.
- Odak şeridin içindeyken otomatik geçiş durur; yalnız işaretçiye bağlı duraklatma klavye
  kullanıcısını korumaz.
- Otomatik geçiş duyurulmaz, kullanıcı eylemiyle gelen geçiş "kaçıncı / kaç" konumuyla
  duyurulur.

#### Sonraki Adım

Buraya kadarki bileşenlerin hepsi sayfanın akışı içindeydi: kart, sekme, tablo ve şerit kendi
yerlerinde durur, kullanıcı da onlara gider. Sıradaki bileşenler ise akışın **üstüne** çıkar ve
çoğu zaman odağı devralır: ödünç alma onayı bir kip pencerede sorulur, sonuç bir bildirim
şeridiyle iletilir, bekleme bir göstergeyle anlatılır. Sonraki konu bu katmanı ele alır ve ilk
dersi kip pencerelerle başlar — odağın nasıl tuzaklanacağı, pencerenin nasıl kapatılacağı ve
kapandığında odağın nereye döneceği.

## Katman ve Geri Bildirim Bileşenleri

Sayfa akışının üstüne çıkan bileşenlerin belirtimi: kip pencereler, araç ipuçları, bildirimler, yükleme göstergeleri, iskelet ekranlar, boş ve hata durumları, rozetler.

### Kip Pencereler

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/kip-pencereler

Kip pencerenin belirtimi; yerel iletişim ögesiyle ARIA kurulumunun ayrımı, odak kuşatmasının klavye tuzağından farkı, dönüş noktasının durum makinesiyle sınanması ve ölçülebilir kısıtlar.

Buraya kadarki bütün bileşenler sayfanın akışı içinde durdu. Düğme, girdi, kart, sekme, tablo:
hepsi belge sırasında bir yer tutar, hepsine sekme tuşuyla sırası gelince ulaşılır ve hiçbiri bir
diğerinin okunmasını engellemez. Kullanıcı isterse hiçbirine uğramadan sayfayı baştan sona
geçebilir.

Bu konudaki bileşenler o akışın **üstüne** çıkar. Ekranda geçici bir katman açarlar, bazen
odağı devralırlar, bazen kullanıcı hiç istemeden belirirler. Akışın üstünde durmak iki yeni
soru getirir: bu katman açıkken altındaki sayfaya ne olur ve katman kapandığında kullanıcı
nereye döner? İlk ders bu iki sorunun en sert biçimini ele alır.

#### Ne Çözer, Ne Zaman Kullanılmaz

**Kip pencere (modal dialog)**, altındaki içeriği etkisizleştirerek tek bir karara odaklanmayı
zorlayan katmandır. Çözdüğü sorun tektir: **geri alınamaz bir eylemin onayını almak** ya da akışın
devamı için zorunlu bir bilgiyi toplamak. Katalog arayüzünde ödünç alma onayı bu tanıma girer —
kayıt kullanıcının hesabına geçer, iade tarihi belirlenir ve başkasının erişimi kapanır.

Kip pencere kullanılmayacak durumlar daha kalabalıktır ve her biri ayrı bir kalıba aittir:

- **Bilgilendirme.** Sonucu bildirmek için pencere açmak, kullanıcıya gereksiz bir kapatma
  eylemi yükler. Bildirim kalıbı bu işi odağı almadan yapar.
- **Uzun içerik.** Pencerenin içine sığmayan içerik kendi sayfasına aittir; pencere içinde
  kaydırma, dış kaydırmayla çakışır.
- **Çok adımlı iş.** Adımların adresi olmalıdır; kip pencerede açılan bir akış geri tuşuyla
  yönetilemez ve paylaşılamaz.
- **Yardım metni.** Bir alanın açıklaması alanın yanına yazılır; onu bir pencereye koymak
  kullanıcıyı alanı doldurmaktan koparır.

Ayrımın bir de kipsiz karşılığı vardır. **Kipsiz pencere (non-modal dialog)** ekranda durur ama
arkasını etkisizleştirmez; kullanıcı pencereyle sayfa arasında gidip gelebilir. Bir süzgeç paneli
kipsiz olabilir; ödünç alma onayı olamaz.

#### Yerel Öge Önce, ARIA Sonra

İletişim penceresi için işaretlemede ayrılmış bir öge vardır: `dialog`. Kip olarak
açıldığında üç işi kullanıcı aracına devreder — arkadaki içeriğin etkisizleştirilmesi, üst
katmana yerleştirme ve **arka örtü (backdrop)** çizimi. Bu ögeyle kurulan bir pencere
`role="dialog"` rolünü ve kip durumunu örtük olarak taşır; ayrıca ARIA yazılmasına gerek
kalmaz.

Yerel ögenin kullanılamadığı yerde kurulum elle yapılır ve dört parça gerekir:

| Parça | Kaynak |
|---|---|
| Rol | `role="dialog"`, kararı erteleyen uyarılarda `role="alertdialog"` |
| Kip durumu | `aria-modal="true"` |
| Ad | `aria-labelledby` ile pencere başlığına bağlanır |
| Açıklama | `aria-describedby` ile gövdedeki özet cümleye bağlanır |

Adın kaynağı bir tasarım kararıdır. "Onay" başlığı bir ad değildir; pencerenin adı yaptığı
işi söylemelidir: "Ödünç alma onayı". Ekran Okuyucu Deneyimi dersindeki kural burada da
geçerlidir — ada rol adı yazılmaz, yani başlık "Ödünç alma onayı penceresi" olmaz.

`aria-modal` özniteliği tek başına yeterli değildir. Bu öznitelik erişilebilirlik ağacına
"arkası kapalı" bilgisini verir, ama sekme tuşunun nereye gideceğini belirlemez ve fareyle
arkadaki bir düğmeye basılmasını engellemez. Arkadaki içerik ayrıca **etkisizleştirilir
(inert)**: odak alamaz hâle getirilir ve erişilebilirlik ağacından çıkarılır. İki kaynaklı
kurulumun tek kaynağa indiği yer, yerel ögenin kip açılışıdır.

#### Klavye Sözleşmesi

| Tuş | Davranış |
|---|---|
| Sekme | Odağı pencere içindeki bir sonraki ögeye taşır; sondan başa döner |
| Geri sekme | Bir önceki ögeye taşır; baştan sona döner |
| Kaçış | Pencereyi kapatır ve odağı dönüş noktasına gönderir |
| Giriş | Birincil eylemi çalıştırır; metin alanı odaktayken alanın kuralı geçerlidir |

Açılışta odağın nereye konacağı içeriğe bağlıdır. Pencerede tek bir birincil eylem varsa
odak pencerenin kendisine ya da başlığına konur; kullanıcı içeriği baştan okuyabilsin diye.
Odağı doğrudan onay düğmesine koymak, onaylanacak metni atlatır. Pencere bir form
içeriyorsa odak ilk alana gider. Odağın yıkıcı eyleme —"Sil", "İptal et"— konması, kaçış
tuşuyla kapatmayı deneyen kullanıcının giriş tuşuna basmasıyla kaza üretir.

Kapanışta odağın nereye gideceği ise bileşenin belirtimindeki en kritik alandır. **Dönüş
noktası (return point)**, pencereyi açan ögedir ve pencere açılırken kaydedilir. Kapanışta
odak oraya döner; dönmezse odak belgenin başına düşer ve kullanıcı listede kaldığı yeri
kaybeder.

#### Kuşatma ve Dönüş Noktasının Sınanması

Bu davranış sınanabilir, çünkü tümüyle sıralı bir durum makinesidir: sayfanın odak sırası,
pencerenin odak sırası, bir yığıt ve bir dönüş noktası. Aşağıdaki sınama kümesi kalıbın
sözleşmesini maddeler hâlinde doğruluyor.

```js
// kip.test.mjs — kip pencerenin odak kusatmasi ve donus noktasi durum makinesi
import { test } from "node:test";
import assert from "node:assert/strict";

// Sayfanin odaklanabilir ogeleri, belge sirasinda.
const SAYFA = ["arama", "suzgec", "satir-1-odunc", "satir-2-odunc", "altlik-yardim"];

// Kip pencere tanimlari: id -> icindeki odaklanabilir ogeler
const PENCERE = {
  "odunc-onay": ["onay-baslik", "onay-sure", "onay-vazgec", "onay-al"],
  "gecikme-uyari": ["uyari-tamam"],
};

function makine(sayfa = SAYFA) {
  return {
    odak: null,
    yigit: [],              // { pencere, donus } — ic ice acilan pencereler
    kalanlar: [...sayfa],   // silinebilen sayfa ogeleri
    kayit: [],              // duyuru kaydi
  };
}

const ustPencere = (d) => (d.yigit.length ? d.yigit[d.yigit.length - 1] : null);

// Odak dolasiminin kapsami: pencere acikken yalniz o pencerenin ogeleri.
function kapsam(d) {
  const ust = ustPencere(d);
  return ust ? PENCERE[ust.pencere] : d.kalanlar;
}

function odakla(d, id) {
  assert.ok(kapsam(d).includes(id), `${id} kapsam disinda`);
  d.odak = id;
}

function sekme(d, geri = false) {
  const k = kapsam(d);
  const i = k.indexOf(d.odak);
  // Kapsamda olmayan bir odaktan gelinirse basa alinir.
  const sonraki = i < 0 ? 0 : (i + (geri ? -1 : 1) + k.length) % k.length;
  d.odak = k[sonraki];
  return d.odak;
}

function ac(d, pencere) {
  d.yigit.push({ pencere, donus: d.odak });
  d.odak = PENCERE[pencere][0];
  d.kayit.push(`acildi: ${pencere}, donus noktasi: ${d.yigit[d.yigit.length - 1].donus}`);
}

// Donus noktasi kaybolduysa: sayfada kalan en yakin onceki oge, o da yoksa ilk oge.
function donusHedefi(d, istenen) {
  if (istenen && d.kalanlar.includes(istenen)) return istenen;
  const eski = SAYFA.indexOf(istenen);
  for (let i = eski - 1; i >= 0; i--) if (d.kalanlar.includes(SAYFA[i])) return SAYFA[i];
  return d.kalanlar[0] ?? null;
}

function kapat(d) {
  const ust = d.yigit.pop();
  assert.ok(ust, "acik pencere yok");
  if (d.yigit.length) { d.odak = ust.donus; }   // ic pencereden dis pencereye
  else d.odak = donusHedefi(d, ust.donus);
  d.kayit.push(`kapandi: ${ust.pencere}, odak: ${d.odak}`);
  return d.odak;
}

const kacis = (d) => (d.yigit.length ? kapat(d) : d.odak);

test("kip pencere acilinca odak icine girer ve donus noktasi kaydedilir", () => {
  const d = makine();
  odakla(d, "satir-2-odunc");
  ac(d, "odunc-onay");
  assert.equal(d.odak, "onay-baslik");
  assert.equal(ustPencere(d).donus, "satir-2-odunc");
});

test("sekme pencerenin son ogesinden ilk ogesine doner", () => {
  const d = makine();
  odakla(d, "satir-2-odunc");
  ac(d, "odunc-onay");
  const gezi = [d.odak];
  for (let i = 0; i < 4; i++) gezi.push(sekme(d));
  assert.deepEqual(gezi, ["onay-baslik", "onay-sure", "onay-vazgec", "onay-al", "onay-baslik"]);
});

test("geri sekme ilk ogeden son ogeye doner", () => {
  const d = makine();
  odakla(d, "satir-2-odunc");
  ac(d, "odunc-onay");
  assert.equal(sekme(d, true), "onay-al");
});

test("sayfa ogeleri pencere acikken dolasima girmez", () => {
  const d = makine();
  odakla(d, "arama");
  ac(d, "odunc-onay");
  const gorulen = new Set();
  for (let i = 0; i < 12; i++) gorulen.add(sekme(d));
  assert.deepEqual([...gorulen].sort(), ["onay-al", "onay-baslik", "onay-sure", "onay-vazgec"]);
  assert.equal([...gorulen].some((x) => SAYFA.includes(x)), false);
});

test("kacis tusu kapatir ve odagi tetikleyen ogeye dondurur", () => {
  const d = makine();
  odakla(d, "satir-2-odunc");
  ac(d, "odunc-onay");
  sekme(d); sekme(d);
  assert.equal(kacis(d), "satir-2-odunc");
  assert.equal(d.yigit.length, 0);
});

test("ic ice pencereler yigit sirasiyla cozulur", () => {
  const d = makine();
  odakla(d, "satir-1-odunc");
  ac(d, "odunc-onay");
  odakla(d, "onay-al");
  ac(d, "gecikme-uyari");
  assert.equal(d.odak, "uyari-tamam");
  assert.equal(kacis(d), "onay-al");     // dis pencereye doner, sayfaya degil
  assert.equal(kacis(d), "satir-1-odunc");
});

test("donus noktasi silindiyse odak kaybolmaz, onceki ogeye duser", () => {
  const d = makine();
  odakla(d, "satir-2-odunc");
  ac(d, "odunc-onay");
  d.kalanlar = d.kalanlar.filter((x) => x !== "satir-2-odunc");   // satir listeden cikti
  assert.equal(kapat(d), "satir-1-odunc");
  assert.notEqual(d.odak, null);
});

test("donus kaydi acilma ve kapanma sirasini korur", () => {
  const d = makine();
  odakla(d, "suzgec");
  ac(d, "odunc-onay");
  ac(d, "gecikme-uyari");
  kapat(d);
  kapat(d);
  assert.deepEqual(d.kayit, [
    "acildi: odunc-onay, donus noktasi: suzgec",
    "acildi: gecikme-uyari, donus noktasi: onay-baslik",
    "kapandi: gecikme-uyari, odak: onay-baslik",
    "kapandi: odunc-onay, odak: suzgec",
  ]);
});
```

```
✔ kip pencere acilinca odak icine girer ve donus noktasi kaydedilir (0.368291ms)
✔ sekme pencerenin son ogesinden ilk ogesine doner (0.290208ms)
✔ geri sekme ilk ogeden son ogeye doner (0.046375ms)
✔ sayfa ogeleri pencere acikken dolasima girmez (0.07125ms)
✔ kacis tusu kapatir ve odagi tetikleyen ogeye dondurur (0.072875ms)
✔ ic ice pencereler yigit sirasiyla cozulur (0.044875ms)
✔ donus noktasi silindiyse odak kaybolmaz, onceki ogeye duser (0.065125ms)
✔ donus kaydi acilma ve kapanma sirasini korur (0.047292ms)
ℹ tests 8
ℹ suites 0
ℹ pass 8
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 35.956291
```

Süre alanları her koşumda değişir; anlamlı olan sayaçlardır.

Yedinci sınama bu kalıbın en çok atlanan yanını yakalıyor. Ödünç alma onaylandığında satır "ödünç
alınabilir" listesinden çıkabilir; yani dönüş noktası artık yoktur. Bu durumda odak hiçbir yere
konmazsa belgenin başına düşer ve klavye kullanıcısı listedeki yerini kaybeder. Belirtim, dönüş
noktasının kaybolduğu durumda nereye düşüleceğini de yazmak zorundadır: en yakın önceki öge, o da
yoksa listenin başı.

Altıncı sınama iç içe pencerelerin yığıt davranışını doğruluyor. Bir kip pencerenin
içinden ikinci bir pencere açmak kaçınılması gereken bir kurulumdur, ama gerçekleştiğinde
kaçış tuşu **yalnız en üstteki** pencereyi kapatmalı ve odağı bir alttakine bırakmalıdır.
İki pencereyi birden kapatan kod, kullanıcının yarım kalan işini de kapatır.

#### Ölçülebilir Kısıtlar

Kalıbın uygunluğu dört ölçütle sınanır ve her birinin somut bir eşiği vardır.

**2.1.2 Klavye Tuzağı Yok.** Odak kuşatması ile klavye tuzağı arasındaki fark, Klavye
Erişimi dersinde tanımlanmıştı ve tek bir koşula bağlıdır: kuşatmadan **standart bir tuşla**
çıkılabiliyorsa kuşatmadır, çıkılamıyorsa tuzaktır. Kaçış tuşuyla kapanmayan bir kip
pencere, sekme dolaşımı doğru çalışsa bile bu ölçütü karşılamaz.

**2.4.3 Odak Sırası.** Pencere açıldığında odağın pencere içine girmesi ve kapandığında
dönüş noktasına dönmesi bu ölçütün gereğidir. Sınama kümesindeki birinci ve beşinci
maddeler doğrudan bu ölçüde karşılık gelir.

**2.4.11 Odak Görünürlüğü (Kısmen Örtülmemiş).** Odaklanan öge, yazarın eklediği bir
katmanla tümüyle örtülmüş olamaz. Kip pencerede bu ölçüt iki yönden ihlal edilebilir:
pencerenin kendisi sayfadaki odaklı bir ögeyi örtebilir, ya da pencere içinde yapışkan bir
altlık şeridi alttaki eylem düğmesini örtebilir. Ölçüt "hiç görünmüyor" eşiğine bağlıdır;
kısmi örtülme AAA düzeyinde ayrıca ele alınır.

**2.5.8 Hedef Boyutu (En Az).** Kapatma düğmesi kip pencerenin en küçük hedefidir ve en az
24 x 24 piksellik bir alan kaplamalıdır. Simge çizimi 16 piksel olsa bile etkin alan bu
eşiğe genişletilir; Ekran Boyutu ve Girdi Türü dersindeki hedef boyutu kararı burada bir
uygunluk gereğine dönüşür.

Beşinci bir kısıt ölçüt numarasına bağlı değildir ama ölçülebilir: kapatma yolu **birden
fazla** olmalıdır. Kaçış tuşu, kapatma düğmesi ve —yıkıcı olmayan pencerelerde— örtüye
tıklama. Örtüye tıklamayla kapanma, veri girilen pencerelerde yanlışlıkla kapanma üretir;
bu yüzden form içeren pencerelerde kapatılır.

#### Sık Yapılan Hata ve Nasıl Anlaşılır

**Arka plan kaydırması sürüyor.** Pencere açıkken tekerlek hareketi arkadaki listeyi
kaydırıyorsa etkisizleştirme eksiktir. Anlaşılması: pencere açıkken sayfayı kaydırmayı
denemek yeter.

**Kuşatma yalnız sekme tuşuna kurulmuş.** Sekme dolaşımı pencerede tutulur, ama tarayıcının
adres alanına çıkıp geri döndüğünde odak arkadaki sayfaya girer. Anlaşılması: kuşatmanın
odak olayına değil yalnız tuş olayına bağlandığını kodda görmek; ya da pencere açıkken
belgedeki gizlenmemiş bağlantı sayısını saymak.

**Dönüş noktası kaydedilmemiş.** Kullanıcı ödünç alma penceresini kapattığında odak sayfa
başına düşüyorsa dönüş noktası hiç tutulmamıştır. Anlaşılması: pencereyi açıp kapattıktan
sonra bir kez sekme tuşuna basmak — odak listenin ortasında değil, sayfanın ilk ögesinde
devam ediyorsa kusur oradadır.

**Başlık ada bağlanmamış.** Pencerenin görsel başlığı var ama `aria-labelledby` bağı yoksa
pencere adsız açılır. Anlaşılması: erişilebilirlik ağacında pencerenin ad alanının boş
olması; bu, otomatik denetleyicilerin varlık kuralıyla yakaladığı bir kusurdur.

#### Özet

- Kip pencere yalnız geri alınamaz bir onay ya da akışın devamı için zorunlu bir bilgi
  gerektiğinde kullanılır; bilgilendirme, uzun içerik, çok adımlı iş ve yardım metni başka
  kalıplara aittir.
- Yerel iletişim ögesi kip olarak açıldığında rolü, kip durumunu, etkisizleştirmeyi ve
  arka örtüyü kullanıcı aracına devreder; elle kurulumda rol, kip durumu, ad ve açıklama
  ayrı ayrı bağlanır.
- Odak kuşatması standart bir tuşla çıkılabildiği sürece kuşatmadır; çıkılamadığında
  klavye tuzağına dönüşür ve 2.1.2 ölçütünü ihlal eder.
- Dönüş noktası pencere açılırken kaydedilir; dönüş noktası kaybolduğunda nereye düşüleceği
  de belirtimin parçasıdır.
- İç içe pencerelerde kaçış tuşu yalnız en üstteki pencereyi kapatır ve odağı bir alttakine
  bırakır.
- Kalıbın uygunluğu 2.1.2, 2.4.3, 2.4.11 ve 2.5.8 ölçütleriyle sınanır; kapatma düğmesi en
  az 24 x 24 piksellik etkin alana genişletilir.

#### Sonraki Adım

Kip pencere odağı isteyerek devraldı ve kullanıcı onu isteyerek açtı. Sıradaki katman ise
tam tersidir: kullanıcı bir ögenin üstüne geldiğinde ya da odaklandığında kendiliğinden
belirir, odağı almaz ve kısa sürede kaybolur. Bu kendiliğindenlik onu en çok yanlış kurulan
bileşen yapar — fareyle çalışan bir kurulum klavyeye ve dokunmaya hiç ulaşmaz, kaybolan bir
katmanın içindeki metin okunamadan gider. Sonraki ders araç ipucunun hangi koşullarda
görünmesi, ne kadar kalması ve nasıl kapatılabilmesi gerektiğini bir ölçüte bağlar.

### Araç İpuçları

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/arac-ipuclari

Araç ipucunun belirtimi; ipucu ile açıklama baloncuğu ayrımı, 1.4.13 ölçütünün üç koşulunun kural denetimi, işaretçinin ipucuna ulaşmasının geometriyle hesaplanması ve dokunma erişiminin çözümü.

Kip pencere odağı isteyerek devraldı; kullanıcı onu bir düğmeye basarak açtı ve kaçış
tuşuyla kapattı. Araç ipucu tam tersi bir bileşendir: kullanıcı istemeden belirir, odağı
almaz, kısa sürer ve çoğu kurulumda yalnız işaretçiyle çalışır.

Bu kendiliğindenlik onu en çok yanlış kurulan bileşen yapar. Fareyle çalışan bir kurulum
klavyeyle hiç açılmaz; dokunmatik ekranda üzerine gelme diye bir durum olmadığı için ipucu
hiç görünmez; kendiliğinden kaybolan bir katmanın içindeki metin okunmadan gider. Bu ders
ipucunu üç koşula bağlar ve koşullardan birini geometriyle hesaplar.

#### Ne Çözer, Ne Zaman Kullanılmaz

**Araç ipucu (tooltip)**, adı zaten olan bir denetime **ek açıklama** getiren katmandır.
Katalog arayüzünde ödünç alma satırındaki "iade tarihi" başlığının yanındaki simge düğme
buna örnektir: başlık kendi başına anlaşılır, ipucu tarihin nasıl hesaplandığını ekler.

İki kural bu tanımdan çıkar. Birincisi, **ipucundaki bilgi hiçbir zaman zorunlu
değildir**; onsuz da görev tamamlanabilmelidir. İkincisi, **ipucu denetimin adı olamaz**;
adı yalnız ipucunda bulunan bir düğme, ipucu görünmediğinde adsızdır.

Kullanılmayacak durumlar:

- **Etkileşimli içerik.** İçinde bağlantı, düğme ya da girdi olan bir katman ipucu değildir;
  o katmana odak gitmesi gerekir ve gittiği anda ipucunun sözleşmesi bozulur.
- **Uzun metin.** Birkaç cümlelik açıklama, alanın yanında duran kalıcı bir yardım metnine
  aittir; kaybolabilen bir katmanda okunamaz.
- **Hata iletisi.** Hata kalıcı olmalıdır ve alanla ilişkilendirilmelidir; üzerine
  gelindiğinde beliren bir hata iletisi görülmeden kaybolur.

Karşı kalıp **açıklama baloncuğu (toggletip)**dur: gerçek bir düğmeyle açılır, tıklamayla
çalışır, içeriği bir canlı bölgeye yazıldığı için duyurulur ve kullanıcı kapatana kadar
durur. Ayrım tetikleyicidedir — üzerine gelme ve odakla açılan katman ipucu, tıklamayla
açılan katman baloncuktur. Dokunmatik ekranda üzerine gelme olmadığı için, bilginin
dokunmayla da gerekli olduğu her yerde doğru kalıp baloncuktur.

#### Yerel Öge Önce, ARIA Sonra

İşaretlemede bu iş için ayrılmış bir öznitelik vardır: `title`. Tanımı gereği **danışma
niteliğinde** bilgi taşır ve nasıl sunulacağı kullanıcı aracına bırakılmıştır. Sunumun
yazara bırakılmaması, ipucunun uyması gereken ölçütün tam olarak yazardan istediği şeyi —
görünme süresini, kapatılabilmesini, üzerine gelinebilmesini — denetlenemez kılar. Bu
yüzden araç ipucu, yerel karşılığın belirtimi karşılamadığı ender kalıplardan biridir ve
elle kurulur.

Elle kurulumda dört bağ vardır:

| Parça | Kaynak |
|---|---|
| Katmanın rolü | `role="tooltip"` |
| Denetimle bağ | Tetikleyicide `aria-describedby`, ipucu kutusunun kimliğine |
| Denetimin adı | İpucundan bağımsız; simge düğmede `aria-label` ile verilir |
| Görünürlük | Katman gizliyken erişilebilirlik ağacında da bulunmaz |

Ad ile açıklamanın ayrımı burada kritiktir. Yalnız simge taşıyan bir düğmenin ipucu çoğu
zaman düğmenin adıyla aynı metni gösterir. Bu durumda metin bir açıklama değil bir addır ve
`aria-describedby` ile bağlanması ada eklenmeyen ikinci bir kopya üretir; doğru kurulum
metni `aria-labelledby` ile ada bağlamak ve ayrıca açıklama vermemektir.

Görünen metnin adın içinde bulunması ayrı bir ölçüte bağlıdır: **2.5.3 Addaki Etiket**. Sesle
denetim kullanan biri ekranda okuduğu sözcüğü söyler; ad o sözcüğü içermiyorsa denetim
çağrılamaz. İpucunda "Uzatma koşulları" yazıp adı "Yardım" olan bir düğme bu ölçütü
karşılamaz.

#### Klavye ve Dokunma Sözleşmesi

| Girdi | Davranış |
|---|---|
| Odak | Tetikleyici odak aldığında ipucu görünür, odak çıkınca gizlenir |
| İşaretçi | Üzerine gelindiğinde görünür; işaretçi ipucunun üstüne geçebilir |
| Kaçış | İpucunu gizler; odak tetikleyicide kalır |
| Dokunma | İpucu görünmez; bilgi gerekliyse baloncuk kalıbına geçilir |

İpucuna sekme tuşuyla gidilmez ve içine odak konmaz. Odak sırası tetikleyicide durur;
ipucu yalnız görünür.

Gecikme iki yönlüdür. Açılma gecikmesi işaretçinin bir dizi ögenin üstünden geçerken art
arda ipucu açmasını engeller. Kapanma gecikmesi işaretçinin tetikleyiciden ipucuna geçerken
aradaki boşlukta kalması durumunda ipucunun kaybolmasını engeller. İkincisi bir tercih
değil, ölçütün gereğidir.

#### Üç Koşulun ve Geometrinin Denetimi

**1.4.13 Üzerine Gelme veya Odakla Beliren İçerik** ölçütü üç koşul koyar: içerik
**kapatılabilir** olmalı (imleci ya da odağı taşımadan gizlenebilmeli), **üzerine
gelinebilir** olmalı (işaretçi içeriğin üstüne kaybolmadan geçebilmeli) ve **kalıcı** olmalı
(üzerine gelme, odak ya da kapatma sona erene kadar durmalı). Üçü ayrı ayrı denetlenir;
biri eksikse ölçüt karşılanmaz.

Üzerine gelinebilirlik koşulu geometriye bağlıdır: işaretçi tetikleyiciden ipucuna giderken
aradaki boşlukta "üzerinde değil" durumuna düşerse ipucu kaybolur. Aşağıdaki betik önce üç koşulu
kural olarak denetliyor, sonra işaretçinin izleyeceği iki yolu tarayıp kaç pikselinin katmanların
dışında kaldığını ölçüyor.

```js
// ipucu.mjs — arac ipucunun uc kosulu ve isaretcinin ipucuna ulasabilmesi

// --- (a) 1.4.13 uc kosulun kural denetimi -----------------------------------
// kapatilabilir: kacis tusu icerigi gizler
// kalici: kendiliginden gizlenme yok (otomatikGizlemeMs === null)
// uzerine gelinebilir: isaretci tetikleyiciden ipucuna gecerken icerik gizlenmiyor
const YAPILAR = [
  { ad: "yalniz uzerine gelme", kacisKapatir: false, otomatikGizlemeMs: 2000, gecisAlani: false },
  { ad: "odak + uzerine gelme", kacisKapatir: false, otomatikGizlemeMs: null, gecisAlani: false },
  { ad: "kacisla kapanan",      kacisKapatir: true,  otomatikGizlemeMs: 5000, gecisAlani: true  },
  { ad: "tam belirtim",         kacisKapatir: true,  otomatikGizlemeMs: null, gecisAlani: true  },
];

console.log("yapi                  kapatilabilir  kalici  uzerine gelinebilir  1.4.13");
for (const y of YAPILAR) {
  const kapatilabilir = y.kacisKapatir;
  const kalici = y.otomatikGizlemeMs === null;
  const gelinebilir = y.gecisAlani;
  console.log(
    `${y.ad.padEnd(21)} ${(kapatilabilir ? "evet" : "hayir").padStart(13)} ` +
      `${(kalici ? "evet" : "hayir").padStart(7)} ${(gelinebilir ? "evet" : "hayir").padStart(20)}  ` +
      `${kapatilabilir && kalici && gelinebilir ? "gecer" : "KALIR"}`,
  );
}

// --- (b) Isaretcinin ipucuna ulasmasi ---------------------------------------
// Tetikleyici: 24 x 24 piksellik simge dugme (satir sonundaki "ne demek" dugmesi).
// Ipucu kutusu: 180 x 36.
const TETIK = { x: 620, y: 300, g: 24, y2: 24 };
const IPUCU = { g: 180, y2: 36 };

const icinde = (n, k) => n.x >= k.x && n.x <= k.x + k.g && n.y >= k.y && n.y <= k.y + k.y2;
const ortusme = (a, b) => Math.max(0, Math.min(a.x + a.g, b.x + b.g) - Math.max(a.x, b.x));

// Bir kirikli yolu 0.5 px adimla tara; kutular birlesimi disinda kalan uzunlugu dondur.
function kopukUzunluk(nokta, kutular) {
  let kopuk = 0;
  for (let p = 0; p < nokta.length - 1; p++) {
    const [a, b] = [nokta[p], nokta[p + 1]];
    const uzunluk = Math.hypot(b.x - a.x, b.y - a.y);
    if (uzunluk === 0) continue;
    const adim = Math.ceil(uzunluk / 0.5);
    let disarida = 0;
    for (let i = 0; i <= adim; i++) {
      const n = { x: a.x + ((b.x - a.x) * i) / adim, y: a.y + ((b.y - a.y) * i) / adim };
      if (!kutular.some((k) => icinde(n, k))) disarida++;
    }
    kopuk += (disarida * uzunluk) / adim;
  }
  return kopuk;
}

function yerlesim({ yon, hiza, bosluk, kopru }) {
  const y = yon === "alt" ? TETIK.y + TETIK.y2 + bosluk : TETIK.y - bosluk - IPUCU.y2;
  const x =
    hiza === "basi" ? TETIK.x
    : hiza === "orta" ? TETIK.x + TETIK.g / 2 - IPUCU.g / 2
    : TETIK.x + TETIK.g - IPUCU.g;
  const ipucu = { x, y, g: IPUCU.g, y2: IPUCU.y2 };
  const kutular = [{ ...TETIK }];
  const ortak = ortusme(TETIK, ipucu);
  if (kopru && bosluk > 0 && ortak > 0) {
    kutular.push({
      x: Math.max(TETIK.x, ipucu.x),
      y: yon === "alt" ? TETIK.y + TETIK.y2 : TETIK.y - bosluk,
      g: ortak,
      y2: bosluk,
    });
  }
  kutular.push(ipucu);
  return { ipucu, kutular, ortak };
}

const merkez = (k) => ({ x: k.x + k.g / 2, y: k.y + k.y2 / 2 });

const SENARYOLAR = [
  { ad: "alt / ortalanmis / 0 px",         yon: "alt", hiza: "orta", bosluk: 0,  kopru: false },
  { ad: "alt / ortalanmis / 12 px",        yon: "alt", hiza: "orta", bosluk: 12, kopru: false },
  { ad: "alt / ortalanmis / 12 px + kopru",yon: "alt", hiza: "orta", bosluk: 12, kopru: true  },
  { ad: "alt / basa hizali / 8 px + kopru",yon: "alt", hiza: "basi", bosluk: 8,  kopru: true  },
  { ad: "ust / sona hizali / 8 px + kopru",yon: "ust", hiza: "sonu", bosluk: 8,  kopru: true  },
  { ad: "alt / kaydirilmis / 8 px + kopru",yon: "alt", hiza: "kaydirilmis", bosluk: 8, kopru: true },
];

console.log("\nyerlesim                         yatay ortusme  dik yol  dogrudan yol  uzerine gelinebilir");
for (const s of SENARYOLAR) {
  // "kaydirilmis": ipucu tetikleyicinin sag disina tasmis (ekran kenari duzeltmesi hatasi)
  const ayar = s.hiza === "kaydirilmis" ? { ...s, hiza: "basi" } : s;
  let { ipucu, kutular, ortak } = yerlesim(ayar);
  if (s.hiza === "kaydirilmis") {
    ipucu = { ...ipucu, x: TETIK.x + TETIK.g + 4 };
    ortak = ortusme(TETIK, ipucu);
    kutular = [{ ...TETIK }, ipucu];
  }
  const m = merkez(ipucu);
  const b = merkez(TETIK);
  const dik = kopukUzunluk([b, { x: b.x, y: m.y }, m], kutular);
  const dogrudan = kopukUzunluk([b, m], kutular);
  console.log(
    `${s.ad.padEnd(32)} ${(ortak.toFixed(0) + " px").padStart(13)} ` +
      `${(dik.toFixed(1) + " px").padStart(8)} ${(dogrudan.toFixed(1) + " px").padStart(13)}  ` +
      `${dik === 0 ? "evet" : "HAYIR"}`,
  );
}

// --- (c) Tetikleyicinin hedef boyutu ----------------------------------------
console.log(`\ntetikleyici hedefi: ${TETIK.g} x ${TETIK.y2} px, 2.5.8 esigi 24 x 24: ` +
  `${TETIK.g >= 24 && TETIK.y2 >= 24 ? "gecer" : "kalir"}`);
```

```
yapi                  kapatilabilir  kalici  uzerine gelinebilir  1.4.13
yalniz uzerine gelme          hayir   hayir                hayir  KALIR
odak + uzerine gelme          hayir    evet                hayir  KALIR
kacisla kapanan                evet   hayir                 evet  KALIR
tam belirtim                   evet    evet                 evet  gecer

yerlesim                         yatay ortusme  dik yol  dogrudan yol  uzerine gelinebilir
alt / ortalanmis / 0 px                  24 px   0.0 px        0.0 px  evet
alt / ortalanmis / 12 px                 24 px  11.5 px       11.5 px  HAYIR
alt / ortalanmis / 12 px + kopru         24 px   0.0 px        0.0 px  evet
alt / basa hizali / 8 px + kopru         24 px   0.0 px       32.4 px  evet
ust / sona hizali / 8 px + kopru         24 px   0.0 px       32.4 px  evet
alt / kaydirilmis / 8 px + kopru          0 px  42.0 px       46.3 px  HAYIR

tetikleyici hedefi: 24 x 24 px, 2.5.8 esigi 24 x 24: gecer
```

İlk tablo koşulların bağımsız olduğunu gösteriyor. Kaçış tuşuyla kapanan bir ipucu, beş
saniye sonra kendiliğinden gizleniyorsa **kalıcı** değildir ve ölçüt yine karşılanmaz.
Yavaş okuyan bir kullanıcı için beş saniye bir güvence değil, bir sınırdır.

İkinci tablo üzerine gelinebilirlik koşulunu piksel cinsinden veriyor. Yol taraması 0.5 piksellik
adımlarla yapıldığı için sonuçlar bu adımın yarısı kadar aşağıdan yuvarlanır; 12 piksellik boşluk
11.5 piksel kopukluk olarak görünür.

Üç sonuç çıkar. **Boşluk bırakmak yeter bir kusurdur:** on iki piksellik görsel boşluk,
işaretçinin on iki piksel boyunca "üzerinde değil" durumuna düşmesi demektir. **Görünmez
geçiş alanı boşluğu kapatır:** iki kutunun yatay örtüşmesi kadar genişlikte, boşluğu
dolduran bir alan, dik yolu kopmasız kılar. **Yatay örtüşme sıfıra düştüğünde hiçbir alan
yardım etmez:** son satırda ipucu ekran kenarına sığmadığı için tetikleyicinin sağ dışına
kaydırılmış ve iki kutunun ortak sütunu kalmamıştır; işaretçinin ipucuna ulaşacağı hiçbir
dik yol yoktur.

Dördüncü ve beşinci satırlar ölçütü geçiyor ama doğrudan yolda otuz iki piksel kopukluk
gösteriyor. Bu, uygunluk kusuru değil kullanılabilirlik maliyetidir: ipucunun merkezine
nişan alan bir kullanıcı tetikleyicinin dışına çıkar. İpucunun tetikleyiciye **ortalanması**
bu maliyeti sıfırlar; ölçüt yalnız bir yolun varlığını ister, tasarım doğal yolun da
çalışmasını ister.

#### Ölçülebilir Kısıtlar

**1.4.13** üç koşulu birlikte arar; tablo bu üçünü ayrı sütunlar hâlinde denetler.

**2.5.8 Hedef Boyutu (En Az).** Simge tetikleyici 24 x 24 piksellik eşiği karşılamalıdır;
16 piksellik bir simge çevresindeki etkin alanla bu boyuta genişletilir.

**1.4.3 Kontrast (En Az)** ve **1.4.11 Metin Dışı Kontrast.** İpucu kutusunun metni
zeminine karşı 4.5:1 oranını, kutunun kenarlığı ve varsa oku çevresindeki yüzeye karşı 3:1
oranını karşılamalıdır. Katman altındaki içeriğin üstüne düştüğü için ipucunun zemini
saydam olamaz.

**1.4.4 Metnin Yeniden Boyutlandırılması.** Metin iki katına çıkarıldığında ipucu kutusu
içeriğini kırpmadan büyümelidir; sabit yükseklikli bir ipucu bu eşikte metni keser.

**1.4.12 Metin Aralığı.** Satır yüksekliği ve harf aralığı artırıldığında kutu yine
taşmamalıdır. İki ölçüt birlikte, ipucu kutusunun içerikle büyüyen bir kutu olmasını
zorunlu kılar.

#### Sık Yapılan Hata ve Nasıl Anlaşılır

**İpucu yalnız üzerine gelmeyle açılıyor.** Klavye kullanıcısı bilgiye hiç ulaşamaz.
Anlaşılması: tetikleyiciye sekme tuşuyla gidip ipucunun belirip belirmediğine bakmak.

**İpucu düğmenin tek adı.** Simge düğmenin `aria-label` değeri yoksa ve ad yalnız ipucunda
yazılıysa, düğme erişilebilirlik ağacında adsızdır. Anlaşılması: ipucunu hiç açmadan
düğmenin ad alanını okumak; otomatik denetleyicilerin varlık kuralı bu kusuru yakalar.

**İpucu içinde bağlantı var.** Kullanıcı bağlantıya ulaşmak için işaretçiyi ipucuna
taşımak zorundadır ve klavyeyle hiç ulaşamaz. Anlaşılması: ipucu içeriğinde odaklanabilir
öge aramak; bulunuyorsa kalıp yanlış seçilmiştir.

**Süreli gizlenme.** İpucu birkaç saniye sonra kendiliğinden kayboluyorsa ölçütün kalıcılık
koşulu karşılanmaz. Anlaşılması: ipucunu açıp beklemek; kaybolan bir ipucu kusurludur.

#### Özet

- Araç ipucu adı zaten olan bir denetime ek açıklama getirir; içindeki bilgi hiçbir zaman
  zorunlu olamaz ve ipucu denetimin adı yerine geçemez.
- Etkileşimli içerik, uzun metin ve hata iletisi ipucuna girmez; tıklamayla açılan ve
  kullanıcı kapatana kadar duran açıklama baloncuğu bu işler için ayrı bir kalıptır.
- `title` özniteliğinin sunumu kullanıcı aracına bırakıldığı için ölçütün istediği
  denetim yazarda kalmaz; ipucu elle kurulur ve `role="tooltip"` ile `aria-describedby`
  bağını taşır.
- 1.4.13 üç bağımsız koşul arar: kapatılabilir, üzerine gelinebilir, kalıcı. Kaçış tuşuyla
  kapanan ama süreyle gizlenen bir ipucu ölçütü karşılamaz.
- Üzerine gelinebilirlik geometriyle hesaplanır: tetikleyici ile ipucu arasındaki boşluk
  görünmez bir geçiş alanıyla kapatılmalı, yatay örtüşme sıfıra düşmemelidir.
- İpucunun tetikleyiciye ortalanması, ölçütün istemediği ama doğal işaretçi yolunun
  gerektirdiği ek bir karardır.

#### Sonraki Adım

Araç ipucu kullanıcının bir ögeye yönelmesiyle belirdi; yani ne zaman görüneceğini kullanıcı
belirledi. Sıradaki katman bu bağı da koparır: sistem bir şey olduğunda bildirimi kendisi
gönderir, kullanıcı ekranın neresine bakıyor olursa olsun. Burada üç yeni soru doğar —
bildirim ne kadar ekranda kalmalı, aynı anda gelen iki bildirim nasıl sıraya girmeli ve
bildirim görsel kanalın dışına nasıl çıkmalı? Sonraki ders görünme süresini metnin okunma
hızından hesaplar ve duyuru kuyruğunu art arda gelen bildirimlerle sınar.

### Bildirim Şeritleri ve Anlık Bildirimler

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/bildirim-seritleri-ve-anlik-bildirimler

Bildirim şeridi ile anlık bildirimin ayrımı, görünme süresinin metnin okunma hızından hesaplanması, art arda gelen olayların birleştirilmesi ve kibar–atılgan kuyruğunun kayıp ölçümü.

Araç ipucunu kullanıcı açtı: bir ögeye yöneldi ve katman belirdi. Bildirimde bu bağ yoktur.
Sistem bir şey olduğunda haberi kendisi gönderir; kullanıcı ekranın neresine bakıyorsa
oraya bakmaktadır, hatta ekrana hiç bakmıyor olabilir.

Bu, üç yeni soru getirir. Bildirim ne kadar ekranda kalmalıdır — kaybolan bir metin
okunmadan gitmiş sayılır. Aynı anda gelen iki bildirim ne yapmalıdır — biri diğerini
bastırırsa ne kaybedilir? Ve görsel kanalın dışındaki kullanıcı bildirimi nasıl alır? Bu
ders üçünü de hesaplanabilir bir kısıta bağlar.

#### Ne Çözer, Ne Zaman Kullanılmaz

İki ayrı kalıp vardır ve karıştırılmaları en yaygın kusurdur.

**Bildirim şeridi (notification banner)**, sayfa akışının içinde duran, kullanıcı ya da
sistem kaldırana kadar kalan bir bölümdür. İçeriği geçerliliğini koruduğu sürece durur:
"Üç kaydınızın iade tarihi geçti", "Katalog bakım nedeniyle salt okunur". Kalıcı olduğu
için içinde eylem bulunabilir ve içine odak gidebilir.

**Anlık bildirim (toast)**, akışın üstünde beliren, süresi dolunca kendiliğinden kaybolan
katmandır. Yalnız **tamamlanmış** ve **geri alınabilir** bir eylemi bildirir: "Kayıt ödünç
alındı".

Kullanılmayacak durumlar tek bir kuraldan türer: **kaybolan bir katman, kullanıcının
yapması gereken hiçbir şeyi taşıyamaz.**

- **Kurtarma gerektiren hata** anlık bildirime konmaz; kullanıcı ekranda değilse hatayı hiç
  görmez ve sistem sessizce başarısız olmuş olur.
- **Onay isteyen soru** anlık bildirime konmaz; süre dolduğunda soru cevapsız kalır.
- **Kayıt tutulması gereken bilgi** anlık bildirime konmaz; bildirimin ayrıca kalıcı bir
  listede karşılığı olmalıdır.

Anlık bildirimde eylem bulunabilir mi? Bulunabilir, ama tek bir koşulla: eylem **isteğe
bağlı** olmalıdır. "Geri al" böyledir — kullanıcı hiçbir şey yapmazsa sonuç yine
tutarlıdır. "Yeniden dene" değildir; hiçbir şey yapılmazsa istek kaybolur.

#### Yerel Öge Önce, ARIA Sonra

Bildirim için ayrılmış bir yerel öge yoktur; kurulum rollerle yapılır.

| Parça | Kaynak |
|---|---|
| Kibar bildirim | `role="status"` (örtük `aria-live="polite"`) |
| Atılgan bildirim | `role="alert"` (örtük `aria-live="assertive"`) |
| Biriken kayıt | `role="log"` — sıralı bildirim listesi; eski kayıtlar kaybolabilir |
| Şeridin adı | Şerit bir bölümse `aria-labelledby` ile başlığına bağlanır |

Ekran Okuyucu Deneyimi dersinde konan kural burada bir kurulum gereğidir: **canlı bölgenin
kabı belgede önceden bulunmalıdır.** Bildirim geldiğinde kabı da içeriğiyle birlikte
eklemek, bildirimin duyurulmamasına yol açabilir. Doğru kurulum, sayfa kurulurken boş bir
kap koymak ve içine yalnız metni yazmaktır.

İkinci kural önceliğin seçimidir. Kibar öncelik sıraya girer; atılgan öncelik bekleyenleri atar.
Ödünç alma onayı kibardır; kullanıcının işini durduran bir hata atılgandır. Her bildirimi atılgan
yapmak, önceliği anlamsızlaştırır ve aşağıdaki ölçümün gösterdiği kaybı üretir.

#### Süre Metinden Hesaplanır

Anlık bildirimin süresi bir tasarım tercihi değil, metnin uzunluğundan türeyen bir
niceliktir. Model üç payı toplar: dikkatin bildirime yönelmesi, metnin okunması ve —eylem
varsa— eyleme ulaşıp karar verme. Sonuç bir alt ve bir üst sınıra kıstırılır.

```js
// bildirim.mjs — gorunme suresinin metinden hesabi, birlestirme ve duyuru kuyrugu

// Model degerleri (secilmis parametreler): tepki payi, okuma hizi, eylem payi.
const TEPKI = 700;          // ms — dikkatin bildirime yonelmesi
const OKUMA_HIZI = 200;     // kelime/dakika — ekrandan okuma icin secilen deger
const EYLEM = 1500;         // ms — bildirimdeki eyleme ulasip karar verme payi
const EN_AZ = 4000, EN_COK = 10000;

const kelime = (m) => m.trim().split(/\s+/).length;
function sure(m, eylemVar) {
  const ham = TEPKI + (kelime(m) / OKUMA_HIZI) * 60000 + (eylemVar ? EYLEM : 0);
  return { ham, gorunme: Math.min(EN_COK, Math.max(EN_AZ, Math.round(ham / 100) * 100)) };
}

const ORNEKLER = [
  { m: "Kayıt ödünç alındı.", eylem: false },
  { m: "Kayıt ödünç alındı. İade tarihi 11 Ağustos.", eylem: false },
  { m: "Kayıt ödünç alındı. İade tarihi 11 Ağustos.", eylem: true },
  { m: "Süzgeç güncellendi; listede 128 kayıt var.", eylem: false },
  { m: "Ödünç alma isteği gönderilemedi. Bağlantı koptuğu için istek tamamlanamadı; yeniden denemek için düğmeyi kullanın.", eylem: true },
];

console.log("bildirim metni                                             kelime  eylem  ham sure  gorunme");
for (const o of ORNEKLER) {
  const s = sure(o.m, o.eylem);
  const kisa = o.m.length > 55 ? o.m.slice(0, 52) + "..." : o.m;
  console.log(
    `${kisa.padEnd(58)} ${String(kelime(o.m)).padStart(6)}  ${(o.eylem ? "var" : "yok").padStart(5)}  ` +
      `${(s.ham.toFixed(0) + " ms").padStart(8)}  ${(s.gorunme + " ms").padStart(7)}`,
  );
}

// --- Art arda gelen olaylar: birlestirilmis ve birlestirilmemis --------------
const OLAYLAR = [
  { an: 0,    tur: "odunc",  konu: "Işık ve Gölge" },
  { an: 350,  tur: "odunc",  konu: "Ağ Protokolleri El Kitabı" },
  { an: 900,  tur: "odunc",  konu: "Kuzey Yamaç Ölçümleri" },
  { an: 4200, tur: "suzgec", konu: "128" },
  { an: 4600, tur: "suzgec", konu: "42" },
  { an: 5100, tur: "suzgec", konu: "7" },
  { an: 7000, tur: "hata",   konu: "Kuzey Yamaç Ölçümleri" },
];

const BIRLESTIRME_PENCERESI = 1500;   // ms — ayni turden olaylarin toplandigi aralik
const ONCELIK = { odunc: "kibar", suzgec: "kibar", hata: "atilgan" };
const EYLEM_VAR = { odunc: true, suzgec: false, hata: true };

const metinYaz = (tur, konular) => {
  if (tur === "odunc")
    return konular.length === 1 ? `${konular[0]} ödünç alındı.` : `${konular.length} kayıt ödünç alındı.`;
  if (tur === "suzgec") return `Süzgeç güncellendi; listede ${konular[konular.length - 1]} kayıt var.`;
  return `${konular[0]} ödünç alınamadı. Kaydın kopyası kalmadığı için istek reddedildi.`;
};

function bildirimler(olaylar, birlestir) {
  if (!birlestir)
    return olaylar.map((o) => ({ an: o.an, tur: o.tur, metin: metinYaz(o.tur, [o.konu]) }));
  const gruplar = [];
  for (const o of olaylar) {
    const son = gruplar[gruplar.length - 1];
    if (son && son.tur === o.tur && o.an - son.sonAn <= BIRLESTIRME_PENCERESI) {
      son.konular.push(o.konu);
      son.sonAn = o.an;
    } else gruplar.push({ tur: o.tur, konular: [o.konu], sonAn: o.an });
  }
  // Birlestirilen grup, penceresi kapandiginda yazilir.
  return gruplar.map((g) => ({ an: g.sonAn, tur: g.tur, metin: metinYaz(g.tur, g.konular) }));
}

function kuyruk(liste) {
  const gelen = [...liste].sort((a, b) => a.an - b.an);
  let i = 0, etkin = null, bekleyen = [];
  let atilan = 0, kesilen = 0, tam = 0;
  const kayit = [];

  const baslat = (b, an) => {
    const s = sure(b.metin, EYLEM_VAR[b.tur]).gorunme;
    etkin = { ...b, bas: an, bitis: an + s, sure: s };
    kayit.push({ an, oncelik: ONCELIK[b.tur], bekleme: an - b.an, metin: b.metin, sure: s });
  };

  while (i < gelen.length || bekleyen.length || etkin) {
    const sonrakiOlay = i < gelen.length ? gelen[i].an : Infinity;
    const bitis = etkin ? etkin.bitis : Infinity;
    if (bitis <= sonrakiOlay) {
      tam++;
      etkin = null;
      if (bekleyen.length) baslat(bekleyen.shift(), bitis);
      continue;
    }
    if (sonrakiOlay === Infinity) break;
    const b = gelen[i++];
    if (ONCELIK[b.tur] === "atilgan") {
      atilan += bekleyen.length;
      bekleyen = [];
      if (etkin) { kesilen++; kayit[kayit.length - 1].kesik = b.an - etkin.bas; }
      baslat(b, b.an);
    } else if (!etkin) baslat(b, b.an);
    else bekleyen.push(b);
  }
  return { atilan, kesilen, tam, kayit };
}

for (const birlestir of [false, true]) {
  const liste = bildirimler(OLAYLAR, birlestir);
  const s = kuyruk(liste);
  console.log(`\n${birlestir ? "birlestirilmis" : "her olay ayri bildirim"}: ` +
    `${OLAYLAR.length} olay -> ${liste.length} bildirim -> ${s.kayit.length} gosterim`);
  console.log("    an  oncelik  bekleme    gorunme  metin");
  for (const k of s.kayit) {
    const gorunme = k.kesik === undefined ? `${k.sure} ms` : `${k.kesik}/${k.sure}`;
    console.log(`  ${String(k.an).padStart(4)}  ${k.oncelik.padEnd(7)} ${(k.bekleme ? k.bekleme + " ms" : "—").padStart(8)} ` +
      `${gorunme.padStart(10)}  ${k.metin}`);
  }
  console.log(`  hic gosterilmeyen: ${s.atilan}, kesilen: ${s.kesilen}, tam gorunen: ${s.tam}`);
}
```

```
bildirim metni                                             kelime  eylem  ham sure  gorunme
Kayıt ödünç alındı.                                             3    yok   1600 ms  4000 ms
Kayıt ödünç alındı. İade tarihi 11 Ağustos.                     7    yok   2800 ms  4000 ms
Kayıt ödünç alındı. İade tarihi 11 Ağustos.                     7    var   4300 ms  4300 ms
Süzgeç güncellendi; listede 128 kayıt var.                      6    yok   2500 ms  4000 ms
Ödünç alma isteği gönderilemedi. Bağlantı koptuğu iç...        14    var   6400 ms  6400 ms

her olay ayri bildirim: 7 olay -> 7 bildirim -> 3 gosterim
    an  oncelik  bekleme    gorunme  metin
     0  kibar          —    4000 ms  Işık ve Gölge ödünç alındı.
  4000  kibar    3650 ms  3000/4000  Ağ Protokolleri El Kitabı ödünç alındı.
  7000  atilgan        —    5500 ms  Kuzey Yamaç Ölçümleri ödünç alınamadı. Kaydın kopyası kalmadığı için istek reddedildi.
  hic gosterilmeyen: 4, kesilen: 1, tam gorunen: 2

birlestirilmis: 7 olay -> 3 bildirim -> 3 gosterim
    an  oncelik  bekleme    gorunme  metin
   900  kibar          —    4000 ms  3 kayıt ödünç alındı.
  5100  kibar          —  1900/4000  Süzgeç güncellendi; listede 7 kayıt var.
  7000  atilgan        —    5500 ms  Kuzey Yamaç Ölçümleri ödünç alınamadı. Kaydın kopyası kalmadığı için istek reddedildi.
  hic gosterilmeyen: 0, kesilen: 1, tam gorunen: 2
```

İlk tablo süreyi metne bağlıyor. Üç kelimelik bir bildirimin ham süresi 1.6 saniyedir ve
alt sınıra çekilir; on dört kelimelik hata iletisi 6.4 saniye ister. Alt sınırın varlığı
kısa bildirimlerin göz kırpar gibi geçmesini, üst sınırın varlığı da uzun metinlerin
ekranda kalıcılaşmasını engeller. Üçüncü satır eylem payını gösteriyor: aynı metin "Geri al"
düğmesi taşıdığında 4.3 saniyeye çıkar, çünkü kullanıcının okuması yetmez, karar da vermesi
gerekir.

Üst sınıra dayanan bir bildirim bir işarettir: metin bir bildirime göre uzundur ve şeride
ya da kalıcı bir listeye aittir.

İkinci ve üçüncü bloklar aynı yedi olayı iki farklı kurulumla işliyor. **Birleştirmesiz
kurulumda yedi olaydan yalnız üçü ekrana çıkıyor**; dördü hiç gösterilmiyor ve biri
kesiliyor. Daha kötüsü
ikinci satırdadır: kullanıcı ikinci kaydı ödünç aldıktan 3.65 saniye sonra onun bildirimini
görüyor. Bildirim, bildirdiği eylemden bu kadar sonra geldiğinde kullanıcı onu üçüncü işlemin
sonucu sanır.

**Birleştirilmiş kurulumda yedi olay üç bildirime iniyor ve hiçbiri kaybolmuyor.** İki
kural bunu sağlıyor. Aynı türden olaylar bir pencere boyunca toplanıyor ve tek bir sayılı
metne dönüşüyor: üç ayrı "ödünç alındı" yerine "3 kayıt ödünç alındı". Ardışık süzgeç
değişimlerinde ise **yalnız son durum** yazılıyor; ara değerler bildirilmiyor, çünkü
kullanıcı için anlamlı olan son sayıdır.

İki kurulumda da atılgan hata bildirimi etkin bildirimi kesiyor. Bu, önceliğin tanımı
gereğidir ve düzeltilecek bir kusur değildir; düzeltilecek olan, kesilen bilginin başka bir
yerde bulunmasıdır. Kesilen bildirimin karşılığı kalıcı bir kayıt listesinde durursa kayıp
gerçek olmaktan çıkar.

#### Ölçülebilir Kısıtlar

**2.2.1 Ayarlanabilir Zamanlama.** Kendiliğinden kaybolan bir bildirim bir zaman sınırıdır.
Ölçüt, sınırın kapatılabilmesini, ayarlanabilmesini ya da uzatılabilmesini ister. Pratikte
bunun karşılığı üç kuraldır: bildirimin üstüne gelindiğinde ya da odak içine girdiğinde
sayaç durur; bildirim elle kapatılabilir; ve içeriği kalıcı bir listede de bulunur.

**4.1.3 Durum İletileri.** Bildirim, odak taşımadan bildirilmelidir. Odağı bildirime taşıyan
kod ölçütü ters yönden kaldırır: kullanıcı yaptığı işten koparılır.

**2.2.2 Duraklat, Durdur, Gizle.** Bildirim bir yerden kayarak giriyorsa bu hareket beş
saniyeden kısa sürmeli ya da durdurulabilmelidir. Aynı ölçüt, ekranda biriken bir bildirim
kaydının kendiliğinden ilerlemesini de kapsar.

**1.4.13.** Bildirim şeridi sayfanın üstünde konumlanıp altındaki içeriği örtüyorsa, önceki
dersteki üç koşul burada da geçerlidir; özellikle kapatılabilirlik.

**Konum ve kaydırma.** Anlık bildirim ekranın bir köşesinde belirir ve o köşedeki içeriği
örter. Örtülen içerik odaklı bir öge ise 2.4.11 ölçütü ihlal edilir. Bildirim alanının
altına ayrılmış boşluk bir tasarım süsü değil, bu ölçütün gereğidir.

#### Sık Yapılan Hata ve Nasıl Anlaşılır

**Her olaya bir bildirim.** Toplu işlemde bildirimler üst üste yığılır. Anlaşılması: birden
çok kaydı hızla ödünç alıp ekranda kaç bildirim belirdiğini saymak; sayı işlem sayısına
eşitse birleştirme yoktur.

**Hata anlık bildirimde.** İstek başarısız olduğunda kullanıcı ekranda değilse hiçbir iz
kalmaz. Anlaşılması: hatayı üretip beklemek; süre dolduktan sonra ekranda hatanın izi
kalmıyorsa kalıp yanlıştır.

**Bildirim kabı sonradan ekleniyor.** Duyuru bazen çalışır bazen çalışmaz. Anlaşılması:
sayfa ilk kurulduğunda canlı bölge kabının belgede bulunup bulunmadığına bakmak.

**Sabit süre.** Bütün bildirimler aynı süre kalıyorsa uzun metinler okunmadan gider.
Anlaşılması: en uzun bildirim metnini kelime sayısıyla ölçüp modelin verdiği süreyle
karşılaştırmak.

#### Özet

- Bildirim şeridi akışın içinde durur ve kalıcıdır; anlık bildirim akışın üstünde belirir
  ve kendiliğinden kaybolur.
- Kaybolan bir katman kullanıcının yapması gereken hiçbir şeyi taşıyamaz; anlık bildirimde
  yalnız isteğe bağlı bir eylem bulunabilir.
- Canlı bölge kabı belgede önceden bulunur; kibar öncelik sıraya girer, atılgan öncelik ise
  bekleyenleri atar ve bu ayrım her bildirimi atılgan yapan kurulumda anlamsızlaşır.
- Görünme süresi metnin kelime sayısından, tepki payından ve eylem payından hesaplanır; alt
  ve üst sınıra kıstırılır ve üst sınıra dayanan metin bildirime değil şeride aittir.
- Art arda gelen aynı türden olaylar bir pencerede birleştirilir; sayılabilir olaylar tek
  sayılı metne, ardışık durum değişimleri yalnız son duruma indirgenir.
- Kendiliğinden kaybolma bir zaman sınırıdır: üzerine gelindiğinde durmalı, elle
  kapatılabilmeli ve içeriği kalıcı bir listede de bulunmalıdır.

#### Sonraki Adım

Bildirim bir işin **bittiğini** söyledi. Aynı işin sürdüğü aralıkta ekranda ne olduğu ise
ayrı bir kalıptır. Bekleme göstergesi iki temel biçime ayrılır: ne kadar süreceği
bilinmeyen bir işin sürdüğünü bildiren gösterge ile tamamlanma oranı hesaplanabilen bir
işin ilerlemesini gösteren gösterge. İkisinin rolü, duyurusu ve güncelleme sıklığı farklıdır
— ve ikincisinde yüzdenin nasıl hesaplandığı, kalan sürenin nasıl kestirildiği kullanıcının
bekleyip beklememe kararını belirler. Sonraki ders bu iki göstergeyi belirtim olarak yazar.

### Yükleme Göstergeleri

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/yukleme-gostergeleri

Belirsiz ve belirli göstergenin belirtimi, ilerleme değerinin ve kalan süre kestiriminin hesaplanması, kestirimin kararsızlığının ölçülmesi ve ilerleme duyurularının sıklık politikası.

Önceki ders bir işin **bittiğini** bildiren katmanı kurdu. İşin sürdüğü aralıkta ekranda ne
olduğu ayrı bir kalıptır ve iki temel biçime ayrılır.

**Belirsiz gösterge (indeterminate indicator)** yalnız bir şey söyler: bir iş sürüyor.
Tamamlanma oranı bilinmediğinde kullanılır. **Belirli gösterge (determinate indicator)**
ise oranı da söyler; hesaplanabilir bir bütünün ne kadarının bittiğini bildirir. Aradaki
fark bir görsel biçim farkı değildir: rolleri, taşıdıkları değerler, duyuruları ve
güncelleme sıklıkları farklıdır. Bu ders ikisini belirtim olarak yazar ve belirli
göstergenin en çok yanlış kurulan parçasını —kalan süre kestirimini— ölçer.

#### Ne Çözer, Ne Zaman Kullanılmaz

Gösterge iki soruyu yanıtlar: sistem çalışıyor mu ve ne kadar kaldı? Birinci soru her
beklemede vardır; ikincisi yalnız bütünün bilindiği işlerde yanıtlanabilir.

Kullanılmayacak durumlar ölçülmüş sürelere bağlıdır. Yükleme ve Boş Durumlar dersinde
katalog aramasının yanıt süreleri ölçülmüş ve isteklerin çoğunun bir saniyenin altında
tamamlandığı görülmüştü; o aralıkta gösterge açmak titreme üretir. Karar oradaki gecikme ve
en az görünme kurallarına bağlanır; bu ders o karardan **sonrasını** yazar.

İki kural daha vardır. **Bütün bilinmiyorsa belirli gösterge kurulmaz**; yüzdeyi uydurmak,
kullanıcıya yanlış bir bitiş vaat eder. **Gelecek içeriğin yerini tutmak gerekiyorsa
gösterge yetmez**; küçük bir gösterge içerik geldiğinde yerleşim kaymasını engellemez ve o
iş sonraki dersin kalıbına aittir.

#### Yerel Öge Önce, ARIA Sonra

İşaretlemede bu iş için ayrılmış bir öge vardır: `progress`. Değer verildiğinde belirli,
değer verilmediğinde belirsiz gösterge olur; rolü ve değer alanlarını kendisi taşır.

Elle kurulumda alanlar tek tek verilir:

| Parça | Belirli gösterge | Belirsiz gösterge |
|---|---|---|
| Rol | `role="progressbar"` | `role="progressbar"` |
| Değer | `aria-valuenow` | verilmez |
| Sınırlar | `aria-valuemin`, `aria-valuemax` | verilmez |
| Okunabilir değer | `aria-valuetext` (örneğin "24 MB'ın 8 MB'ı") | verilmez |
| Ad | `aria-labelledby` ya da `aria-label` |  aynı |
| Bölgenin durumu | Yenilenen bölümde `aria-busy="true"` | aynı |

İki ayrım kritiktir. Birincisi, **`aria-valuenow` alanının bulunmaması belirsizliğin
bildirilme yoludur**; sıfır yazmak "hiç ilerlemedi" demektir ve yanlıştır. İkincisi,
`aria-valuetext` yalnız ham sayı anlamsız olduğunda yazılır: yüzde anlaşılırken "yüzde 40"
metnini ayrıca yazmak duyuruyu iki katına çıkarır.

Göstergenin kendisi bir canlı bölge **değildir**. İlerleme değerinin her değişimini
duyurmak, aşağıdaki ölçümün gösterdiği duyuru selini üretir. Duyuru, göstergenin yanındaki
ayrı bir kibar bölgeden ve seyrek yapılır.

#### Klavye ve Odak Sözleşmesi

Gösterge etkileşimli değildir; odak almaz ve sekme sırasında yer tutmaz. Klavye sözleşmesi
göstergenin kendisinde değil, çevresindedir.

**İptal düğmesi.** On saniyeyi aşan işlerde iptal zorunludur ve iptal düğmesi
odaklanabilir olmalıdır. İptal edildiğinde odak, gösterge kaybolduğu için bir yere
taşınmalıdır: işi başlatan düğmeye.

**Yer değiştiren odak.** Gösterge, odaklı bir ögenin bulunduğu bölümün yerine konursa odak
kaybolur ve belgenin başına düşer. Doğru kurulum, bölümü değiştirmeden içine gösterge
koymak ya da odağı bilinçli olarak taşımaktır. Kip Pencereler dersindeki dönüş noktası
kuralı burada da geçerlidir.

**Devre dışı bırakılan denetimler.** İş sürerken düğmeyi devre dışı bırakmak, odağı o
düğmede olan kullanıcının odağını düşürebilir. Yerine `aria-disabled` ile işaretlemek ve
tıklamayı yok saymak odağı korur.

#### Değer, Kalan Süre ve Duyuru Sıklığı

Yüzde hesabı basit ve güvenilirdir: biriken miktarın bütüne oranı. Kalan süre ise bir
**kestirimdir** ve kestirimin kalitesi ölçülebilir. Aşağıdaki betik yirmi dört megabaytlık
bir katalog dışa aktarmasını dört evreli bir akışla üretiyor, iki kestirim yöntemini
karşılaştırıyor ve duyuru sıklığı politikalarını sınıyor.

```js
// ilerleme.mjs — belirli ilerlemenin yuzdesi, kalan sure kestirimi ve duyuru sikligi

// Katalog disa aktarmasi: 24 MB, 200 ms'lik adimlarla gelen parcalar.
const TOPLAM = 24 * 1024 * 1024;
const ADIM = 200;                    // ms

// Yinelenebilir ornek: dogrusal esleyici uretec.
function uretec(tohum) {
  let s = tohum >>> 0;
  return () => ((s = (1664525 * s + 1013904223) >>> 0) / 4294967296);
}
const rastgele = uretec(20260728);

// Dort evre: yavas baslangic, hizli akis, duraklama, hizli bitis.
const EVRE = [
  { adim: 10, taban: 180_000, oynama: 0.5 },
  { adim: 20, taban: 900_000, oynama: 0.3 },
  { adim: 8,  taban: 40_000,  oynama: 0.8 },
  { adim: 40, taban: 700_000, oynama: 0.3 },
];

const artis = [];
for (const e of EVRE)
  for (let i = 0; i < e.adim; i++)
    artis.push(Math.max(0, Math.round(e.taban * (1 + e.oynama * (rastgele() * 2 - 1)))));

// Toplam tam olarak dolana kadar kes / tamamla.
let toplam = 0;
const gelen = [];
for (const a of artis) {
  if (toplam >= TOPLAM) break;
  const parca = Math.min(a, TOPLAM - toplam);
  toplam += parca;
  gelen.push(parca);
}
if (toplam < TOPLAM) gelen.push(TOPLAM - toplam);

// --- Yuzde ve kalan sure kestirimi ------------------------------------------
const ALFA = 0.25;                   // ustel agirlikli ortalamanin agirligi
const KOVA = 5000;                   // ms — gosterilen kestirimin yuvarlanma adimi
let birikmis = 0, hiz = null, oncekiGosterilen = Infinity;
const satir = [];
for (let i = 0; i < gelen.length; i++) {
  birikmis += gelen[i];
  const gecen = (i + 1) * ADIM;
  const yuzde = (birikmis / TOPLAM) * 100;
  const anlikHiz = gelen[i] / ADIM;                       // bayt/ms
  hiz = hiz === null ? anlikHiz : ALFA * anlikHiz + (1 - ALFA) * hiz;
  const naif = yuzde > 0 ? (gecen * (100 - yuzde)) / yuzde : Infinity;
  const agirlikli = hiz > 0 ? (TOPLAM - birikmis) / hiz : Infinity;
  // Gosterilen kestirim: kovaya yuvarlanir ve hicbir zaman artmaz.
  const gosterilen = Math.min(oncekiGosterilen, Math.ceil(agirlikli / KOVA) * KOVA);
  oncekiGosterilen = gosterilen;
  satir.push({ i: i + 1, gecen, yuzde, naif, agirlikli, gosterilen });
}

const gercekSure = gelen.length * ADIM;
console.log(`toplam ${(TOPLAM / 1024 / 1024).toFixed(0)} MB, ${gelen.length} adim, gercek sure ${gercekSure} ms`);
console.log("\nadim  gecen(ms)  yuzde   naif kalan  agirlikli kalan  gosterilen  gercek kalan");
for (const s of satir) {
  if (![1, 5, 10, 15, 25, 30, 34, 38, satir.length].includes(s.i)) continue;
  const yaz = (v) => (v === Infinity ? "—" : Math.round(v) + " ms");
  console.log(
    `${String(s.i).padStart(4)} ${String(s.gecen).padStart(10)} ${s.yuzde.toFixed(1).padStart(6)} ` +
      `${yaz(s.naif).padStart(12)} ${yaz(s.agirlikli).padStart(16)} ${yaz(s.gosterilen).padStart(11)} ` +
      `${(gercekSure - s.gecen + " ms").padStart(13)}`,
  );
}

// Kestirimlerin kararliligi: kalan surenin artmasi, en buyuk sicrama, gercekten sapma
function kararlilik(alan) {
  let artan = 0, enBuyuk = 0, sapmaToplam = 0, degisim = 0;
  for (let i = 1; i < satir.length; i++) {
    const fark = satir[i][alan] - satir[i - 1][alan];
    if (fark > 0) { artan++; enBuyuk = Math.max(enBuyuk, fark); }
    if (fark !== 0) degisim++;
    sapmaToplam += Math.abs(satir[i][alan] - (gercekSure - satir[i].gecen));
  }
  return { artan, enBuyuk, degisim, ortSapma: sapmaToplam / (satir.length - 1) };
}
console.log("\nkestirim      artan adim  en buyuk sicrama  ekran degisimi  ortalama mutlak sapma");
for (const [ad, alan] of [["naif", "naif"], ["agirlikli", "agirlikli"], ["gosterilen", "gosterilen"]]) {
  const k = kararlilik(alan);
  console.log(`${ad.padEnd(13)} ${String(k.artan).padStart(10)} ${(Math.round(k.enBuyuk) + " ms").padStart(17)} ` +
    `${String(k.degisim).padStart(15)} ${(Math.round(k.ortSapma) + " ms").padStart(22)}`);
}

// --- Duyuru sikligi ----------------------------------------------------------
// Iki duyuru arasindaki en az aralik, duyuru metninin okunma suresidir (onceki dersin modeli).
const TEPKI = 700, OKUMA_HIZI = 200;
const DUYURU = "yüzde 40 tamamlandı";
const EN_AZ_ARALIK = Math.round(TEPKI + (DUYURU.trim().split(/\s+/).length / OKUMA_HIZI) * 60000);
console.log(`\nduyuru metni: "${DUYURU}" -> en az aralik ${EN_AZ_ARALIK} ms`);
const POLITIKA = [
  { ad: "her adimda", esik: null, sure: null },
  { ad: "her %1", esik: 1, sure: null },
  { ad: "her %10", esik: 10, sure: null },
  { ad: "her %25", esik: 25, sure: null },
  { ad: "en az 2 sn arayla", esik: null, sure: 2000 },
  { ad: "her %10 + en az 2 sn", esik: 10, sure: 2000 },
];

console.log("politika            duyuru sayisi  en kisa aralik  okunamayan duyuru");
for (const p of POLITIKA) {
  const anlar = [];
  let sonYuzde = -Infinity, sonAn = -Infinity;
  for (const s of satir) {
    const yuzdeUygun = p.esik === null ? true : Math.floor(s.yuzde / p.esik) > Math.floor(sonYuzde / p.esik);
    const sureUygun = p.sure === null ? true : s.gecen - sonAn >= p.sure;
    if (p.esik === null && p.sure === null) { anlar.push(s.gecen); sonYuzde = s.yuzde; sonAn = s.gecen; continue; }
    if (yuzdeUygun && sureUygun) { anlar.push(s.gecen); sonYuzde = s.yuzde; sonAn = s.gecen; }
  }
  let enKisa = Infinity, okunamayan = 0;
  for (let i = 1; i < anlar.length; i++) {
    const aralik = anlar[i] - anlar[i - 1];
    enKisa = Math.min(enKisa, aralik);
    if (aralik < EN_AZ_ARALIK) okunamayan++;
  }
  console.log(`${p.ad.padEnd(20)} ${String(anlar.length).padStart(13)} ` +
    `${((enKisa === Infinity ? "—" : enKisa + " ms")).padStart(15)} ${String(okunamayan).padStart(18)}`);
}
```

```
toplam 24 MB, 45 adim, gercek sure 9000 ms

adim  gecen(ms)  yuzde   naif kalan  agirlikli kalan  gosterilen  gercek kalan
   1        200    0.6     33474 ms         33474 ms    35000 ms       8800 ms
   5       1000    3.9     24647 ms         25221 ms    30000 ms       8000 ms
  10       2000    7.5     24808 ms         23986 ms    25000 ms       7000 ms
  15       3000   27.8      7806 ms          4395 ms     5000 ms       6000 ms
  25       5000   65.0      2698 ms          1971 ms     5000 ms       4000 ms
  30       6000   81.8      1336 ms          1047 ms     5000 ms       3000 ms
  34       6800   82.6      1436 ms          2813 ms     5000 ms       2200 ms
  38       7600   82.8      1574 ms          7764 ms     5000 ms       1400 ms
  45       9000  100.0         0 ms             0 ms        0 ms          0 ms

kestirim      artan adim  en buyuk sicrama  ekran degisimi  ortalama mutlak sapma
naif                  11           2424 ms              44                4839 ms
agirlikli             13           2795 ms              44                5365 ms
gosterilen             0              0 ms               6                6405 ms

duyuru metni: "yüzde 40 tamamlandı" -> en az aralik 1600 ms
politika            duyuru sayisi  en kisa aralik  okunamayan duyuru
her adimda                      45          200 ms                 44
her %1                          36          200 ms                 35
her %10                         11          400 ms                  8
her %25                          5         1200 ms                  1
en az 2 sn arayla                5         2000 ms                  0
her %10 + en az 2 sn             5         2000 ms                  0
```

Birinci tablo kestirimin ne kadar kötü olduğunu gösteriyor. İlk adımda iki yöntem de otuz üç
saniye söylüyor; gerçek kalan sekiz virgül sekiz saniyedir. Otuz dördüncü ve otuz sekizinci
adımlarda —akışın duraksadığı evrede— ağırlıklı kestirim iki virgül sekizden yedi virgül sekiz
saniyeye çıkarken gerçek kalan iki virgül ikiden bir virgül dört saniyeye inmiştir. Duraksama
boyunca kestirim **yükselir**, yani kullanıcı ekranda "kalan süre artıyor" görür.

İkinci tablo bunu üç sayıda topluyor. Naif kestirim kırk beş adımın on birinde artıyor,
ağırlıklı kestirim on üçünde. Ağırlıklı yöntemin ortalama mutlak sapması naif yöntemden
**daha kötü** çıkıyor: son ölçüme ağırlık vermek, duraksamayı geleceğe yayıyor. Buradan
çıkan sonuç, daha iyi bir kestirim yöntemi aramak değil, kestirimin sunumunu değiştirmektir.

Üçüncü sütun bunu yapıyor. Gösterilen değer beş saniyelik kovalara yuvarlanıyor ve **hiçbir
zaman artmıyor**. Sonuç: sıfır artış, kırk beş adımda yalnız altı ekran değişimi. Bedeli
üçüncü sütunun son alanında görünür — ortalama mutlak sapma 6405 milisaniyeye, yani iki
yöntemden de yükseğe çıkar. Bu bilinçli bir takastır: kalan süre zaten güvenilir değildir,
o hâlde **yanlış yöne gitmeyen ve seyrek değişen** bir sunum, doğruya daha yakın ama
zıplayan bir sunumdan iyidir.

Yüzdenin kendisi bu sorundan muaftır: biriken miktar azalmadığı için yüzde de azalmaz.
Yüzdenin geri gittiği tek durum bütünün sonradan değişmesidir; bu durumda gösterge belirli
olmaktan çıkar ve belirsize düşmelidir.

Son tablo duyuru sıklığını bağlıyor. Her adımda duyuru kırk beş duyuru üretiyor ve
kırk dördü bir öncekinin okunma süresi dolmadan geliyor — yani hiçbiri okunmuyor. Yalnız
yüzde eşiğine bakan politikalar da yetmiyor: hızlı evrede yüzde on eşiği dört yüz
milisaniyede iki kez aşılıyor. Yeterli olan, **zaman eşiği**dir; iki saniyelik aralık
duyuru sayısını beşe indiriyor ve okunamayan duyuruyu sıfıra çekiyor. Yüzde eşiği ile
birlikte uygulandığında sonuç aynı kalıyor, çünkü kısıtlayan koşul zamandır.

#### Ölçülebilir Kısıtlar

**4.1.3 Durum İletileri.** İlerleme bildirimi odak taşımadan duyurulmalıdır; duyuru
göstergenin kendisinden değil, yanındaki kibar bölgeden yapılır ve sıklığı yukarıdaki
politikayla sınırlanır.

**2.2.2 Duraklat, Durdur, Gizle.** Beş saniyeden uzun süren ve kendiliğinden hareket eden
bir gösterge duraklatılabilmeli ya da gizlenebilmelidir. Belirsiz göstergenin dönme
hareketi bu ölçütün kapsamına girer; hareketi bir kısıtlama sorgusuyla azaltmak, ölçütü
karşılamanın en ucuz yoludur — hareket yerine soluk alıp veren ya da hiç kıpırdamayan bir
gösterge konur.

**2.3.1 Üç Yanıp Sönme.** Gösterge saniyede üçten fazla yanıp sönmemelidir. Hızlı yanıp
sönen bir yükleme animasyonu bu eşiği aşabilir.

**1.4.11 Metin Dışı Kontrast.** İlerleme çubuğunun dolu kısmı ile boş kanalı arasındaki
oran en az 3:1 olmalıdır; ayrıca çubuğun kendisi çevresindeki yüzeye karşı ayırt
edilebilmelidir. Yalnız açık ve koyu tonla ayrılan ince bir çubuk bu eşiği kaçırır.

**2.5.8 Hedef Boyutu (En Az).** İptal düğmesi 24 x 24 piksellik eşiği karşılamalıdır.

#### Sık Yapılan Hata ve Nasıl Anlaşılır

**Belirsiz göstergeye sıfır değer yazmak.** `aria-valuenow="0"` yazan belirsiz bir
gösterge, "hiç ilerlemedi" bilgisini yayar. Anlaşılması: ilerleme sürerken değerin sabit
sıfır kalması.

**Göstergeyi canlı bölge yapmak.** Her değer değişiminde duyuru üretilir. Anlaşılması:
duyuru sayısını adım sayısıyla karşılaştırmak; ikisi eşitse politika yoktur.

**Kalan süreyi saniye saniye göstermek.** Sayı ileri geri zıplar ve güven kaybettirir.
Anlaşılması: kalan süre alanının artıp artmadığını izlemek; bir kez bile arttıysa sunum
kusurludur.

**Yüzdeyi zamandan uydurmak.** Bütün bilinmediği hâlde geçen süreye bakarak yüzde üretmek,
yüzde 90'da takılan bir çubuk üretir. Anlaşılması: göstergenin değerinin gerçek bir
ölçümle mi yoksa sayaçla mı beslendiğine bakmak.

#### Özet

- Belirsiz gösterge yalnız işin sürdüğünü, belirli gösterge tamamlanma oranını bildirir;
  belirsizlik `aria-valuenow` alanının **bulunmamasıyla** anlatılır, sıfır yazılarak değil.
- Gösterge etkileşimli değildir ve odak almaz; klavye sözleşmesi iptal düğmesinde ve
  göstergenin yerini aldığı bölümdeki odağın korunmasındadır.
- Yüzde güvenilirdir ve azalmaz; kalan süre bir kestirimdir ve ölçümde iki yöntem de
  saniyeler mertebesinde sapma üretir, ağırlıklı yöntem duraksamada daha da kötüleşir.
- Kestirim, kovalara yuvarlanıp hiç artmayacak biçimde sunulduğunda ekran değişimi kırk
  dörtten altıya iner; bedeli daha büyük bir sapmadır ve bilinçli ödenir.
- İlerleme duyurusu göstergeden değil ayrı bir kibar bölgeden yapılır ve zaman eşiğine
  bağlanır; yalnız yüzde eşiği hızlı evrelerde okunamayan duyuru üretir.
- Kalıp 4.1.3, 2.2.2, 2.3.1, 1.4.11 ve 2.5.8 ölçütleriyle sınanır; hareketi azaltma
  tercihine uyan bir gösterge en ucuz uygunluk yoludur.

#### Sonraki Adım

Bu dersin göstergesi "çalışıyorum" dedi ama gelecek içeriğin **yerini** tutmadı. İçerik
geldiğinde altındaki her şey aşağı itilirse kullanıcı baktığı noktayı kaybeder; ölçüldüğünde
bu itilme bir sayıya dönüşür. Sonraki ders yer tutan yükleme biçimini —iskelet ekranı—
belirtim olarak yazar: iskeletin erişilebilirlik ağacında nasıl görünmesi gerektiğini,
tek bir bölge yerine birden çok bölge yenilendiğinde kaymanın nasıl biriktiğini ve
iskeletin taahhüt ettiği yapının yanlış çıkmasının ne maliyet ürettiğini hesaplar.

### İskelet Ekranlar

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/iskelet-ekranlar

İskelet ekranın belirtimi; erişilebilirlik ağacındaki yeri, çok bölgeli yüklemede kümülatif düzen kaymasının oturum penceresiyle hesaplanması ve iskeletin taahhüt ettiği yapının yanlış çıkmasının maliyeti.

Önceki dersin göstergesi "çalışıyorum" dedi ama gelecek içeriğin yerini tutmadı. Katalog
sayfası açıldığında ekranda beliren şey tek bir bölüm değildir: üst şerit, süzgeç paneli,
sonuç listesi ve öneriler ayrı zamanlarda gelir. Her biri geldiğinde altındaki her şey
aşağı itilir.

**İskelet ekran (skeleton screen)**, gelecek içeriğin yerini önceden tutan gri kutulardan
oluşan yükleme görünümüdür. Yükleme ve Boş Durumlar dersinde tek bir kararsız öge üzerinden
ölçülmüş ve iskeletin satır sayısı görünür alana göre belirlenmişti. Bu ders aynı kalıbı
belirtim olarak yazar ve ölçümü iki yönde derinleştirir: iskeletin erişilebilirlik ağacında
ne olduğu ve birden çok bölge ayrı zamanlarda geldiğinde kaymanın nasıl **biriktiği**.

#### Ne Çözer, Ne Zaman Kullanılmaz

İskelet iki iş yapar. Yer ayırarak yerleşim kaymasını engeller ve gelecek içeriğin
yapısını önceden göstererek beklemenin daha kısa algılanmasını sağlar. İkinci iş bir
algı etkisidir ve ölçülmesi zordur; birinci iş sayıya çevrilebilir ve kararın dayanağı
odur.

Kullanılmayacak durumlar üç tanedir:

- **Sonuç gelmeme olasılığı yüksekse.** İskelet dokuz satır çizip üç sonuç geldiğinde
  kullanıcı bir an dokuz sonuç olduğunu sanır; hiç sonuç gelmezse iskelet düpedüz yalan
  söylemiş olur.
- **Yapı bilinmiyorsa.** İskelet, çizdiği kutuların yerini taahhüt eder. Gelecek içeriğin
  satır yüksekliği ya da alan sayısı değişkense taahhüt tutmaz ve kayma yine oluşur.
- **Bekleme algı eşiğinin altındaysa.** Yüz milisaniyede gelen bir içerik için çizilen
  iskelet, kendisi bir titreme kaynağıdır.

#### Yerel Öge Önce, ARIA Sonra

İskeletin işaretlemede karşılığı yoktur; kutular yalnız görsel yer tutuculardır. Belirtimin
kritik yanı da budur: **iskelet ögeleri erişilebilirlik ağacında bulunmamalıdır.**

| Parça | Kaynak |
|---|---|
| İskelet kutuları | `aria-hidden="true"` — anlam taşımayan süsleyici ögeler |
| Yenilenen bölüm | `aria-busy="true"`, içerik geldiğinde kaldırılır |
| Durum bildirimi | Ayrı bir kibar bölge: "Sonuçlar yükleniyor" -> "12 kayıt listeleniyor" |
| Odak | Bölümün kendisi değiştirilmez; içindeki odak korunur |

Gerekçe doğrudandır. İskelet kutuları ağaçta göründüğünde ekranda okunacak bir şey varmış
gibi bildirilir; oysa hiçbiri metin taşımaz. Simge Kullanımı dersindeki süsleyici öge kuralı
burada tam olarak geçerlidir: anlam taşımayan görsel gizlenir.

Bekleme bilgisi bu yüzden iskeletten değil, **durum bölgesinden** gelir. Bölge sayfa
kurulurken konur, yükleme başladığında kısa bir cümle alır ve içerik geldiğinde sonuç
sayısıyla güncellenir. Bildirim Şeritleri dersindeki iki kural burada da geçerlidir: bölge
önceden bulunmalı ve son durumu bildirmelidir.

#### Kayma Tek Bir Olay Değil, Bir Toplamdır

Yerleşim kayması puanı Yükleme ve Boş Durumlar dersinde tanımlanmıştı: kayan içeriğin
etkilediği alanın görüntü alanına oranı ile kat edilen uzaklığın en büyük kenara oranının
çarpımı. Frontend Kalitesi kursunda bu puanın **oturum penceresi** içinde toplandığı
eklenmişti: bir pencere ilk kaymayla başlar, bir saniyeden uzun bir boşlukta ya da beş
saniye dolduğunda kapanır; **kümülatif düzen kayması** pencerelerin en büyüğüdür.

Aşağıdaki betik dört bölgeli bir katalog sayfasını dört ayrı iskelet kurulumuyla
yüklüyor ve her kurulumun kaymalarını pencerelere ayırıyor.

```js
// iskelet.mjs — cok bolgeli yuklemede kumulatif duzen kaymasi ve oturum penceresi

const GORUNTU = { g: 1280, y: 800 };
const ALTLIK = 80;

// Sayfa, gorsel sirada bolgelerden olusur. Her bolge kendi aninda gercek yuksekligine gecer.
const TASARIM = [
  { ad: "uyari seridi",  an: 6500, gercek: 64,  iskelet: 0   },
  { ad: "ust serit",     an: 400,  gercek: 96,  iskelet: 96  },
  { ad: "suzgec paneli", an: 700,  gercek: 120, iskelet: 120 },
  { ad: "sonuc listesi", an: 1500, gercek: 648, iskelet: 648 },  // 9 satir x 72 px
  { ad: "oneriler",      an: 2200, gercek: 240, iskelet: 240 },
];

// Senaryolar: her bolgenin baslangic (yer tutucu) yuksekligi ve gercek yuksekligi.
const SENARYO = {
  "iskeletsiz": (b) => ({ bas: b.ad === "sonuc listesi" ? 120 : 0, son: b.gercek }),
  "kismi iskelet": (b) => ({ bas: b.ad === "sonuc listesi" ? b.iskelet : 0, son: b.gercek }),
  "tam iskelet": (b) => ({ bas: b.iskelet, son: b.gercek }),
  "iskelet 9 satir, 3 sonuc": (b) => ({
    bas: b.iskelet,
    son: b.ad === "sonuc listesi" ? 3 * 72 : b.gercek,
  }),
};

function kaymalar(senaryo) {
  const kur = SENARYO[senaryo];
  const yukseklik = TASARIM.map((b) => kur(b).bas);
  const olaylar = TASARIM.map((b, i) => ({ i, an: b.an, ad: b.ad, son: kur(b).son }))
    .sort((a, b) => a.an - b.an);

  const sonuc = [];
  for (const o of olaylar) {
    const delta = o.son - yukseklik[o.i];
    if (delta === 0) { yukseklik[o.i] = o.son; continue; }

    // Degisen bolgenin altinda kalan icerigin ust kenari ve toplam yuksekligi
    const ustKenar = yukseklik.slice(0, o.i).reduce((a, b) => a + b, 0);
    const oncekiAltUst = ustKenar + yukseklik[o.i];
    const altToplam = yukseklik.slice(o.i + 1).reduce((a, b) => a + b, 0) + ALTLIK;
    const sonrakiAltUst = oncekiAltUst + delta;

    // Etki payi: kayan icerigin once ve sonraki gorunur alanlarinin birlesimi
    const gorunur = (ust) => [Math.max(0, Math.min(GORUNTU.y, ust)),
                              Math.max(0, Math.min(GORUNTU.y, ust + altToplam))];
    const [o1, o2] = gorunur(oncekiAltUst);
    const [s1, s2] = gorunur(sonrakiAltUst);
    const etkiPayi = (Math.max(o2, s2) - Math.min(o1, s1)) / GORUNTU.y;
    const uzaklikPayi = Math.abs(delta) / Math.max(GORUNTU.g, GORUNTU.y);

    sonuc.push({ an: o.an, ad: o.ad, delta, etkiPayi, uzaklikPayi, puan: etkiPayi * uzaklikPayi });
    yukseklik[o.i] = o.son;
  }
  return sonuc;
}

// Oturum penceresi: ilk kaymayla baslar, 1 sn'den uzun bosluk ya da 5 sn'lik sure kapatir.
function pencereler(kaymalar) {
  const p = [];
  for (const k of kaymalar) {
    const son = p[p.length - 1];
    if (son && k.an - son.sonAn <= 1000 && k.an - son.basAn <= 5000) {
      son.sonAn = k.an; son.toplam += k.puan; son.sayi++;
    } else p.push({ basAn: k.an, sonAn: k.an, toplam: k.puan, sayi: 1 });
  }
  return p;
}

let ilk = true;
for (const ad of Object.keys(SENARYO)) {
  const k = kaymalar(ad);
  if (!ilk) console.log("");
  ilk = false;
  console.log(ad);
  console.log("    an  bolge             delta  etki payi  uzaklik payi  kayma puani");
  for (const s of k)
    console.log(
      `  ${String(s.an).padStart(4)}  ${s.ad.padEnd(14)} ${((s.delta > 0 ? "+" : "") + s.delta + " px").padStart(7)} ` +
        `${s.etkiPayi.toFixed(4).padStart(10)} ${s.uzaklikPayi.toFixed(4).padStart(13)} ${s.puan.toFixed(4).padStart(12)}`,
    );
  const p = pencereler(k);
  for (const w of p)
    console.log(`  oturum penceresi ${w.basAn}-${w.sonAn} ms: ${w.sayi} kayma, toplam ${w.toplam.toFixed(4)}`);
  const cls = p.length ? Math.max(...p.map((w) => w.toplam)) : 0;
  console.log(`  kumulatif duzen kaymasi: ${cls.toFixed(4)}  (iyi kabul edilen ust sinir 0.100) -> ${cls <= 0.1 ? "gecer" : "KALIR"}`);
}
```

```
iskeletsiz
    an  bolge             delta  etki payi  uzaklik payi  kayma puani
   400  ust serit       +96 px     0.3700        0.0750       0.0278
   700  suzgec paneli  +120 px     0.4000        0.0938       0.0375
  1500  sonuc listesi  +528 px     0.5800        0.4125       0.2392
  2200  oneriler       +240 px     0.0000        0.1875       0.0000
  6500  uyari seridi    +64 px     1.0000        0.0500       0.0500
  oturum penceresi 400-2200 ms: 4 kayma, toplam 0.3045
  oturum penceresi 6500-6500 ms: 1 kayma, toplam 0.0500
  kumulatif duzen kaymasi: 0.3045  (iyi kabul edilen ust sinir 0.100) -> KALIR

kismi iskelet
    an  bolge             delta  etki payi  uzaklik payi  kayma puani
   400  ust serit       +96 px     1.0000        0.0750       0.0750
   700  suzgec paneli  +120 px     0.8800        0.0938       0.0825
  2200  oneriler       +240 px     0.0000        0.1875       0.0000
  6500  uyari seridi    +64 px     1.0000        0.0500       0.0500
  oturum penceresi 400-700 ms: 2 kayma, toplam 0.1575
  oturum penceresi 2200-2200 ms: 1 kayma, toplam 0.0000
  oturum penceresi 6500-6500 ms: 1 kayma, toplam 0.0500
  kumulatif duzen kaymasi: 0.1575  (iyi kabul edilen ust sinir 0.100) -> KALIR

tam iskelet
    an  bolge             delta  etki payi  uzaklik payi  kayma puani
  6500  uyari seridi    +64 px     1.0000        0.0500       0.0500
  oturum penceresi 6500-6500 ms: 1 kayma, toplam 0.0500
  kumulatif duzen kaymasi: 0.0500  (iyi kabul edilen ust sinir 0.100) -> gecer

iskelet 9 satir, 3 sonuc
    an  bolge             delta  etki payi  uzaklik payi  kayma puani
  1500  sonuc listesi  -432 px     0.4600        0.3375       0.1553
  6500  uyari seridi    +64 px     1.0000        0.0500       0.0500
  oturum penceresi 1500-1500 ms: 1 kayma, toplam 0.1553
  oturum penceresi 6500-6500 ms: 1 kayma, toplam 0.0500
  kumulatif duzen kaymasi: 0.1553  (iyi kabul edilen ust sinir 0.100) -> KALIR
```

Dört kurulum dört ayrı sonuç veriyor ve aralarındaki fark tek bir sayıya inmiyor.

**İskeletsiz kurulum** 0.3045 puanla eşiğin üç katını üretiyor. Beklendiği gibi en büyük
pay sonuç listesindedir: 528 pikselin bir anda araya girmesi tek başına 0.2392 puan
demektir.

**Kısmi iskelet** toplamı 0.1575'e indiriyor ama beklenenden az. Nedeni tablonun ilk iki
satırındadır: yalnız listeye iskelet konduğunda üst şeridin ve süzgeç panelinin kaymaları
**büyüyor** — etki payı 0.3700'den 1.0000'e, 0.4000'den 0.8800'e çıkıyor. Sebep açıktır:
artık onların altında itilecek uzun bir iskelet blok vardır. Bir bölgeye iskelet koymak,
üstündeki bölgelerin kaymalarını ucuzlatmaz, pahalılaştırır. İskelet kısmi uygulandığında
kazanç da kısmi kalır.

**Tam iskelet** ilk penceredeki bütün kaymaları sıfırlıyor. Geriye yalnız altıncı saniyede gelen
uyarı şeridi kalıyor; o da ayrı bir oturum penceresine düştüğü için toplama eklenmiyor ve
kümülatif değer 0.0500'de kalıyor. Pencerelerin ayrı sayılması iskeletsiz kurulumda
belirleyicidir: oradaki beş kayma tek bir pencereye düşseydi toplam 0.3045 değil 0.3545 olurdu.

**Yanlış taahhüt** senaryosu iskeletin kendi riskini ölçüyor. Dokuz satırlık iskelet
çizilip üç sonuç geldiğinde liste 432 piksel **kısalıyor** ve 0.1553 puan üretiyor. Kayma
yönü önemsizdir; içeriğin yukarı sıçraması da aşağı itilmesi kadar puan üretir. Buradan
kesin bir kural çıkar: **iskeletin satır sayısı, sonuç sayısı bilindiğinde ona eşitlenir;
bilinmiyorsa iskelet, kaymayı taşıyabilecek kadar küçük seçilir.**

Öneriler bölgesi bütün senaryolarda sıfır puan üretiyor. Nedeni, o bölgenin altındaki
içeriğin görüntü alanının dışında kalmasıdır; ekranda görünmeyen bir kayma ölçüte
girmez. Bu, alt bölgelerin iskeletsiz bırakılabileceği anlamına gelmez — küçük ekranda
aynı bölge görünür alana girer ve puan üretir.

#### Ölçülebilir Kısıtlar

**Kümülatif düzen kayması 0.100.** İyi kabul edilen üst sınır budur ve hesap yukarıdaki
gibi oturum pencereleriyle yapılır. Bu bir başarı ölçütü değil bir başarım eşiğidir, ama
erişilebilirlik sonucu doğrudandır: kayan bir düzen, düğmeye basmak üzere olan kullanıcının
yanlış düğmeye basmasına yol açar.

**2.2.2 Duraklat, Durdur, Gizle.** İskelet kutularının üzerinden geçen parıltı hareketi beş
saniyeden uzun sürüyorsa durdurulabilmelidir. Hareketi azaltma tercihi bildirilmişse
parıltı tümüyle kaldırılır; iskelet düz gri kutular olarak da işini yapar.

**2.3.1 Üç Yanıp Sönme.** Parıltının saniyede üçten fazla yinelenmemesi gerekir.

**1.4.11 Metin Dışı Kontrast** iskelet kutularına **uygulanmaz**, çünkü kutular anlam
taşımaz ve ağaçtan gizlidir. Buna karşılık iskeletin yüzeyle karışacak kadar silik olması
kullanıcının sayfayı boş sanmasına yol açar; bu bir ölçüt değil bir tasarım kararıdır.

**4.1.3 Durum İletileri.** Yükleme ve tamamlanma bildirimleri iskeletten değil kibar
durum bölgesinden gelir.

#### Sık Yapılan Hata ve Nasıl Anlaşılır

**İskelet ağaçta görünüyor.** Kutular gizlenmediğinde ekran boş sayılmaz, ama okunacak
hiçbir şey de bulunmaz. Anlaşılması: yükleme sırasında sayfadaki metin ögelerini saymak;
iskelet kutuları sayıya giriyorsa gizleme eksiktir.

**İskelet yalnız bir bölgeye konmuş.** Kazanç beklenenden az çıkar ve üstteki bölgelerin
kaymaları büyür. Anlaşılması: kaymaları bölge bölge ölçmek; iskeletli bölgenin üstündeki
puanlar artmışsa neden budur.

**İskelet gerçek satır yüksekliğini taklit etmiyor.** Kutular gerçek satırdan kısa
çizildiğinde içerik geldiğinde yine kayma olur. Anlaşılması: iskelet yüksekliği ile
yerleşmiş satır yüksekliğini karşılaştırmak.

**Geç gelen şerit sayfanın üstüne ekleniyor.** Uyarı şeridi altıncı saniyede tepeye
girdiğinde bütün sayfa aşağı kayar. Anlaşılması: geç gelen bölümlerin yerinin baştan ayrılıp
ayrılmadığına bakmak; ayrılmıyorsa şerit içeriğin üstüne değil, akışın dışına konmalıdır.

#### Özet

- İskelet ekran gelecek içeriğin yerini tutar; ölçülebilir işi yerleşim kaymasını
  engellemektir, algılanan hıza etkisi ikincil ve doğrudan ölçülemeyen bir kazançtır.
- İskelet kutuları anlam taşımaz ve erişilebilirlik ağacından gizlenir; bekleme bilgisi
  iskeletten değil ayrı bir kibar durum bölgesinden gelir.
- Kayma tek bir olay değil bir toplamdır: oturum pencereleri içinde toplanır ve kümülatif
  değer pencerelerin en büyüğüdür; geç gelen bir kayma ayrı pencereye düşerse toplamı
  değiştirmez.
- Kısmi iskelet, üstündeki bölgelerin kaymalarını büyütür; ölçümde üst şeridin etki payı
  0.3700'den 1.0000'e çıkar. Kazanç ancak bütün bölgeler yer ayırdığında tamamlanır.
- İskeletin çizdiği satır sayısı bir taahhüttür; dokuz satır çizip üç sonuç getirmek 0.1553
  puanlık bir kayma üretir ve kaymanın yönü puanı etkilemez.
- Parıltı hareketi 2.2.2 ve 2.3.1 ölçütlerine bağlıdır; hareketi azaltma tercihinde iskelet
  parıltısız da işini görür.

#### Sonraki Adım

İskelet, içeriğin **geleceği** varsayımına dayanıyordu. Yükleme bittiğinde bu varsayımın
tutmadığı iki durum kalır: hiçbir kayıt gelmemesi ve isteğin başarısız olması. İkisi
ekranda benzer görünür —boş bir alan ve bir cümle— ama kullanıcıya bambaşka şeyler söyler
ve bambaşka kurtarma yolları gerektirir. Sonraki ders bu iki durumu belirtim olarak yazar:
hangi rolün ve hangi önceliğin kullanılacağını, odağın nereye gideceğini, hata özetinin
alanlara nasıl bağlanacağını ve kurtarma yolunun uzunluğunu tuş sayısıyla ölçer.

### Boş ve Hata Durumları

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/bos-ve-hata-durumlari

Boş durum ile hata durumunun rol, öncelik ve odak belirtimi; kurtarma yolunun tuş sayısıyla ölçülmesi, hata özetinin keşif maliyetine etkisi ve durum belirtiminin eksiksizlik denetimi.

İskelet, içeriğin geleceği varsayımına dayanıyordu. Yükleme bittiğinde bu varsayımın
tutmadığı iki durum kalır: hiçbir kayıt gelmemesi ve isteğin başarısız olması. İkisi ekranda
benzer görünür — boş bir alan ve bir cümle — ama kullanıcıya bambaşka şeyler söyler.

Yükleme ve Boş Durumlar dersi boşluğun üç nedenini ayırmış, Hata ve Uyarı Durumları dersi de
hatayı kurtarılabilirliğine göre sınıflandırmıştı. Bu ders o ayrımların **belirtim** karşılığını
yazar: hangi rol, hangi duyuru önceliği, odak nereye gider ve kurtarma yolu kaç tuş sürer.

#### Ne Çözer, Ne Zaman Kullanılmaz

Boş durum ve hata durumu iki ayrı kalıptır ve karıştırılmaları en pahalı kusurdur.

**Boş durum**, sistemin doğru çalıştığı ama gösterecek içeriğin bulunmadığı ekrandır. Hata
sınıfına girmez; bilgi sınıfına girer. Uyarı rengi, uyarı simgesi ve atılgan duyuru
kullanılmaz.

**Hata durumu**, bir şeyin planlandığı gibi gitmediği ekrandır. Kullanıcının bir şey
yapması gerekir ve yapılacak şey ekranda yazılıdır.

Ayrımın sınandığı tek soru şudur: **kullanıcı bir şey yapmadığında sistem yanlış bir durumda
mı kalıyor?** Yanıt hayırsa boş durumdur. Sonuçsuz arama boş durumdur; ödünç alma isteğinin
gönderilememesi hatadır.

Kullanılmayacak durumlar:

- **Boş durumu hata gibi sunmak.** Süzgeç hiçbir kayıt döndürmediğinde kırmızı bir uyarı
  göstermek, kullanıcıya sistemin bozulduğunu söyler.
- **Hatayı boş durum gibi sunmak.** İstek başarısız olduğunda "Kayıt bulunamadı" yazmak,
  var olan kayıtları yok göstermek demektir; kullanıcı aramayı bırakır.
- **Her ikisini de kip pencereye koymak.** Boş durum sayfanın kendisidir; kip pencere onu
  kullanıcının kapatması gereken bir engele çevirir.

#### Yerel Öge Önce, ARIA Sonra

| Durum | Rol | Öncelik | Odak |
|---|---|---|---|
| Boş durum (üç türü) | `role="status"` | kibar | Yerinde kalır |
| Alan doğrulama hatası | Hata özeti `role="alert"` | atılgan | Özete taşınır |
| İstek hatası | `role="alert"` | atılgan | İleti kabına taşınır |
| Ağ kopması | `role="status"` | kibar | Yerinde kalır |

Üç kural bu tabloyu üretir.

**Boş durum odağı çalmaz.** Kullanıcı süzgeç alanında yazmaya devam ediyor olabilir; sonuç
sayısı değiştiğinde odağı listeye taşımak yazmayı keser. Bildirim, kibar bölgeden yapılır.

**Ağ kopması kibardır, hata atılgan.** Ağın kopması kullanıcının yaptığı bir şeyin sonucu
değildir ve bağlantı geldiğinde kendiliğinden düzelir; işi durduran bir bildirim değildir.

**Hata özeti belgede önceden bulunur.** Formlar ve Kimlik konusundaki erişilebilir hata
sunumu kuralı burada geçerlidir: özet kabı boş olarak konur, gönderim başarısız olduğunda
içi doldurulur ve odak oraya taşınır.

Ayrıca boş durumun bir **başlığı** olmalıdır. Başlık, ekran okuyucu kullanıcısının başlık
düzleminden bu bölüme atlamasını sağlar; başlıksız bir boş durum düzlemde görünmez.

#### Kurtarma Yolu Ölçülebilir

Belirtimin en somut yanı, hatadan düzeltmeye giden yolun uzunluğudur. Odak halkası bilinen
bir dizidir; tuş sayısı sayılabilir. Aşağıdaki betik üyelik formunda iki hatalı alan
bulunan bir gönderimi üç ayrı odak stratejisiyle ölçüyor, sonra dokuz durumun belirtimini
dört alanlı bir denetimden geçiriyor.

```js
// kurtarma.mjs — kurtarma yolunun tus sayisiyla olculmesi ve durum belirtiminin denetimi

// --- (a) Kurtarma yolunun uzunlugu ------------------------------------------
// Uyelik formunun bulundugu sayfanin odak halkasi (belge sirasinda).
const SAYFA = [
  "atlama-baglantisi", "logo", "gezinme-katalog", "gezinme-hesabim", "arama-alani",
  "form-uye-no", "form-ad", "form-eposta", "form-telefon", "form-adres",
  "form-iade-tarihi", "form-not", "form-gonder",
  "altlik-yardim", "altlik-iletisim", "altlik-gizlilik",
];

// Sekme ve geri sekme ayni maliyette; iki oge arasindaki uzaklik halkada en kisa yoldur.
function adim(halka, bas, hedef) {
  const i = halka.indexOf(bas), j = halka.indexOf(hedef);
  if (i < 0 || j < 0) throw new Error(`halkada yok: ${i < 0 ? bas : hedef}`);
  return Math.min((j - i + halka.length) % halka.length, (i - j + halka.length) % halka.length);
}

// Bir strateji: baslangic odagi ve sirayla gidilecek hedefler.
// baglanti: true olan hedefe tek tusla (giris) gidilir.
function yol(ad, halka, bas, hedefler) {
  let konum = bas, toplam = 0;
  const parca = [];
  for (const h of hedefler) {
    const n = h.baglanti ? 1 : adim(halka, konum, h.hedef);
    toplam += n;
    parca.push(`${h.hedef}:${n}${h.baglanti ? "*" : ""}`);
    konum = h.hedef;
  }
  return { ad, toplam, iz: parca.join("  ") };
}

const A = yol("ozetsiz, odak yerinde", SAYFA, "form-gonder", [
  { hedef: "form-eposta" }, { hedef: "form-iade-tarihi" }, { hedef: "form-gonder" },
]);

// Hata ozeti forma eklenir; odak ozete tasinir, ozetteki baglanti alana goturur.
const HALKA_B = [...SAYFA];
HALKA_B.splice(HALKA_B.indexOf("form-uye-no"), 0, "hata-ozeti", "ozet-eposta", "ozet-iade-tarihi");
const B = yol("hata ozeti + odak ozete", HALKA_B, "hata-ozeti", [
  { hedef: "ozet-eposta" }, { hedef: "form-eposta", baglanti: true },
  { hedef: "form-iade-tarihi" }, { hedef: "form-gonder" },
]);

const C = yol("odak ilk hatali alana", SAYFA, "form-eposta", [
  { hedef: "form-eposta" }, { hedef: "form-iade-tarihi" }, { hedef: "form-gonder" },
]);

// Kesif: kac hata oldugunu ogrenmek icin kac alanin gezilmesi gerekir?
const ALAN_SAYISI = SAYFA.filter((x) => x.startsWith("form-") && x !== "form-gonder").length;
const KESIF = { [A.ad]: ALAN_SAYISI, [B.ad]: 0, [C.ad]: ALAN_SAYISI - 1 };

console.log(`form alani sayisi: ${ALAN_SAYISI}, hatali alan: form-eposta ve form-iade-tarihi`);
console.log("\nstrateji                 tus sayisi  kesif  iz (* = baglantiyla dogrudan)");
for (const s of [A, B, C])
  console.log(`${s.ad.padEnd(24)} ${String(s.toplam).padStart(10)} ${String(KESIF[s.ad]).padStart(6)}  ${s.iz}`);

// --- (b) Durum belirtiminin denetimi ----------------------------------------
// Her durum icin dort alan tanimli olmali: rol, duyuru onceligi, odak hedefi, kurtarma eylemi.
const DURUM = [
  { ad: "ilk kullanim boslugu",  rol: "status", oncelik: "kibar",   odak: "yerinde",      kurtarma: "arama alanina yonlendiren aciklama" },
  { ad: "sonucsuz arama",        rol: "status", oncelik: "kibar",   odak: "yerinde",      kurtarma: "suzgecleri tek tek kaldirma" },
  { ad: "temizlenmis liste",     rol: "status", oncelik: "kibar",   odak: "yerinde",      kurtarma: null },
  { ad: "alan dogrulama hatasi", rol: "alert",  oncelik: "atilgan", odak: "hata ozeti",   kurtarma: "alana giden baglanti ve duzeltme onerisi" },
  { ad: "sunucu hatasi",         rol: "alert",  oncelik: "atilgan", odak: "hata iletisi", kurtarma: "yeniden dene dugmesi" },
  { ad: "ag kopmasi",            rol: "status", oncelik: "kibar",   odak: "yerinde",      kurtarma: "baglanti gelince yeniden deneme" },
  { ad: "yetki hatasi",          rol: "alert",  oncelik: "atilgan", odak: null,           kurtarma: "oturum acma sayfasina donus" },
  { ad: "bulunamayan kayit",     rol: null,     oncelik: null,      odak: "baslik",       kurtarma: "katalog aramasina donus" },
  { ad: "istek siniri asildi",   rol: "alert",  oncelik: "atilgan", odak: "hata iletisi", kurtarma: null },
];

const ZORUNLU = ["rol", "oncelik", "odak", "kurtarma"];
// "temizlenmis liste" bir basaridir; kurtarma eylemi aranmaz.
const MUAF = { "temizlenmis liste": ["kurtarma"] };

console.log("\ndurum                     rol      oncelik   odak            kurtarma  eksik");
let eksikToplam = 0;
for (const d of DURUM) {
  const eksik = ZORUNLU.filter((a) => d[a] === null && !(MUAF[d.ad] ?? []).includes(a));
  eksikToplam += eksik.length;
  console.log(
    `${d.ad.padEnd(25)} ${(d.rol ?? "—").padEnd(8)} ${(d.oncelik ?? "—").padEnd(9)} ${(d.odak ?? "—").padEnd(15)} ` +
      `${(d.kurtarma ? "var" : "—").padEnd(9)} ${eksik.length ? eksik.join(",") : "yok"}`,
  );
}
console.log(`\ntoplam eksik alan: ${eksikToplam} / ${DURUM.length * ZORUNLU.length}`);
```

```
form alani sayisi: 7, hatali alan: form-eposta ve form-iade-tarihi

strateji                 tus sayisi  kesif  iz (* = baglantiyla dogrudan)
ozetsiz, odak yerinde            10      7  form-eposta:5  form-iade-tarihi:3  form-gonder:2
hata ozeti + odak ozete           7      0  ozet-eposta:1  form-eposta:1*  form-iade-tarihi:3  form-gonder:2
odak ilk hatali alana             5      6  form-eposta:0  form-iade-tarihi:3  form-gonder:2

durum                     rol      oncelik   odak            kurtarma  eksik
ilk kullanim boslugu      status   kibar     yerinde         var       yok
sonucsuz arama            status   kibar     yerinde         var       yok
temizlenmis liste         status   kibar     yerinde         —         yok
alan dogrulama hatasi     alert    atilgan   hata ozeti      var       yok
sunucu hatasi             alert    atilgan   hata iletisi    var       yok
ag kopmasi                status   kibar     yerinde         var       yok
yetki hatasi              alert    atilgan   —               var       odak
bulunamayan kayit         —        —         baslik          var       rol,oncelik
istek siniri asildi       alert    atilgan   hata iletisi    —         kurtarma

toplam eksik alan: 4 / 36
```

İlk tablo tek bir sayıyla karar vermenin neden yanlış olduğunu gösteriyor.

**Özetsiz kurulum on tuş istiyor** ve keşif sütunu asıl bedeli veriyor: kullanıcı kaç hata
olduğunu öğrenmek için yedi alanın hepsini gezmek zorundadır. Gönderim başarısız olduğunda
odak gönder düğmesinde kaldığı için, ilk hatalı alana ulaşmak beş geri sekme tuşu gerektirir.

**Odağı doğrudan ilk hatalı alana taşımak beş tuşla en ucuz yol.** Ama keşif sütunu altı diyor:
kullanıcı odaklandığı alanın hatalı olduğunu öğrenir, ama ikinci hatanın varlığını öğrenmez.
İkinci hatayı ancak forma devam ederken bulur ya da hiç bulmaz ve gönder düğmesine ikinci kez
basar.

**Hata özeti yedi tuş istiyor** — doğrudan taşımadan iki tuş fazla — ama keşif maliyetini
sıfıra indiriyor. Özet, kaç hata olduğunu tek bir cümlede söyler ve her hataya bir bağlantı
verir. İki fazladan tuş, "kaç hata var" sorusunun yanıtının bedelidir ve ucuzdur.

Bu ölçüm bir kuralı gerekçelendirir: **birden çok hata varsa özet, tek hata varsa doğrudan
taşıma.** Tek hatada özetin keşif kazancı yoktur ve iki tuş boşa gider.

İkinci tablo belirtimin kendisini denetliyor. Dokuz durumun otuz altı alanından dördü
eksik ve üçü ayrı türden kusur:

- **Yetki hatasında odak hedefi tanımsız.** İleti ekranda belirir ama odak formda kalır;
  kullanıcı iletiyi hiç duymaz.
- **Bulunamayan kayıtta rol ve öncelik tanımsız.** Ekranda bir başlık vardır, duyuru yoktur.
  Bu durum aslında bir sayfa geçişidir ve başlığa odaklanma doğru karardır; eksik olan,
  bunun bir durum bildirimi olarak da tanımlanmasıdır.
- **İstek sınırı aşıldığında kurtarma eylemi tanımsız.** Kullanıcıya ne yapacağı
  söylenmiyor; "biraz sonra yeniden deneyin" gibi bir yol yazılmadığında hata çıkmaz sokaktır.

"Temizlenmiş liste" satırında kurtarma alanı boş olduğu hâlde eksik sayılmıyor, çünkü o
durum bir başarıdır ve kurtarılacak bir şey yoktur. Denetimin muafiyet listesi bu ayrımı
belirtimin parçası hâline getirir.

#### Ölçülebilir Kısıtlar

**3.3.1 Hata Tanımlama.** Hatalı alan **metinle** belirtilmelidir; kırmızı kenarlık tek
başına yetmez. Bu, 1.4.1 Rengin Kullanımı ölçütünün form karşılığıdır.

**3.3.3 Hata Önerisi.** Hatanın nasıl düzeltileceği biliniyorsa yazılmalıdır. "Geçersiz
tarih" bir tanımlamadır, öneri değildir; "İade tarihi bugünden sonra olmalı" ikisini
birden yapar.

**3.3.4 Hata Önleme.** Geri alınamaz işlemlerde gönderim tersine çevrilebilir, denetlenebilir
ya da onaylanabilir olmalıdır. Kip Pencereler dersindeki onay kalıbı bu ölçütün
karşılığıdır.

**4.1.3 Durum İletileri.** Boş durumun sonuç sayısı odak taşımadan duyurulmalıdır.

**2.4.6 Başlıklar ve Etiketler.** Boş durumun başlığı, içeriğin ne olduğunu betimlemelidir;
"Sonuç yok" başlığı yerine "Süzgeçlere uyan kayıt yok" başlığı düzlemde işe yarar.

**1.4.11 Metin Dışı Kontrast.** Hata alanının kenarlığı çevresindeki yüzeye karşı en az
3:1 oranını karşılamalıdır; hata rengi yalnız metinde değil sınırda da ayırt edilebilmelidir.

#### Sık Yapılan Hata ve Nasıl Anlaşılır

**Odak gönderimden sonra yerinde kalıyor.** Anlaşılması: formu hatalı gönderip bir kez
sekme tuşuna basmak; odak formun başına ya da özete gitmiyorsa strateji yoktur.

**Hata özeti sonradan ekleniyor.** Duyuru bazen yapılır bazen yapılmaz. Anlaşılması: sayfa
ilk kurulduğunda özet kabının belgede olup olmadığına bakmak.

**Boş durum atılgan duyuruyla bildiriliyor.** Süzgeç her değiştiğinde kullanıcının okuması
kesilir. Anlaşılması: boş durum bölgesinin rolüne bakmak; `role="alert"` ise yanlıştır.

**Hata iletisi alana bağlanmamış.** İleti ekranda görünür ama alana odaklanıldığında
okunmaz. Anlaşılması: alanın `aria-describedby` bağının ileti kimliğini gösterip
göstermediğine bakmak.

**Boş durumda başlık yok.** Anlaşılması: başlık düzlemini çıkarıp boş durumun orada görünüp
görünmediğine bakmak.

#### Özet

- Boş durum sistemin doğru çalıştığı bir sonuçtur ve bilgi sınıfına girer; hata durumu
  kullanıcıdan bir eylem ister ve o eylem ekranda yazılıdır.
- Ayrımın sınandığı soru tektir: kullanıcı hiçbir şey yapmazsa sistem yanlış bir durumda
  kalıyor mu?
- Boş durum kibar bölgeden bildirilir ve odağı çalmaz; alan doğrulama hatası atılgan
  bildirilir ve odak hata özetine taşınır.
- Kurtarma yolu tuş sayısıyla ölçülür: özetsiz kurulum on tuş ve yedi alanlık keşif,
  doğrudan taşıma beş tuş ve altı alanlık keşif, hata özeti yedi tuş ve sıfır keşif üretir.
- Birden çok hata varsa özet, tek hata varsa doğrudan taşıma seçilir; özetin iki fazladan
  tuşu "kaç hata var" sorusunun bedelidir.
- Bir durumun belirtimi rol, öncelik, odak hedefi ve kurtarma eylemi alanlarının dördünü de
  taşımalıdır; denetim, eksik alanı ve muafiyeti birlikte tanımlar.

#### Sonraki Adım

Boş ve hata durumları bir ekranın tamamını kaplayan bildirimlerdi. Aynı bilgi çoğu zaman
çok daha küçük bir yüzeyde taşınır: satırın yanındaki küçük bir etiket, sayının yanındaki
renkli bir nokta, kaydın durumunu bildiren tek sözcüklük bir rozet. Bu küçük yüzeyler iki
tuzağı birlikte barındırır — bilgiyi yalnız renkle taşımak ve erişilebilir ada yalnız sayı
yazmak. Sonraki ders rozetlerin ve etiketlerin belirtimini yazar, rengin tek başına ayırt
edici olmadığını kontrast ve renk körlüğü izdüşümüyle hesaplar ve ikinci kanalın nasıl
denetleneceğini gösterir.

### Rozet ve Etiketler

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/rozet-ve-etiketler

Rozet ve etiketin belirtimi, sayı rozetinin erişilebilir adının kurulması, durum renklerinin kontrast denetimi ve renk bilgisi olmadan ayırt edilebilirliğin bağıl parlaklıkla hesaplanması.

Boş ve hata durumları bir ekranın tamamını kaplayan bildirimlerdi. Aynı bilgi çoğu zaman
çok daha küçük bir yüzeyde taşınır: satırın yanındaki tek sözcüklük bir işaret, düğmenin
köşesindeki bir sayı, kaydın türünü bildiren bir etiket.

Küçük yüzey iki tuzağı birlikte barındırır. Birincisi, bilginin **yalnız renkte** kalması;
ikincisi, erişilebilir adın **yalnız sayıdan** ibaret olması. Bu ders ikisini de ölçülebilir
bir denetime bağlar.

#### Ne Çözer, Ne Zaman Kullanılmaz

**Rozet (badge)**, başka bir ögeye iliştirilen küçük durum ya da sayı işaretidir. İki türü
vardır: durum rozeti kaydın hâlini bildirir ("Gecikti"), sayı rozeti bir miktarı bildirir
(bildirim sayısı).

**Etiket (tag)**, bir kaydın sınıflandırmasını gösteren, çoğu zaman kaldırılabilen
işarettir. Süzgeç panelinde uygulanmış ölçütleri gösteren etiketler bu türdendir.

Ayrım işlevseldir: rozet **iliştirildiği ögeyi niteler** ve kendi başına etkileşimli
değildir; etiket **kendi başına bir nesnedir** ve kaldırılabilir.

Kullanılmayacak durumlar:

- **Rozet, kritik bilginin tek taşıyıcısı olamaz.** "Gecikti" bilgisi yalnız rozette
  duruyorsa, rozeti göremeyen kullanıcı için o bilgi yoktur; aynı bilgi kaydın ayrıntı
  görünümünde de bulunmalıdır.
- **Rozet tıklanabilir yapılmaz.** Tıklanabilir bir işaret düğmedir ve düğme sözleşmesine
  uymak zorundadır; rozet görünümünde bir düğme, hedef boyutu eşiğini de kaçırır.
- **Etiket, kaldırma denetimi olmadan kaldırılabilir gösterilmez.** Üzerinde çarpı işareti
  bulunan bir etiket klavyeyle kaldırılamıyorsa yanıltıcıdır.

#### Yerel Öge Önce, ARIA Sonra

Rozetin işaretlemede ayrılmış bir karşılığı yoktur; kurulum ada ve metne dayanır.

| Parça | Kaynak |
|---|---|
| Durum rozeti | Metin taşıyan bir öge; anlam metnin kendisindedir, ek rol gerekmez |
| Sayı rozeti | Sayı, iliştirildiği denetimin **adının içine** yazılır |
| Sayının değişmesi | Denetimin yanındaki kibar bölge bildirir, rozet bildirmez |
| Kaldırılabilir etiket | Metin bir öge, kaldırma düğmesi ayrı bir düğme |
| Kaldırma düğmesinin adı | "X etiketini kaldır" — etiket metnini içerir |

Sayı rozetinin kurulumu en çok yanlış yapılan yerdir. "3" yazan bir rozet, düğmenin yanına
ayrı bir düğüm olarak konduğunda erişilebilir ağaçta bağlamsız bir sayı olarak durur.
Doğru kurulum, sayıyı düğmenin adına katmaktır: "Bildirimler, 3 okunmamış". Görsel rozet
ayrıca ağaçtan gizlenir, çünkü bilgisi zaten adın içindedir.

Kısaltılmış sayılar ayrı bir kural ister. Ekranda "99+" yazan bir rozetin adında gerçek
sayı bulunmalıdır; görsel kısaltma bir yer kısıtıdır, bilgi kısıtı değil.

#### Klavye Sözleşmesi

Rozet odak almaz ve sekme sırasında yer tutmaz. Etiketin kaldırma düğmesi ise tam bir
düğmedir:

| Tuş | Davranış |
|---|---|
| Sekme | Kaldırma düğmesine sırayla ulaşılır |
| Giriş / boşluk | Etiketi kaldırır |
| Kaldırma sonrası odak | Bir sonraki etikete; son etiket kaldırıldıysa etiket listesinin kabına |

Son maddede Kip Pencereler dersindeki dönüş noktası kuralının bir başka biçimi görünür:
odaklı öge kaldırıldığında odağın nereye gideceği belirtimin parçasıdır. Belirlenmediğinde
odak belgenin başına düşer ve kullanıcı süzgeç panelindeki yerini kaybeder.

Etiket kaldırıldığında sonuç sayısı değişir; bu değişim kibar bölgeden bildirilir.

#### Renk Yeterli Bir Kanal Değildir

Durum rozetlerinin rengi bir kısayoldur ve kısayolun sınırı hesaplanabilir. İki ölçüm
gerekir: rozetin kendi kontrastı ve rozetlerin **birbirine karşı** ayırt edilebilirliği.
İkincisi renk bilgisi kullanılamadığında geriye ne kaldığını gösterir.

```js
// rozet.mjs — rozet renklerinin kontrasti, parlaklik cakismasi ve ikinci kanal denetimi

const ayir = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255);
const dogrusal = (k) => (k <= 0.04045 ? k / 12.92 : ((k + 0.055) / 1.055) ** 2.4);
const parlaklik = (hex) => {
  const [r, g, b] = ayir(hex).map(dogrusal);
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};
const oran = (a, b) => {
  const [x, y] = [parlaklik(a), parlaklik(b)].sort((p, q) => q - p);
  return (x + 0.05) / (y + 0.05);
};

const YUZEY = "#F4F4F5";   // kayit kartinin yuzeyi
const ROZET = [
  { ad: "odunc alinabilir", zemin: "#2E7D32", metin: "#FFFFFF", etiket: "Mevcut",  simge: true  },
  { ad: "rezerve",          zemin: "#B26A00", metin: "#FFFFFF", etiket: "Rezerve", simge: true  },
  { ad: "iade gecikti",     zemin: "#C62828", metin: "#FFFFFF", etiket: "Gecikti", simge: true  },
  { ad: "kayip",            zemin: "#616161", metin: "#FFFFFF", etiket: "Kayıp",   simge: false },
  { ad: "yeni kayit",       zemin: "#1565C0", metin: "#FFFFFF", etiket: null,      simge: false },
];

console.log("rozet             zemin    bagil parlaklik  metin/zemin  zemin/yuzey  1.4.3  1.4.11");
for (const r of ROZET) {
  const mz = oran(r.metin, r.zemin), zy = oran(r.zemin, YUZEY);
  console.log(
    `${r.ad.padEnd(17)} ${r.zemin}  ${parlaklik(r.zemin).toFixed(4).padStart(15)} ` +
      `${(mz.toFixed(2) + ":1").padStart(12)} ${(zy.toFixed(2) + ":1").padStart(12)} ` +
      `${(mz >= 4.5 ? "gecer" : "KALIR").padStart(6)} ${(zy >= 3 ? "gecer" : "KALIR").padStart(7)}`,
  );
}

// --- Parlaklik cakismasi: renk bilgisi kullanilamadiginda rozetler ayirt edilebilir mi?
// Iki rozetin birbirine karsi kontrast orani 1'e yakinsa, renksiz kanalda ozdeslesirler.
console.log("\nrozetler arasi kontrast orani (renk bilgisi olmadan ayirt edilebilirlik)");
console.log("                  " + ROZET.map((r) => r.ad.slice(0, 8).padStart(9)).join(""));
const cakisan = [];
for (const a of ROZET) {
  let satir = a.ad.padEnd(18);
  for (const b of ROZET) {
    const o = oran(a.zemin, b.zemin);
    satir += o.toFixed(2).padStart(9);
    if (a !== b && o < 1.5 && ROZET.indexOf(a) < ROZET.indexOf(b)) cakisan.push([a.ad, b.ad, o]);
  }
  console.log(satir);
}
console.log("\nrenksiz kanalda ayirt edilemeyen cift (esik 1.5:1):");
for (const [a, b, o] of cakisan) console.log(`  ${a} / ${b}: ${o.toFixed(2)}:1`);
if (!cakisan.length) console.log("  yok");

// --- Ikinci kanal denetimi ---------------------------------------------------
// Durum, renk disinda en az bir kanalda daha tasinmali: metin ya da bicim.
console.log("\nrozet             metin  simge  ikinci kanal");
for (const r of ROZET) {
  const kanal = [r.etiket ? "metin" : null, r.simge ? "simge" : null].filter(Boolean);
  console.log(`${r.ad.padEnd(17)} ${(r.etiket ? "var" : "—").padStart(5)} ${(r.simge ? "var" : "—").padStart(6)}  ` +
    `${kanal.length ? kanal.join(" + ") : "YOK — durum yalniz renkte"}`);
}

// --- Sayi rozetinin erisilebilir adi ----------------------------------------
const AD_ORNEK = [
  { gorunen: "3", ad: "3" },
  { gorunen: "3", ad: "Bildirimler" },
  { gorunen: "3", ad: "Bildirimler, 3 okunmamış" },
  { gorunen: "99+", ad: "Bildirimler, 128 okunmamış" },
];
console.log("\ngorunen  erisilebilir ad                 sayi var  baglam var  yeterli");
for (const a of AD_ORNEK) {
  const sayiVar = /\d/.test(a.ad);
  const baglamVar = /[A-Za-zÇĞİÖŞÜçğıöşü]{3,}/.test(a.ad);
  console.log(`${a.gorunen.padEnd(8)} ${a.ad.padEnd(30)} ${(sayiVar ? "evet" : "hayir").padStart(8)} ` +
    `${(baglamVar ? "evet" : "hayir").padStart(11)}  ${sayiVar && baglamVar ? "evet" : "HAYIR"}`);
}
```

```
rozet             zemin    bagil parlaklik  metin/zemin  zemin/yuzey  1.4.3  1.4.11
odunc alinabilir  #2E7D32           0.1548       5.13:1       4.66:1  gecer   gecer
rezerve           #B26A00           0.1977       4.24:1       3.86:1  KALIR   gecer
iade gecikti      #C62828           0.1368       5.62:1       5.11:1  gecer   gecer
kayip             #616161           0.1195       6.19:1       5.63:1  gecer   gecer
yeni kayit        #1565C0           0.1327       5.75:1       5.23:1  gecer   gecer

rozetler arasi kontrast orani (renk bilgisi olmadan ayirt edilebilirlik)
                   odunc al  rezerve iade gec    kayip yeni kay
odunc alinabilir       1.00     1.21     1.10     1.21     1.12
rezerve                1.21     1.00     1.33     1.46     1.36
iade gecikti           1.10     1.33     1.00     1.10     1.02
kayip                  1.21     1.46     1.10     1.00     1.08
yeni kayit             1.12     1.36     1.02     1.08     1.00

renksiz kanalda ayirt edilemeyen cift (esik 1.5:1):
  odunc alinabilir / rezerve: 1.21:1
  odunc alinabilir / iade gecikti: 1.10:1
  odunc alinabilir / kayip: 1.21:1
  odunc alinabilir / yeni kayit: 1.12:1
  rezerve / iade gecikti: 1.33:1
  rezerve / kayip: 1.46:1
  rezerve / yeni kayit: 1.36:1
  iade gecikti / kayip: 1.10:1
  iade gecikti / yeni kayit: 1.02:1
  kayip / yeni kayit: 1.08:1

rozet             metin  simge  ikinci kanal
odunc alinabilir    var    var  metin + simge
rezerve             var    var  metin + simge
iade gecikti        var    var  metin + simge
kayip               var      —  metin
yeni kayit            —      —  YOK — durum yalniz renkte

gorunen  erisilebilir ad                 sayi var  baglam var  yeterli
3        3                                  evet       hayir  HAYIR
3        Bildirimler                       hayir        evet  HAYIR
3        Bildirimler, 3 okunmamış           evet        evet  evet
99+      Bildirimler, 128 okunmamış         evet        evet  evet
```

Birinci tablo beklenen denetimi yapıyor ve bir kusur buluyor: turuncu rozetin beyaz metne
karşı oranı 4.24:1, yani metin eşiğinin altında. Aynı renk metin dışı eşiği (3:1)
karşılıyor. Bu, renk sisteminde sık görülen bir tuzaktır — turuncu, kırmızıyla yeşilin
arasında bir parlaklığa oturur ve beyaz metni taşımaya yetmez. Çözüm rengi koyulaştırmak
ya da metni koyu yazmaktır.

İkinci tablo dersin asıl bulgusudur. **Beş rozet renginin onu çiftinin tamamı, renk bilgisi
olmadan birbirinden ayırt edilemez.** En kötü çift 1.02:1 ile "iade gecikti" ve "yeni
kayıt"; kırmızı ile mavi, bağıl parlaklıkları neredeyse eşit olduğu için renksiz kanalda
aynı gri tona düşer. En iyi çift bile 1.46:1'de kalır ve seçilen 1.5:1 eşiğini geçmez.

Bu sonuç bir tasarım hatası değildir. Renk paleti, İşlevsel Renkler dersindeki gibi rollere
göre seçilmiştir ve her biri kendi zemininde eşikleri karşılar. Kanıtlanan şey daha
temeldir: **durum renkleri birbirinden ayırt edilecek biçimde seçilemez.** Beş rolün beşini
birden yeterli parlaklık aralığıyla ayırmak, renkleri kullanılamayacak kadar açık ve koyu
uçlara itmek demektir. Bu yüzden 1.4.1 ölçütü rengi bilgi taşıyıcı olarak kullanmayı
yasaklamaz; **tek** taşıyıcı olarak kullanmayı yasaklar.

Üçüncü tablo ikinci kanalın denetimidir. Beş rozetin üçü hem metin hem simge taşıyor,
biri yalnız metin, biri hiçbiri. "Yeni kayıt" rozeti yalnız renkli bir noktadır ve bilgisi
renkten başka hiçbir kanalda bulunmaz — denetim bunu doğrudan yakalıyor. Yalnız metin
taşıyan "kayıp" rozeti yeterlidir; simge zorunlu değildir, ikinci kanalın **bir tanesi**
yeter.

Son tablo sayı rozetinin adını sınıyor. "3" adı sayıyı taşır ama neyin üçü olduğunu
söylemez; "Bildirimler" adı bağlamı taşır ama sayıyı düşürür. İkisini birleştiren ad her
iki denetimi de geçer. Dördüncü satır kısaltma kuralını gösteriyor: ekranda "99+" yazarken
adda gerçek sayı durur.

#### Ölçülebilir Kısıtlar

**1.4.1 Rengin Kullanımı.** Renk, bilgiyi ileten tek görsel araç olamaz. Denetimin
karşılığı yukarıdaki ikinci kanal tablosudur.

**1.4.3 Kontrast (En Az).** Rozet metni zeminine karşı 4.5:1; büyük metin sayılan boyutlarda
3:1. Rozet metni küçük olduğu için pratikte hep 4.5:1 aranır.

**1.4.11 Metin Dışı Kontrast.** Rozetin zemini, üzerine oturduğu yüzeye karşı 3:1
karşılamalıdır; rozetin sınırı belirsizse rozetin kendisi görülemez.

**2.5.8 Hedef Boyutu (En Az).** Etiketin kaldırma düğmesi 24 x 24 piksellik alana genişletilir.
Düğme bu boyutu karşıladığında ölçüt yerine gelir; ölçütteki boşluk koşulu yalnız 24 x 24
piksellik alanın altında kalan hedefler için bir seçenektir.

**1.4.4 Metnin Yeniden Boyutlandırılması.** Rozet metni iki katına çıktığında rozet
büyümeli, metni kırpmamalıdır. Sabit genişlikli daire biçiminde bir sayı rozeti bu eşikte
sayıyı keser.

**4.1.3 Durum İletileri.** Rozetteki sayının değişmesi bir durum iletisidir ve rozetin
kendisinden değil kibar bölgeden bildirilir.

#### Sık Yapılan Hata ve Nasıl Anlaşılır

**Rozet ayrı bir düğüm olarak okunuyor.** Düğmenin yanındaki "3", bağlamsız bir sayı
olarak bildirilir. Anlaşılması: düğmenin ad alanında sayının bulunup bulunmadığına bakmak.

**Durum yalnız renkli bir noktayla gösteriliyor.** Anlaşılması: ekranı renksiz düşünmek
yetmez, ölçmek gerekir — durum renklerinin ikili kontrast oranlarını hesaplamak ve
1.5:1'in altındaki çiftleri saymak.

**Etiket kaldırma düğmesinin adı "kaldır".** Beş etiketin beşinde de aynı ad görünür ve
hangi etiketin kaldırılacağı anlaşılmaz. Anlaşılması: bağlantı ve düğme düzleminde aynı
adın kaç kez tekrarlandığını saymak; Ekran Okuyucu Deneyimi dersindeki "aynı metin farklı
hedef" kusurunun düğme karşılığıdır.

**Sayı rozeti sıfırken de duruyor.** Boş bir rozet, olmayan bir bilgiyi ekranda tutar.
Anlaşılması: sayı sıfırlandığında rozetin kaldırılıp kaldırılmadığına bakmak.

#### Özet

- Rozet iliştirildiği ögeyi niteler ve etkileşimli değildir; etiket kendi başına bir
  nesnedir ve kaldırma düğmesi tam bir düğmedir.
- Sayı rozetinin bilgisi iliştirildiği denetimin **adına** yazılır; görsel rozet ağaçtan
  gizlenir ve ekrandaki kısaltma adda açılır.
- Rozet metni 4.5:1, rozet zemini yüzeye karşı 3:1 eşiğini karşılamalıdır; turuncu zemin
  ölçümde 4.24:1 ile metin eşiğini kaçırdı.
- Durum renkleri birbirinden ayırt edilecek biçimde seçilemez: beş rengin on çiftinin
  tamamı renksiz kanalda 1.5:1 eşiğinin altında kalır, en yakın çift 1.02:1'dir.
- Bu yüzden her durum rozetinin ikinci bir kanalı olmalıdır — metin ya da simge; birinin
  bulunması yeterlidir, ikisi birden zorunlu değildir.
- Etiket kaldırıldığında odağın nereye gideceği ve sonuç sayısının nasıl bildirileceği
  belirtimin parçasıdır.

#### Sonraki Adım

Bu konudaki yedi kalıbın hepsinde son karar metne düştü. Bildirimin süresi metnin kelime
sayısından hesaplandı, hata iletisinin yeterliliği kurtarma yolunu yazıp yazmamasına
bağlandı, rozetin ikinci kanalı çoğu zaman bir sözcük oldu. Metin, bileşen belirtiminin
son alanı değil; belirtimin kendisini taşıyan malzeme.

Sıradaki konu bu malzemeyi sisteme çevirir. Önce arayüz metnini denetlenebilir kurallara
bağlar — ses tonu, uzunluk ve terim tutarlılığı ölçülebilir hâle gelir. Sonra hata iletisi
yazımını, yerelleştirme kısıtlarını, sağdan sola düzeni ve bölgesel alan gerekliliklerini
ele alır. İlk ders, aynı kavramın arayüzde kaç ayrı sözcükle anıldığını sayarak başlar.

## Metin ve Yerelleştirme

Arayüz metninin denetlenebilir bir sisteme çevrilmesi, hata iletisi yazımı, yerelleştirme kısıtları, sağdan sola düzen ve bölgesel alan gereklilikleri.

### Arayüz Metni Rehberi

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/arayuz-metni-rehberi

Arayüz metni rehberinin denetlenebilir kurallara çevrilmesi; terim defteri, ton kuralları, sınıf başına uzunluk sınırı ve görünen metnin erişilebilir adın içinde bulunması ölçütü.

Önceki konudaki yedi kalıbın hepsinde son karar metne düştü. Bildirimin süresi metnin
kelime sayısından hesaplandı, hata iletisinin yeterliliği kurtarma yolunu yazıp yazmamasına
bağlandı, rozetin ikinci kanalı çoğu zaman tek bir sözcük oldu.

Mikro Metin dersi arayüz metnini bir **düzen kısıtı** olarak ölçmüştü: etiketin genişliği,
kırpmanın ürettiği çakışma, hata iletisinin satır sayısı. Bu ders aynı metni bir **sistem**
olarak ele alır. Sorusu şudur: bir arayüz metni rehberi, kişisel üslup tercihlerinin listesi
olmaktan çıkıp denetlenebilir kurallara nasıl çevrilir?

#### Rehber Neyi Çözer, Ne Zaman İşe Yaramaz

Arayüz metni rehberi, aynı arayüzde çalışan farklı kişilerin aynı sözcükleri seçmesini
sağlar. Çözdüğü sorun tutarsızlıktır: bir ekranda "ödünç alma", ötekinde "emanet",
üçüncüsünde "çıkış" yazıldığında kullanıcı üç ayrı işlem olduğunu sanır.

Rehber üç durumda işe yaramaz:

- **Örnek yoksa.** "Kısa ve net yaz" bir kural değildir; sınanamaz.
- **Karşı örnek yoksa.** Neyin yanlış olduğu yazılmadığında kural yorum bırakır.
- **Denetlenmiyorsa.** Elle okunan bir rehber, kataloğun büyümesiyle uygulanmaz olur.

Bu üç eksik aynı çözüme işaret eder: kurallar **makine tarafından sınanabilir** biçimde
yazılmalıdır. Rehberin gövdesi düzyazı olabilir, ama her kuralın bir denetim karşılığı
bulunmalıdır.

#### Dört Kural Ailesi

**Terim defteri.** Her kavram için bir sözcük seçilir, eşanlamlıları yasaklanır. Defter bir
sözlük değildir; bir **karar kaydıdır** ve nedeni de yazılır. "Kayıt" seçildiyse, "materyal"
ve "eser" yasaktır.

**Ton.** Üç kural yeter: eylem etiketleri kısa emir kipinde yazılır, sistem iletileri
kullanıcıyı suçlamaz, vurgu için büyük harf ve ünlem kullanılmaz. Suçlamama kuralının
gerekçesi nezaket değil doğruluktur — "yanlış girdiniz" ifadesi, alanın beklediği biçimi
söylemediği için kullanıcıya hiçbir bilgi vermez.

**Uzunluk.** Sınır metin sınıfına göre değişir: eylem etiketi en çok üç kelime, boş durum
başlığı altı, yardım metni on dört, ileti yirmi. Sayılar keyfîdir ve o arayüz için seçilir;
işlevleri denetlenebilir olmalarıdır.

**Görünen metin ile ad.** **2.5.3 Addaki Etiket** ölçütü, görünen etiketin erişilebilir adın
içinde bulunmasını ister. Bu, bir yazım kuralı gibi görünmez ama doğrudan metin kararıdır:
ekranda "Filtreyi uygula" yazıp adı "Uygula" bırakmak ölçütü kaldırır.

#### Rehberin Denetimi

Aşağıdaki betik on dört girdilik bir arayüz metni kataloğunu dört kural ailesiyle
denetliyor ve son bölümde her kavramın kaç ayrı sözcükle anıldığını sayıyor.

```js
// metin-denetimi.mjs — arayuz metni rehberinin dort kuralinin catalog uzerinde denetimi

// Arayuz metni katalogu: her girdinin sinifi, gorunen metni ve (denetimlerde) erisilebilir adi.
const KATALOG = [
  { anahtar: "dugme.odunc",        sinif: "eylem",   metin: "Ödünç al",                       ad: "Ödünç al" },
  { anahtar: "dugme.rezerve",      sinif: "eylem",   metin: "Rezerve et",                     ad: "Rezerve et" },
  { anahtar: "dugme.emanet",       sinif: "eylem",   metin: "Emanete çıkar",                  ad: "Emanete çıkar" },
  { anahtar: "dugme.suzgec",       sinif: "eylem",   metin: "Filtreyi uygula",                ad: "Uygula" },
  { anahtar: "dugme.temizle",      sinif: "eylem",   metin: "Süzgeçleri temizle",             ad: "Süzgeçleri temizle" },
  { anahtar: "dugme.disa",         sinif: "eylem",   metin: "Seçili kayıtları dışa aktar",    ad: "Seçili kayıtları dışa aktar" },
  { anahtar: "baslik.bos",         sinif: "baslik",  metin: "Süzgeçlere uyan kayıt yok",      ad: null },
  { anahtar: "baslik.ilk",         sinif: "baslik",  metin: "KATALOGDA ARAMA YAPIN",          ad: null },
  { anahtar: "hata.isbn",          sinif: "ileti",   metin: "ISBN yanlış girdiniz!",          ad: null },
  { anahtar: "hata.tarih",         sinif: "ileti",   metin: "İade tarihi bugünden sonra olmalı. Takvimden bir gün seçin.", ad: null },
  { anahtar: "yardim.uye",         sinif: "yardim",  metin: "Üye numaranız kartınızın arkasında, barkodun altında yazar; boşluksuz yazın.", ad: null },
  { anahtar: "yardim.materyal",    sinif: "yardim",  metin: "Materyal türünü seçerek sonuçları daraltabilirsiniz.", ad: null },
  { anahtar: "durum.sonuc",        sinif: "ileti",   metin: "128 kayıt listeleniyor",         ad: null },
  { anahtar: "dugme.kaldir",       sinif: "eylem",   metin: "Kaldır",                         ad: "Tür etiketini kaldır" },
];

// --- Kural 1: terim defteri --------------------------------------------------
// Her kavram icin tek bir sozcuk; otekiler kurs boyunca kullanilmaz.
const TERIM = {
  "ödünç alma": ["emanet", "kiralama", "çıkış yapma"],
  "kayıt":      ["materyal", "eser", "ürün"],
  "süzgeç":     ["filtre"],
  "üye":        ["müşteri", "abone"],
};

// --- Kural 2: ton ------------------------------------------------------------
const SUCLAYAN = ["yanlış girdiniz", "hatalı girdiniz", "unuttunuz", "yapmalısınız", "girmediniz"];
const KISALTMA = new Set(["ISBN", "QR"]);

// --- Kural 3: uzunluk --------------------------------------------------------
const SINIR = { eylem: 3, baslik: 6, ileti: 20, yardim: 14 };

const kelimeler = (m) => m.trim().split(/\s+/);
const kucuk = (m) => m.toLocaleLowerCase("tr");
// Turkce ekli dillerde alt dizgi aramasi yanlis eslesir ("turunu" icinde "urun" gecer):
// eslesme sozcuk basindan baslamali, sonu serbest kalmali.
const gecer = (metin, sozcuk) =>
  new RegExp(`(^|[^\\p{L}])${kucuk(sozcuk)}`, "u").test(kucuk(metin));

const bulgular = [];
for (const g of KATALOG) {
  // 1. terim
  for (const [dogru, yasak] of Object.entries(TERIM))
    for (const y of yasak)
      if (gecer(g.metin, y))
        bulgular.push({ anahtar: g.anahtar, kural: "terim", ayrinti: `"${y}" yerine "${dogru}"` });

  // 2. ton
  if (g.metin.includes("!"))
    bulgular.push({ anahtar: g.anahtar, kural: "ton", ayrinti: "ünlem" });
  for (const s of SUCLAYAN)
    if (gecer(g.metin, s))
      bulgular.push({ anahtar: g.anahtar, kural: "ton", ayrinti: `suçlayan kalıp: "${s}"` });
  for (const k of kelimeler(g.metin)) {
    const sade = k.replace(/[^\p{L}]/gu, "");
    if (sade.length >= 4 && sade === sade.toLocaleUpperCase("tr") && !KISALTMA.has(sade))
      bulgular.push({ anahtar: g.anahtar, kural: "ton", ayrinti: `büyük harfle yazım: "${sade}"` });
  }

  // 3. uzunluk
  const n = kelimeler(g.metin).length;
  if (n > SINIR[g.sinif])
    bulgular.push({ anahtar: g.anahtar, kural: "uzunluk", ayrinti: `${n} kelime, sınır ${SINIR[g.sinif]}` });

  // 4. 2.5.3 — gorunen metin adin icinde olmali
  if (g.ad !== null && !kucuk(g.ad).includes(kucuk(g.metin)))
    bulgular.push({ anahtar: g.anahtar, kural: "2.5.3", ayrinti: `görünen "${g.metin}", ad "${g.ad}"` });
}

console.log(`katalog: ${KATALOG.length} girdi, bulgu: ${bulgular.length}\n`);
console.log("anahtar             kural     ayrinti");
for (const b of bulgular)
  console.log(`${b.anahtar.padEnd(20)}${b.kural.padEnd(10)}${b.ayrinti}`);

const sayim = {};
for (const b of bulgular) sayim[b.kural] = (sayim[b.kural] ?? 0) + 1;
console.log("\nkural bazinda: " + Object.entries(sayim).map(([k, v]) => `${k}=${v}`).join(", "));

// --- Ayni kavramin kac ayri sozcukle anildigi -------------------------------
console.log("\nkavram        kullanilan sozcukler");
for (const [dogru, yasak] of Object.entries(TERIM)) {
  const gorulen = new Set();
  for (const g of KATALOG) {
    if (gecer(g.metin, dogru.split(" ")[0])) gorulen.add(dogru);
    for (const y of yasak) if (gecer(g.metin, y)) gorulen.add(y);
  }
  console.log(`${dogru.padEnd(14)}${gorulen.size ? [...gorulen].join(", ") : "—"}   (${gorulen.size} biçim)`);
}
```

```
katalog: 14 girdi, bulgu: 10

anahtar             kural     ayrinti
dugme.emanet        terim     "emanet" yerine "ödünç alma"
dugme.suzgec        terim     "filtre" yerine "süzgeç"
dugme.suzgec        2.5.3     görünen "Filtreyi uygula", ad "Uygula"
dugme.disa          uzunluk   4 kelime, sınır 3
baslik.ilk          ton       büyük harfle yazım: "KATALOGDA"
baslik.ilk          ton       büyük harfle yazım: "ARAMA"
baslik.ilk          ton       büyük harfle yazım: "YAPIN"
hata.isbn           ton       ünlem
hata.isbn           ton       suçlayan kalıp: "yanlış girdiniz"
yardim.materyal     terim     "materyal" yerine "kayıt"

kural bazinda: terim=3, 2.5.3=1, uzunluk=1, ton=5

kavram        kullanilan sozcukler
ödünç alma    ödünç alma, emanet   (2 biçim)
kayıt         kayıt, materyal   (2 biçim)
süzgeç        filtre, süzgeç   (2 biçim)
üye           üye   (1 biçim)
```

On dört girdilik küçük bir katalogda on bulgu çıkıyor. Bulgular dört ayrı türden ve her
biri farklı bir maliyet üretir.

**Terim kayması üç girdide görünüyor.** Son tablo bunun toplam etkisini veriyor: dört
kavramın üçü arayüzde iki ayrı sözcükle anılıyor. Kullanıcı için bu, üç kavramın altı
kavram gibi görünmesi demektir. Terim kayması aynı zamanda bir çeviri maliyetidir; her
eşanlamlı, çeviri kataloğunda ayrı bir anahtar ve ayrı bir karar üretir.

**Büyük harfle yazım üç bulgu üretiyor** ve tek bir başlıktan geliyor. Büyük harf yalnız
bir üslup kararı değildir: harflerin dış hatları benzeştiği için okuma yavaşlar ve bazı
okuma araçları büyük harfli sözcükleri kısaltma sanıp harf harf okuyabilir. Vurgu, biçim
kararlarıyla verilir — ağırlık, boyut, konum.

**Suçlayan kalıp ve ünlem aynı girdide.** "ISBN yanlış girdiniz!" iletisi kullanıcıya neyin
yanlış olduğunu söylemez, ne yapacağını söylemez ve suçu ona yükler. Aynı kataloğun
`hata.tarih` girdisi karşı örnektir: "İade tarihi bugünden sonra olmalı. Takvimden bir gün
seçin." Kuralı ve çözümü birlikte verir, hiçbir denetime takılmaz.

**Uzunluk sınırı tek girdide aşılıyor.** "Seçili kayıtları dışa aktar" dört kelimedir ve
eylem sınırını aşar; Mikro Metin dersindeki genişlik hesabı bu sınırın nereden geldiğini
gösterir.

**2.5.3 bulgusu en sessiz kusurdur.** Ekranda "Filtreyi uygula" yazan düğmenin adı
"Uygula"; görsel olarak hiçbir sorun görünmez. Sesle denetim kullanan biri ekrandaki
sözcüğü söylediğinde düğme çağrılmaz. Denetimin bunu yakalaması, kuralın makine tarafından
sınanabilir yazılmasının doğrudan kazancıdır.

Betikteki `gecer` işlevi ayrıca Türkçeye özgü bir tuzağı çözüyor. Alt dizgi araması "türünü"
sözcüğü içinde "ürün" bulur ve yanlış bir bulgu üretir. Eklerin sona geldiği bir dilde eşleşme
**sözcük başından** aranır ve sonu serbest bırakılır; böylece "materyal" ile "materyalin" birlikte
yakalanır, "türünü" yakalanmaz.

#### Rehbere Girmesi Gereken Kararlar

Denetim, kuralı uygular ama kuralı seçmez. Rehberin düzyazı bölümünde şu kararlar
gerekçeleriyle yazılır.

**Kime seslenilir.** Arayüz metni kullanıcının diliyle yazılır, sistemin diliyle değil.
"Kayıt oluştur" veri tabanının işidir; "Kitap ekle" kullanıcının işidir.

**Kim özne olur.** Sistem iletilerinde özne sistemdir: "İstek gönderilemedi". Kullanıcının
eylemini anlatan yerlerde özne kullanıcıdır: "Ödünç aldığınız kayıtlar". Karışması,
sorumluluğun yanlış yere düşmesine yol açar.

**Sayı ve tarih nasıl yazılır.** Bu karar bir sonraki derslerin konusudur ve rehberde tek
bir satırla belirtilir: sayı ve tarih elle biçimlenmez.

**Kısaltma ne zaman kullanılır.** Kısaltma yalnız kullanıcı sözlüğünde bulunanlar için
serbesttir; arayüz içi kısaltmalar yasaktır.

#### Sık Yapılan Hata ve Nasıl Anlaşılır

**Rehber var, denetim yok.** Anlaşılması: kataloğu yasaklı sözcük listesiyle taramak;
bulgu çıkıyorsa rehber uygulanmıyordur.

**Aynı anahtarın iki yerde farklı metni.** Anlaşılması: kavram başına kullanılan sözcük
sayısını çıkarmak; birden büyük her sayı bir karardır.

**Görünen metin adın içinde değil.** Anlaşılması: denetimin dördüncü kuralını
çalıştırmak; görsel incelemeyle yakalanamaz.

**Metin koda gömülü.** Denetlenecek bir katalog yoksa hiçbir kural uygulanamaz.
Anlaşılması: metnin koddan ayrılıp ayrılmadığına bakmak; Uygulama Mimarisi kursundaki
çeviri kataloğu kurulumu bu denetimin önkoşuludur.

#### Özet

- Arayüz metni rehberi, ancak her kuralın makine tarafından sınanabilir bir karşılığı varsa
  uygulanır; örneksiz ve karşı örneksiz kurallar yorum bırakır.
- Dört kural ailesi yeterlidir: terim defteri, ton, sınıf başına uzunluk sınırı ve görünen
  metnin erişilebilir adın içinde bulunması.
- Terim kayması yalnız tutarsızlık değil, çeviri kataloğunda çoğalan anahtar demektir; on
  dört girdilik katalogda dört kavramın üçü iki ayrı sözcükle anılıyordu.
- Büyük harfle yazım ve ünlem vurgu aracı değildir; vurgu ağırlık, boyut ve konumla verilir.
- 2.5.3 ölçütü görsel incelemeyle yakalanamaz: ekranda görünen etiket erişilebilir adın
  içinde bulunmalıdır.
- Türkçe gibi ekli dillerde sözcük denetimi alt dizgi aramasıyla yapılmaz; eşleşme sözcük
  başından aranır ve sonu serbest bırakılır.

#### Sonraki Adım

Denetimin yakaladığı en ağır bulgu bir hata iletisiydi ve karşı örneği aynı katalogda
duruyordu. İki iletinin farkı üslup değil **yapı**dır: biri neyin yanlış olduğunu söyler,
öteki neyin doğru olduğunu ve nasıl yapılacağını. Sonraki ders hata iletisini kaynağına
göre sınıflandırır, her sınıf için hangi parçaların zorunlu olduğunu belirler ve iletileri
bu parçalara göre denetleyen bir kural kümesi kurar.

### Hata Mesajı Yazımı

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/hata-mesaji-yazimi

Hata iletisinin kaynağa göre sınıflandırılması, her sınıfın zorunlu parçaları, parçaları tam ama içeriksiz iletilerin dil denetimiyle yakalanması ve iletinin yeri ile ömrünün sınıftan türetilmesi.

Önceki dersin denetimi en ağır bulguyu bir hata iletisinde bulmuştu: "ISBN yanlış girdiniz!" Karşı
örneği aynı katalogda duruyordu ve aradaki fark üslup değil **yapıydı**. Birincisi neyin yanlış
olduğunu söylüyor; ikincisi neyin doğru olduğunu ve kullanıcının ne yapacağını söylüyordu.

Bu ders hata iletisini bir metin sorunu olarak değil, **parçaları belli bir yapı** olarak
ele alır. Yapının parçaları iletinin kaynağına göre değişir; kaynak sınıflandırılmadan
hangi parçanın zorunlu olduğu bilinemez.

#### Hata Kaynağa Göre Sınıflandırılır

Beş sınıf, katalog arayüzündeki bütün hataları karşılar. **Girdi hatası** kullanıcının
yazdığı değerin biçim ya da aralık kuralını karşılamamasıdır; kullanıcı düzeltebilir.
**Yetki hatası** eylemin kullanıcının hakları dışında olmasıdır; kullanıcı düzeltemez ama
bir yol izleyebilir. **Çatışma** eylemin başka bir durum yüzünden yapılamamasıdır — cilt
başkasında. **Geçici hata** sistemin o an yanıt verememesidir. **Kurtarılamaz hata** ise
kullanıcının hiçbir adımının sonucu değiştirmediği durumdur.

Sınıf üç şeyi belirler: hangi parçaların zorunlu olduğunu, iletinin nereye konacağını ve ne
kadar kalacağını. Aşağıdaki hesap bu kuralları katalogdan toplanmış altı ileti üzerinde
çalıştırır.

```js
// hata.mjs — hata iletilerinin kaynagina gore siniflandirilmasi ve parca denetimi

// Her sinif icin zorunlu parcalar. "cozum" kullanicinin atabilecegi adimdir.
const SINIF = {
  girdi:        { parcalar: ["neden", "cozum"],            alanaBagli: true,  kalici: false },
  yetki:        { parcalar: ["neden", "cozum"],            alanaBagli: false, kalici: true  },
  catisma:      { parcalar: ["neden", "cozum", "durum"],   alanaBagli: false, kalici: true  },
  gecici:       { parcalar: ["neden", "cozum"],            alanaBagli: false, kalici: false },
  kurtarilamaz: { parcalar: ["neden", "kimlik"],           alanaBagli: false, kalici: true  },
};

// Katalog arayuzunden toplanan iletiler.
const ILETILER = [
  { kimlik: "H1", sinif: "girdi", alan: "isbn", metin: "ISBN yanlış girdiniz!",
    parcalar: ["neden"] },
  { kimlik: "H2", sinif: "girdi", alan: "isbn",
    metin: "ISBN 13 hane olmalı; kitabın arka kapağındaki çizgi kodun altında yazar.",
    parcalar: ["neden", "cozum"] },
  { kimlik: "H3", sinif: "yetki", alan: null,
    metin: "Bu kaydı ödünç alma yetkiniz yok. Ödünç hakkı öğrenci kartıyla açılır; kütüphane girişindeki bankoya başvurun.",
    parcalar: ["neden", "cozum"] },
  { kimlik: "H4", sinif: "catisma", alan: null,
    metin: "Bu cilt sizden önce ödünç alındı. Şu an rafta değil; iade tarihi 12 Ağustos, ayırtma listesine eklenebilirsiniz.",
    parcalar: ["neden", "cozum", "durum"] },
  { kimlik: "H5", sinif: "gecici", alan: null, metin: "Bir hata oluştu. Lütfen tekrar deneyin.",
    parcalar: ["neden", "cozum"] },
  { kimlik: "H6", sinif: "kurtarilamaz", alan: null,
    metin: "Ödünç kaydı oluşturulamadı. Kayıt numarası KYT-4471 ile bankoya başvurabilirsiniz.",
    parcalar: ["neden", "kimlik"] },
];

// 1. Parca denetimi: her ileti sinifinin zorunlu parcalarini tasiyor mu?
console.log("ileti  sinif          zorunlu parcalar        eksik");
let eksikToplam = 0;
for (const i of ILETILER) {
  const zorunlu = SINIF[i.sinif].parcalar;
  const eksik = zorunlu.filter((p) => !i.parcalar.includes(p));
  eksikToplam += eksik.length;
  console.log(
    `${i.kimlik.padEnd(6)} ${i.sinif.padEnd(14)} ${zorunlu.join(", ").padEnd(23)} ${eksik.length ? eksik.join(", ") : "-"}`
  );
}
console.log(`eksik parca toplami: ${eksikToplam}`);

// 2. Dil denetimi: suclayan kalip, unlem, ozur ve teknik siziinti.
const KURALLAR = [
  { ad: "suclayan kalip", sina: (m) => /\b(girdiniz|yaptiniz|yanlış girdiniz|hatalı girdiniz)\b/i.test(m) },
  { ad: "unlem", sina: (m) => m.includes("!") },
  { ad: "ozur kalibi", sina: (m) => /(özür|üzgünüz|maalesef)/i.test(m) },
  { ad: "bos nezaket", sina: (m) => /(lütfen tekrar deneyin|bir hata oluştu)/i.test(m) },
  { ad: "teknik sizinti", sina: (m) => /(null|undefined|exception|error code \d|HTTP \d{3})/i.test(m) },
];
console.log("\nileti  bulgular");
let bulguToplam = 0;
for (const i of ILETILER) {
  const bulgular = KURALLAR.filter((k) => k.sina(i.metin)).map((k) => k.ad);
  bulguToplam += bulgular.length;
  console.log(`${i.kimlik.padEnd(6)} ${bulgular.length ? bulgular.join(", ") : "-"}`);
}
console.log(`dil bulgusu toplami: ${bulguToplam}`);

// 3. Sunum karari: ileti nereye ve ne kadar kalir?
console.log("\nileti  sunum yeri                 kalicilik");
for (const i of ILETILER) {
  const s = SINIF[i.sinif];
  console.log(
    `${i.kimlik.padEnd(6)} ${(s.alanaBagli ? "alanin yaninda" : "eylemin yaninda / sayfa ustu").padEnd(26)} ` +
      (s.kalici ? "kullanici kapatana kadar" : "sonraki denemeye kadar")
  );
}

// 4. Ileti uzunlugu: okunabilirlik siniri.
const SINIR = 120;
console.log("\nileti  karakter  sinir asimi");
for (const i of ILETILER) {
  console.log(`${i.kimlik.padEnd(6)} ${String(i.metin.length).padStart(8)}  ${i.metin.length > SINIR ? "evet" : "hayir"}`);
}

// 5. Toplu denetim ozeti: hangi iletiler yeniden yazilmali?
const yenidenYaz = ILETILER.filter((i) => {
  const eksik = SINIF[i.sinif].parcalar.filter((p) => !i.parcalar.includes(p)).length;
  const bulgu = KURALLAR.filter((k) => k.sina(i.metin)).length;
  return eksik > 0 || bulgu > 0;
});
console.log(`\nyeniden yazilmasi gereken ileti: ${yenidenYaz.length} / ${ILETILER.length}`);
for (const i of yenidenYaz) console.log(`  ${i.kimlik}: ${JSON.stringify(i.metin)}`);
```

```
ileti  sinif          zorunlu parcalar        eksik
H1     girdi          neden, cozum            cozum
H2     girdi          neden, cozum            -
H3     yetki          neden, cozum            -
H4     catisma        neden, cozum, durum     -
H5     gecici         neden, cozum            -
H6     kurtarilamaz   neden, kimlik           -
eksik parca toplami: 1

ileti  bulgular
H1     suclayan kalip, unlem
H2     -
H3     -
H4     -
H5     bos nezaket
H6     -
dil bulgusu toplami: 3

ileti  sunum yeri                 kalicilik
H1     alanin yaninda             sonraki denemeye kadar
H2     alanin yaninda             sonraki denemeye kadar
H3     eylemin yaninda / sayfa ustu kullanici kapatana kadar
H4     eylemin yaninda / sayfa ustu kullanici kapatana kadar
H5     eylemin yaninda / sayfa ustu sonraki denemeye kadar
H6     eylemin yaninda / sayfa ustu kullanici kapatana kadar

ileti  karakter  sinir asimi
H1           21  hayir
H2           72  hayir
H3          110  hayir
H4          112  hayir
H5           39  hayir
H6           82  hayir

yeniden yazilmasi gereken ileti: 2 / 6
  H1: "ISBN yanlış girdiniz!"
  H5: "Bir hata oluştu. Lütfen tekrar deneyin."
```

#### Zorunlu Parçalar

Çıktının ilk bölümü parça denetimidir. Her sınıf için **neden** zorunludur: kullanıcı neyin
olduğunu bilmelidir. Girdi, yetki, çatışma ve geçici hatalarda ayrıca **çözüm** zorunludur —
kullanıcının atabileceği bir adım. Çatışmada bir de **durum** bilgisi gerekir: cildin ne
zaman döneceği, alternatifin ne olduğu.

Kurtarılamaz hata bu kalıbın istisnasıdır. Çözüm önermek yanıltıcı olur; onun yerine
**kimlik** zorunludur: kullanıcı bir yetkiliyle konuşacaksa elinde bir kayıt numarası
olmalıdır. Bu ayrım Hata ve Uyarı Durumları dersinde kurulmuştu; burada iletinin metnine
uygulanıyor.

Modelde yalnız bir ileti parça denetiminden kalıyor: "ISBN yanlış girdiniz!" nedeni söylüyor, ama
çözümü söylemiyor. Karşı örneği hem kuralı hem de kuralın nerede yazdığını veriyor — hane sayısı
ve çizgi kodun altındaki yer.

#### Parçalar Tam, İleti Boş

İkinci bölüm dil denetimidir ve daha ince bir kusuru yakalıyor. Geçici hata iletisi parça
denetiminden geçiyor: bir nedeni var, bir çözümü var. Ama ikisi de içeriksiz. "Bir hata
oluştu" bir neden değildir, "tekrar deneyin" ise koşulsuz bir öneridir — kullanıcı ne kadar
bekleyeceğini ve tekrar denemenin niçin işe yarayacağını bilmez.

Denetim bunu boş nezaket kalıbı olarak işaretliyor. Aynı bölüm suçlayan kalıbı ve ünlemi de
yakalıyor: "girdiniz" eki hatayı kullanıcıya bağlar, ünlem ise onu yükseltir. Teknik sızıntı
kuralı da buradadır; sistemin iç hata kodunu kullanıcıya göstermek bilgi vermez, yalnız
güveni sarsar. Kayıt numarası ise sızıntı değildir: kullanıcının bir yetkiliye
verebileceği, o iş için üretilmiş bir kimliktir.

#### İletinin Yeri ve Ömrü

Üçüncü bölüm sunum kararını sınıftan türetiyor. Girdi hatası alanın yanında durur — hangi
alanın sorunlu olduğu ancak konumla anlaşılır ve bu bağ Erişilebilir Hata Sunumu dersindeki
gibi işaretlemede de kurulur. Diğer sınıflar eyleme ya da sayfaya bağlıdır.

Ömür de sınıftan gelir. Girdi ve geçici hata bir sonraki denemeye kadar durur; kullanıcı
yeniden denediğinde ileti anlamını yitirir. Yetki, çatışma ve kurtarılamaz hata ise
kullanıcı kapatana kadar kalır: bunlar tekrar denemekle çözülmez ve kullanıcı bilgiyi
okumak, not almak ya da başka birine iletmek isteyebilir.

Dördüncü bölüm uzunluğu ölçüyor. Sınır katı bir kural değil, bir uyarı eşiğidir: bir hata
iletisi bir paragraf uzunluğuna geldiyse çoğu zaman iki ayrı bilgi taşıyordur ve ikincisi
yardım metnine aittir.

#### Denetimin Çıktısı

Son bölüm iki iletiyi yeniden yazılmak üzere işaretliyor. Denetimin değeri sayının kendisi
değil, **hangi kuralın** düştüğünü söylemesidir: biri eksik parça, öteki içeriksiz parça.
İki kusur farklı düzeltme gerektirir — birincisine bilgi eklenir, ikincisinde var olan
bilginin yerine gerçek bilgi konur.

Bu denetim rehber dosyasının bir parçasıdır ve sürüm kontrolünde çalışır: yeni bir ileti
eklendiğinde sınıfı yazılır, parçaları denetlenir ve dil kuralları taranır. Kural kümesi
büyüdükçe yanlış alarm da üretir; her yanlış alarm kuralın daraltılması için bir kayıttır.

#### Özet

- Hata iletisi bir üslup sorunu değil, parçaları kaynağa göre belirlenen bir yapıdır: neden
  her sınıfta zorunludur, çözüm dört sınıfta zorunludur, kurtarılamaz hatada yerini kimlik
  bilgisi alır.
- Parça denetimi eksik bilgiyi yakalar, dil denetimi içeriksiz bilgiyi: "bir hata oluştu,
  tekrar deneyin" iletisi parça denetiminden geçer ama boş nezaket kalıbına takılır.
- Suçlayan ek, ünlem, özür kalıbı ve teknik sızıntı taranabilir kurallardır; kayıt numarası
  sızıntı değil, kullanıcının kullanabileceği bir kimliktir.
- İletinin yeri ve ömrü sınıftan türetilir: girdi hatası alanın yanında ve bir sonraki
  denemeye kadar, yetki ve çatışma kullanıcı kapatana kadar durur.
- Denetim hangi kuralın düştüğünü söyler; eksik parça ile içeriksiz parça farklı düzeltme
  gerektirir.

#### Sonraki Adım

Bu iki ders arayüz metnini tek bir dilde ele aldı. Katalog başka bir dile açıldığında
metnin kendisi kadar **çevresi** de değişir: aynı ileti bazı dillerde yüzde otuz uzar ve
kutusuna sığmaz, tarih ve sayı biçimleri değişir, çoğul kuralları ikiden fazla biçim
gerektirir. Sonraki ders yerelleştirme gereksinimlerini ele alır ve bunların hepsini
hesapla ölçer: metin uzamasının düzene etkisi, biçimlemenin yerel ayardan türetilmesi ve
dizgi birleştirmenin neden çeviriyi bozduğu.

### Yerelleştirme Gereksinimleri

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/yerellestirme-gereksinimleri

Biçimlemenin yerel ayardan türetilmesi, çoğul biçimi sayısının dile göre değişmesi, kısa dizgilerin orantılı olarak daha çok uzaması, dizgi birleştirmenin sözcük sırasını gömmesi ve çeviri kapsamının ölçülmesi.

Önceki iki ders arayüz metnini tek bir dilde ele aldı: rehber kuralları ve hata iletisinin
yapısı. Katalog başka bir dile açıldığında metnin kendisi kadar çevresi de değişir. Aynı
düğme yazısı bazı dillerde iki katına çıkar ve kutusuna sığmaz; tarih, sayı ve yüzde
biçimleri değişir; "kayıt bulundu" gibi bir cümlenin sözcük sırası dilden dile aynı kalmaz.

Bu ders yerelleştirmeyi çeviri işi olarak değil, **arayüzün taşıması gereken bir esneklik**
olarak ele alır. Esnekliğin ölçüleri vardır ve hepsi hesaplanabilir.

#### Biçimleme Metnin Parçası Değildir

Tarih, sayı, yüzde ve para birimi arayüz metnine yazılmaz; yerel ayardan türetilir. Aşağıdaki
hesap aynı değerin altı yerel ayardaki karşılığını üretir ve çoğul kurallarını sayar.

```js
// yerel.mjs — metin uzamasi, bicimleme ve cogul kurallarinin yerel ayardan turetilmesi

const DILLER = ["tr", "en", "de", "fr", "ru", "ja"];

// 1. Bicimleme: ayni deger, alti yerel ayar.
const TARIH = new Date(Date.UTC(2026, 7, 12, 9, 30));
const SAYI = 1234567.89;
console.log("yerel  tarih (uzun)            sayi              yuzde");
for (const d of DILLER) {
  const tarih = new Intl.DateTimeFormat(d, { dateStyle: "long", timeZone: "UTC" }).format(TARIH);
  const sayi = new Intl.NumberFormat(d).format(SAYI);
  const yuzde = new Intl.NumberFormat(d, { style: "percent", maximumFractionDigits: 1 }).format(0.427);
  console.log(`${d.padEnd(6)} ${tarih.padEnd(24)} ${sayi.padEnd(17)} ${yuzde}`);
}

// 2. Cogul kurallari: kac ayri bicim gerekiyor?
console.log("\nyerel  gereken cogul bicimi  1        2        5        11       0.5");
for (const d of DILLER) {
  const kural = new Intl.PluralRules(d);
  const bicimler = new Set([0, 1, 2, 3, 5, 11, 21, 100].map((n) => kural.select(n)));
  const ornek = [1, 2, 5, 11, 0.5].map((n) => kural.select(n).padEnd(8)).join(" ");
  console.log(`${d.padEnd(6)} ${String(bicimler.size).padStart(18)}  ${ornek}`);
}

// 3. Liste birlestirme: dizgi toplama neden kirilir?
console.log("\nyerel  liste birlestirme (uc oge)");
for (const d of DILLER) {
  const liste = new Intl.ListFormat(d, { style: "long", type: "conjunction" })
    .format(["gökbilim", "meteoroloji", "arşivcilik"]);
  console.log(`${d.padEnd(6)} ${liste}`);
}

// 4. Metin uzamasi: kisa dizgiler orantili olarak daha cok uzar.
// Olcum yerine kural: uzunluga gore beklenen uzama pay araligi.
const UZAMA = [
  { altSinir: 1,  ustSinir: 10,  pay: 2.0 },
  { altSinir: 11, ustSinir: 20,  pay: 1.8 },
  { altSinir: 21, ustSinir: 30,  pay: 1.6 },
  { altSinir: 31, ustSinir: 50,  pay: 1.4 },
  { altSinir: 51, ustSinir: 70,  pay: 1.3 },
  { altSinir: 71, ustSinir: 999, pay: 1.15 },
];
const payBul = (n) => UZAMA.find((u) => n >= u.altSinir && n <= u.ustSinir).pay;

const ARAYUZ = [
  { anahtar: "dugme.odunc", metin: "Ödünç al", kutu: 120 },
  { anahtar: "dugme.iptal", metin: "İptal", kutu: 90 },
  { anahtar: "etiket.raf", metin: "Raf kodu", kutu: 110 },
  { anahtar: "baslik.sonuc", metin: "Arama sonuçları", kutu: 220 },
  { anahtar: "yardim.isbn", metin: "ISBN 13 hane olmalı; çizgi kodun altında yazar.", kutu: 420 },
];
const KARAKTER_GENISLIGI = 8; // px, gövde metni için ortalama
console.log("\nanahtar         karakter  pay   en genis hal (px)  kutu  sigar mi");
for (const a of ARAYUZ) {
  const pay = payBul(a.metin.length);
  const genislik = Math.ceil(a.metin.length * pay * KARAKTER_GENISLIGI);
  console.log(
    `${a.anahtar.padEnd(15)} ${String(a.metin.length).padStart(8)} ${pay.toFixed(2).padStart(5)} ` +
      `${String(genislik).padStart(17)} ${String(a.kutu).padStart(5)}  ${genislik <= a.kutu ? "evet" : "HAYIR"}`
  );
}

// 5. Dizgi birlestirme tuzagi: parcali cumle ile tam cumle.
const parcali = (n) => "Toplam " + n + " kayıt bulundu";       // parcalar ayri cevrilir
const tam = (n, dil) =>
  new Intl.NumberFormat(dil).format(n) +
  ({ tr: " kayıt bulundu", en: " records found" }[dil] ?? " kayıt bulundu");
console.log("\nparcali kurulum : " + parcali(1234567));
console.log("tam kurulum (tr): " + tam(1234567, "tr"));
console.log("tam kurulum (en): " + tam(1234567, "en"));
console.log("  -> parcali kurulumda sayi bicimlenmez ve sozcuk sirasi sabitlenir.");

// 6. Ceviri kapsami: eksik anahtar denetimi.
const KATALOG = {
  tr: { "dugme.odunc": "Ödünç al", "dugme.iptal": "İptal", "etiket.raf": "Raf kodu", "baslik.sonuc": "Arama sonuçları", "yardim.isbn": "ISBN 13 hane olmalı." },
  en: { "dugme.odunc": "Borrow", "dugme.iptal": "Cancel", "etiket.raf": "Shelf mark", "baslik.sonuc": "Search results" },
  de: { "dugme.odunc": "Ausleihen", "dugme.iptal": "Abbrechen" },
};
const anahtarlar = Object.keys(KATALOG.tr);
console.log("\ndil  ceviri  kapsam  eksik anahtarlar");
for (const [dil, tablo] of Object.entries(KATALOG)) {
  const eksik = anahtarlar.filter((a) => !(a in tablo));
  console.log(
    `${dil.padEnd(4)} ${String(Object.keys(tablo).length).padStart(6)}  ` +
      `%${String(Math.round((100 * (anahtarlar.length - eksik.length)) / anahtarlar.length)).padStart(4)}  ${eksik.join(", ") || "-"}`
  );
}
```

```
yerel  tarih (uzun)            sayi              yuzde
tr     12 Ağustos 2026          1.234.567,89      %42,7
en     August 12, 2026          1,234,567.89      42.7%
de     12. August 2026          1.234.567,89      42,7 %
fr     12 août 2026             1 234 567,89      42,7 %
ru     12 августа 2026 г.       1 234 567,89      42,7 %
ja     2026年8月12日               1,234,567.89      42.7%

yerel  gereken cogul bicimi  1        2        5        11       0.5
tr                      2  one      other    other    other    other   
en                      2  one      other    other    other    other   
de                      2  one      other    other    other    other   
fr                      2  one      other    other    other    one     
ru                      3  one      few      many     many     other   
ja                      1  other    other    other    other    other   

yerel  liste birlestirme (uc oge)
tr     gökbilim, meteoroloji ve arşivcilik
en     gökbilim, meteoroloji, and arşivcilik
de     gökbilim, meteoroloji und arşivcilik
fr     gökbilim, meteoroloji et arşivcilik
ru     gökbilim, meteoroloji и arşivcilik
ja     gökbilim、meteoroloji、arşivcilik

anahtar         karakter  pay   en genis hal (px)  kutu  sigar mi
dugme.odunc            8  2.00               128   120  HAYIR
dugme.iptal            5  2.00                80    90  evet
etiket.raf             8  2.00               128   110  HAYIR
baslik.sonuc          15  1.80               216   220  evet
yardim.isbn           47  1.40               527   420  HAYIR

parcali kurulum : Toplam 1234567 kayıt bulundu
tam kurulum (tr): 1.234.567 kayıt bulundu
tam kurulum (en): 1,234,567 records found
  -> parcali kurulumda sayi bicimlenmez ve sozcuk sirasi sabitlenir.

dil  ceviri  kapsam  eksik anahtarlar
tr        5  % 100  -
en        4  %  80  yardim.isbn
de        2  %  40  etiket.raf, baslik.sonuc, yardim.isbn
```

Çıktının ilk bölümü tek bir tarihin ve tek bir sayının altı ayrı görünümünü veriyor:
ayıraçlar değişiyor, ay adının yeri değişiyor, yüzde işareti kimi dilde önde kimi dilde
arkada duruyor ve arada boşluk olup olmadığı bile dile bağlı. Bu farkların hiçbiri
tasarımcının kararı değildir; kararın tamamı yerel ayara aittir.

Kural şudur: biçimleme çalışma zamanında yapılır. Metin kataloğuna "12 Ağustos 2026" yazmak, o
dizgiyi bir dilde dondurmaktır; katalog yalnız **kalıbı** taşır, değeri de biçimleyici üretir.

#### Çoğul İki Biçimden İbaret Değildir

İkinci bölüm sık yapılan bir varsayımı yıkıyor. Türkçe, İngilizce, Almanca ve Fransızca iki çoğul
biçimiyle yetinirken Rusça tam sayılarda üç ayrı biçim gerektiriyor; Japoncada ise sayıya göre
biçim değişmiyor. Ondalık sayılar da ayrı davranıyor: Fransızcada 0.5 tekil biçimi alıyor,
Türkçede almıyor, Rusçada ise dördüncü bir biçimi devreye sokuyor.

Bunun sonucu doğrudandır: "1 kayıt" ve "N kayıt" biçiminde iki dallı bir kurulum, üç biçim
gerektiren dilde yanlış metin üretir. Doğru kurulum çoğul biçimini yerel ayardan sorar ve
her biçim için ayrı bir metin tutar. Aynı şey liste birleştirmede de geçerlidir: üçüncü
bölümdeki çıktı, virgül ve bağlacın dilden dile değiştiğini — Japoncada bağlacın hiç
kullanılmadığını — gösteriyor.

#### Metin Uzaması Bir Düzen Kısıtıdır

Dördüncü bölüm uzamayı ölçüyor. Kural şudur: kısa dizgiler orantılı olarak daha çok uzar.
Sekiz karakterlik bir düğme yazısı iki katına çıkabilirken elli karakterlik bir yardım metni
yaklaşık yüzde kırk uzar. Nedeni, kısa metinlerin çoğu dilde tek bir sözcükle
karşılanamamasıdır.

Hesap üç bileşenin en geniş hâlinin kutusuna sığmadığını gösteriyor. Bu bulgu tasarım
aşamasına aittir: kutuların sabit genişlikte verilmesi, arayüzü tek dile bağlar. Çözüm iç
boşluğa göre büyüyen kutular, satır kaymasına izin veren düzen ve — Düzen ve Aralık
konusundaki kuralla — genişliğin içerikten türetilmesidir.

Ölçünün kendisi bir tahmindir ve tahmin olduğu söylenmelidir; gerçek uzama çeviri
yapıldıktan sonra ölçülür. Tahminin işlevi, çeviri gelmeden önce **hangi bileşenlerin
riskli** olduğunu göstermektir.

#### Dizgi Birleştirme Çeviriyi Bozar

Beşinci bölüm parçalı kurulumla tam kurulumu karşılaştırıyor. Parçalı kurulumda sayı ham
hâliyle yapıştırılır ve sözcük sırası koda gömülür: "Toplam " + n + " kayıt bulundu".
Bu yapı iki şeyi birden kaybeder — sayı biçimlenmez ve çevirmen sözcük sırasını
değiştiremez.

Doğru kurulum tek bir kalıp tutar ve değerleri adlandırılmış yuvalarla yerleştirir. Böylece
çevirmen yuvaları kendi dilinin sırasına göre taşıyabilir, sayı da yerel ayara göre
biçimlenir. Bu, Uluslararasılaştırma dersinde kurulan kuralın arayüz metni tarafındaki
karşılığıdır.

#### Kapsam Ölçülür

Son bölüm çeviri kapsamını sayıyor. Eksik anahtar bir kusur değil, bir **durum**dur: çeviri
her zaman koddan geride gelir. Önemli olan eksikliğin görünür olması ve bir yedek davranışın
tanımlanmasıdır — eksik anahtar için hangi dilin metni gösterilecek ve arayüzde bu durum
nasıl işaretlenecek.

Kapsam ölçümü aynı zamanda bir yayın ölçütüdür: bir dil, kapsamı belli bir eşiğin altındayken
kullanıcıya sunulmaz. Eşiğin sayısı ürüne göre değişir; ölçümün varlığı değişmez.

#### Özet

- Tarih, sayı ve yüzde biçimleri metnin parçası değildir; çalışma zamanında yerel ayardan
  türetilir — aynı değer altı yerel ayarda altı ayrı görünüm alır.
- Çoğul biçimi sayısı dile bağlıdır: modelde bir dil üç biçim gerektiriyor, bir dil hiç
  ayırt etmiyor; iki dallı kurulum bu dillerde yanlış metin üretir.
- Kısa dizgiler orantılı olarak daha çok uzar; sabit genişlikli kutular arayüzü tek dile
  bağlar ve modelde üç bileşen en geniş hâlinde kutusuna sığmıyor.
- Dizgi birleştirme sözcük sırasını koda gömer ve sayıyı biçimlenmemiş bırakır; kalıp ve
  adlandırılmış yuva bu iki sorunu birden çözer.
- Çeviri kapsamı ölçülür ve eksik anahtar için yedek davranış tanımlanır; kapsam bir yayın
  ölçütüdür.

#### Sonraki Adım

Bu dersin bütün örnekleri soldan sağa yazılan dillerdeydi. Arayüz sağdan sola yazılan bir
dile açıldığında değişen şey yalnız metnin yönü değildir: düzenin kendisi aynalanır, simge
yönleri anlam değiştirir, sayılar ve gömülü Latin harfli terimler kendi yönlerini korur.
Sonraki ders yön duyarlı düzen kararlarını ele alır ve fiziksel özelliklerle mantıksal
özellikler arasındaki farkın neden bir çeviri sorunu değil bir mimari karar olduğunu
hesapla gösterir.

### Sağdan Sola Diller

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/sagdan-sola-diller

Yön değişiminin biçem katmanındaki karşılığı, mantıksal özelliklerin fiziksel özelliklere üstünlüğü, simge aynalamasının ölçüte bağlanması, iki yönlü metinde yalıtım ve klavye sözleşmelerinin akış yönüyle yazılması.

Önceki dersin bütün örnekleri soldan sağa yazılan dillerdeydi. Katalog sağdan sola yazılan
bir dile açıldığında değişen şey metnin yönünden ibaret değildir: kayıt kartının kenarlığı
karşı tarafa geçer, süzgeç paneli ekranın öbür yanına taşınır, ileri oku ters çevrilir ama
saat simgesi olduğu gibi kalır.

Bu ders yön değişimini bir çeviri sorunu olarak değil, bir **mimari karar** olarak ele alır.
Kararın alındığı yer biçem katmanıdır ve kararın ölçüsü, kaç bildirimin yön değişiminden
etkilendiğidir.

#### Fiziksel Değil Mantıksal Eksen

Sol ve sağ, ekranın sabit yönleridir. Başlangıç ve bitiş ise **akışa** bağlıdır: soldan sağa
yazılan bir düzende başlangıç soldadır; sağdan sola yazılanda sağdadır. Bir bileşenin kenarlığı
"solda" diye yazıldığında yön değiştiğinde de solda kalır ve düzen bozulur; aynı kenarlık "satır
içi başlangıçta" diye yazıldığında akışı izler.

Aşağıdaki hesap katalog bileşenlerinden toplanan bildirimleri fiziksel ve mantıksal
karşılıklarıyla listeler ve kaçının yön duyarlı olduğunu sayar.

```js
// yon.mjs — yon duyarli duzen: mantiksal ozellikler, aynalanan ve aynalanmayan ogeler

// 1. Fiziksel ozelliklerin mantiksal karsiliklari.
const ESLEME = {
  "margin-left": "margin-inline-start",
  "margin-right": "margin-inline-end",
  "padding-left": "padding-inline-start",
  "padding-right": "padding-inline-end",
  "border-left": "border-inline-start",
  "text-align: left": "text-align: start",
  "left": "inset-inline-start",
  "width": "inline-size",
  "margin-top": "margin-block-start",
  "border-bottom": "border-block-end",
};
// Katalog bilesenlerinden toplanmis bildirimler.
const BILDIRIMLER = [
  { bilesen: "kayit-karti", bildirim: "padding-left" },
  { bilesen: "kayit-karti", bildirim: "border-left" },
  { bilesen: "kayit-karti", bildirim: "margin-top" },
  { bilesen: "suzgec-paneli", bildirim: "width" },
  { bilesen: "suzgec-paneli", bildirim: "text-align: left" },
  { bilesen: "odunc-dugmesi", bildirim: "margin-right" },
  { bilesen: "bildirim-seridi", bildirim: "left" },
  { bilesen: "bildirim-seridi", bildirim: "border-bottom" },
];
const yatay = (b) => /left|right|width|inline/.test(b);
console.log("bilesen           fiziksel bildirim     mantiksal karsilik        yon duyarli");
let duyarli = 0;
for (const b of BILDIRIMLER) {
  if (yatay(b.bildirim)) duyarli++;
  console.log(
    `${b.bilesen.padEnd(17)} ${b.bildirim.padEnd(21)} ${ESLEME[b.bildirim].padEnd(25)} ${yatay(b.bildirim) ? "evet" : "hayir"}`
  );
}
console.log(`yon duyarli bildirim: ${duyarli} / ${BILDIRIMLER.length}`);
console.log("dikey eksendeki bildirimler yon degisiminden etkilenmez; yine de mantiksal");
console.log("yazilmalari dikey yazim kiplerinde ayni kazanci verir.");

// 2. Aynalanan ve aynalanmayan ogeler.
const OGELER = [
  { ad: "geri oku", aynalanir: true, gerekce: "yonu akisa bagli" },
  { ad: "ileri oku", aynalanir: true, gerekce: "yonu akisa bagli" },
  { ad: "kirinti ayiraci", aynalanir: true, gerekce: "hiyerarsi yonunu gosterir" },
  { ad: "ilerleme cubugu", aynalanir: true, gerekce: "artis yonu akisi izler" },
  { ad: "saat simgesi", aynalanir: false, gerekce: "gercek dunyada yonu sabit" },
  { ad: "onay isareti", aynalanir: false, gerekce: "yon anlam tasimaz" },
  { ad: "arama buyuteci", aynalanir: false, gerekce: "nesnenin kendi bicimi" },
  { ad: "ses seviyesi simgesi", aynalanir: true, gerekce: "artis yonu akisi izler" },
  { ad: "kalem/duzenle", aynalanir: false, gerekce: "nesnenin kendi bicimi" },
];
console.log("\noge                   aynalanir  gerekce");
for (const o of OGELER) console.log(`  ${o.ad.padEnd(20)} ${(o.aynalanir ? "evet" : "hayir").padEnd(10)} ${o.gerekce}`);
console.log(`aynalanan: ${OGELER.filter((o) => o.aynalanir).length} / ${OGELER.length}`);

// 3. Sayilar ve gomulu Latin terimler kendi yonunu korur.
// Iki yonlu metin: paragrafin temel yonu sagdan sola, icindeki sayi soldan saga yazilir.
const SEGMENTLER = [
  { metin: "raf kodu", yon: "rtl" },
  { metin: "QB-141", yon: "ltr" },
  { metin: "yil", yon: "rtl" },
  { metin: "1998", yon: "ltr" },
];
console.log("\nparagraf temel yonu: rtl");
console.log("segment      yon   yalitim gerekir mi");
for (const s of SEGMENTLER) {
  console.log(`  ${s.metin.padEnd(11)} ${s.yon.padEnd(5)} ${s.yon === "ltr" ? "evet" : "hayir"}`);
}
console.log("yalitimsiz birlestirmede soldan saga segmentlerin komsu noktalama isaretleri");
console.log("yanlis tarafa kayar; bu yuzden gomulu segment yalitilir.");

// 4. Bir dizginin gorunen sirasi: yalitimli ve yalitimsiz.
// Basit model: rtl paragrafta segmentler ters sirada dizilir, ltr segment kendi icinde duz kalir.
function gorunenSira(segmentler, temel) {
  const dizi = temel === "rtl" ? [...segmentler].reverse() : segmentler;
  return dizi.map((s) => s.metin).join(" ");
}
console.log("\nmantiksal sira : " + SEGMENTLER.map((s) => s.metin).join(" "));
console.log("gorunen sira   : " + gorunenSira(SEGMENTLER, "rtl"));

// 5. Kaydirma ve klavye: ok tuslarinin anlami degismez.
const TUSLAR = [
  { tus: "Sag ok", ltr: "sonraki oge", rtl: "onceki oge" },
  { tus: "Sol ok", ltr: "onceki oge", rtl: "sonraki oge" },
  { tus: "Home", ltr: "ilk oge", rtl: "ilk oge" },
  { tus: "End", ltr: "son oge", rtl: "son oge" },
];
console.log("\ntus       soldan saga        sagdan sola");
for (const t of TUSLAR) console.log(`  ${t.tus.padEnd(9)} ${t.ltr.padEnd(18)} ${t.rtl}`);
console.log("ok tuslari fiziksel yonu izler: sag ok her zaman ekranda saga gider,");
console.log("dolayisiyla sagdan sola duzende bir onceki ogeye ulasir.");

// 6. Denetim: fiziksel bildirim kalan bilesenler yayina cikamaz.
const kalan = BILDIRIMLER.filter((b) => yatay(b.bildirim));
const bilesenler = [...new Set(kalan.map((b) => b.bilesen))];
console.log(`\nyon duyarli fiziksel bildirim tasiyan bilesen: ${bilesenler.length}`);
for (const b of bilesenler) {
  const sayi = kalan.filter((x) => x.bilesen === b).length;
  console.log(`  ${b.padEnd(17)} ${sayi} bildirim`);
}
```

```
bilesen           fiziksel bildirim     mantiksal karsilik        yon duyarli
kayit-karti       padding-left          padding-inline-start      evet
kayit-karti       border-left           border-inline-start       evet
kayit-karti       margin-top            margin-block-start        hayir
suzgec-paneli     width                 inline-size               evet
suzgec-paneli     text-align: left      text-align: start         evet
odunc-dugmesi     margin-right          margin-inline-end         evet
bildirim-seridi   left                  inset-inline-start        evet
bildirim-seridi   border-bottom         border-block-end          hayir
yon duyarli bildirim: 6 / 8
dikey eksendeki bildirimler yon degisiminden etkilenmez; yine de mantiksal
yazilmalari dikey yazim kiplerinde ayni kazanci verir.

oge                   aynalanir  gerekce
  geri oku             evet       yonu akisa bagli
  ileri oku            evet       yonu akisa bagli
  kirinti ayiraci      evet       hiyerarsi yonunu gosterir
  ilerleme cubugu      evet       artis yonu akisi izler
  saat simgesi         hayir      gercek dunyada yonu sabit
  onay isareti         hayir      yon anlam tasimaz
  arama buyuteci       hayir      nesnenin kendi bicimi
  ses seviyesi simgesi evet       artis yonu akisi izler
  kalem/duzenle        hayir      nesnenin kendi bicimi
aynalanan: 5 / 9

paragraf temel yonu: rtl
segment      yon   yalitim gerekir mi
  raf kodu    rtl   hayir
  QB-141      ltr   evet
  yil         rtl   hayir
  1998        ltr   evet
yalitimsiz birlestirmede soldan saga segmentlerin komsu noktalama isaretleri
yanlis tarafa kayar; bu yuzden gomulu segment yalitilir.

mantiksal sira : raf kodu QB-141 yil 1998
gorunen sira   : 1998 yil QB-141 raf kodu

tus       soldan saga        sagdan sola
  Sag ok    sonraki oge        onceki oge
  Sol ok    onceki oge         sonraki oge
  Home      ilk oge            ilk oge
  End       son oge            son oge
ok tuslari fiziksel yonu izler: sag ok her zaman ekranda saga gider,
dolayisiyla sagdan sola duzende bir onceki ogeye ulasir.

yon duyarli fiziksel bildirim tasiyan bilesen: 4
  kayit-karti       2 bildirim
  suzgec-paneli     2 bildirim
  odunc-dugmesi     1 bildirim
  bildirim-seridi   1 bildirim
```

Çıktının ilk bölümü sekiz bildirimin altısının yön duyarlı olduğunu gösteriyor. Dikey
eksendeki iki bildirim yön değişiminden etkilenmez; yine de mantıksal yazılmaları, dikey
yazım kiplerinde aynı kazancı verir. Kural basittir: yatay eksene dokunan her bildirim
mantıksal yazılır ve bu, çeviri başlamadan önce alınması gereken bir karardır.

Son bölüm aynı veriyi bir denetim çıktısına çeviriyor: dört bileşen hâlâ fiziksel bildirim
taşıyor. Bu liste, bir dilin yayına alınmasından önce kapatılması gereken iş kalemidir ve
Belirteçler konusundaki denetimlerle aynı biçimde otomatikleştirilir.

#### Her Simge Aynalanmaz

İkinci bölüm simgeleri ayırıyor. Aynalanma ölçütü tek bir sorudur: simgenin yönü **akışa**
mı bağlı, yoksa nesnenin kendi biçimi mi? İleri oku akışa bağlıdır ve aynalanır; kırıntı
ayıracı hiyerarşinin yönünü gösterir ve aynalanır; ilerleme çubuğu artış yönünü akıştan
alır ve aynalanır.

Saat simgesi aynalanmaz, çünkü saatin yönü gerçek dünyada sabittir. Onay işareti aynalanmaz,
çünkü yönü anlam taşımaz. Büyüteç ve kalem aynalanmaz, çünkü bunlar nesnenin kendi biçimidir.
Dokuz simgenin beşi aynalanıyor; ayrım simgenin ne gösterdiğine bakılarak yapılır, toplu bir
dönüşümle değil. Bütün simgeleri aynalayan bir kural, saati ters çevirir.

#### Sayılar ve Gömülü Terimler Kendi Yönünü Korur

Üçüncü ve dördüncü bölüm iki yönlü metni ele alıyor. Sağdan sola bir paragrafın içindeki
sayılar ve Latin harfli terimler — raf kodu, ISBN, ölçüm birimi — kendi yönlerinde yazılır.
Paragrafın temel yönü ile segmentin yönü farklı olduğunda, aradaki noktalama işaretlerinin
hangi tarafa düşeceği belirsizleşir.

Çözüm gömülü segmenti yalıtmaktır: segment kendi yönünü bildirir ve komşularının yönünü
etkilemez. Dördüncü bölümdeki karşılaştırma mantıksal sıra ile görünen sıranın aynı
olmadığını gösteriyor — metnin bellekteki sırası ile ekrandaki sırası farklıdır ve bu fark
kopyalama, arama ve sıralama işlemlerinde de kendini gösterir.

#### Klavye Fiziksel Yönü İzler

Beşinci bölüm sık atlanan bir ayrımı yazıyor. Ok tuşları fiziksel yönü izler: sağ ok her
zaman ekranda sağa gider. Sağdan sola bir listede ekranda sağa gitmek, listenin **bir
önceki** ögesine gitmek demektir.

Bu, Sekmeler ve Menüler derslerindeki klavye sözleşmelerinin sağdan sola düzende yeniden
yazılması gerektiği anlamına gelir: "sağ ok bir sonrakine geçer" cümlesi yön bağımlıdır.
Doğru yazım akışa göredir — "akış yönünde bir sonrakine geçer". Başlangıç ve bitiş tuşları
ise yön değişiminden etkilenmez, çünkü ilk ve son öge akışta tanımlıdır.

#### Ölçülebilir Kısıtlar

- Yatay eksene dokunan her bildirim mantıksal özelliklerle yazılır; denetim fiziksel
  bildirim kalan bileşenleri raporlar.
- Simge aynalaması tek tek karara bağlanır; toplu aynalama saat, onay ve nesne biçimli
  simgeleri bozar.
- Gömülü ters yönlü segmentler yalıtılır; yalıtımsız birleştirme noktalama konumunu
  belirsiz bırakır.
- Ok tuşu sözleşmeleri fiziksel yön yerine akış yönüyle yazılır.
- Metin uzaması kuralı sağdan sola dillerde de geçerlidir; iki kısıt birlikte denetlenir.

#### Sık Yapılan Hata

En sık hata, yön desteğini bir çeviri işi sanıp biçem katmanına hiç dokunmamaktır; bu
durumda metin çevrilir ama düzen soldan sağa kalır ve arayüz okunamaz hâle gelir. İkincisi,
bütün simgeleri toplu aynalamaktır. Üçüncüsü, yön değişimini yalnız bir kök özniteliğiyle
bildirip bileşenlerdeki fiziksel bildirimleri bırakmaktır: bildirim düzeyinde düzeltme
yapılmadan kök bildirimi tek başına yeterli değildir.

#### Özet

- Sol ve sağ ekranın sabit yönleridir; başlangıç ve bitiş akışa bağlıdır. Yatay eksendeki
  her bildirim mantıksal yazılır — modeldeki sekiz bildirimin altısı yön duyarlı.
- Simge aynalaması ölçüte bağlanır: yönü akışa bağlı olan aynalanır, nesnenin kendi biçimi
  olan aynalanmaz; dokuz simgenin beşi aynalanıyor.
- Sağdan sola paragrafta sayılar ve Latin harfli terimler kendi yönünü korur ve yalıtılır;
  metnin mantıksal sırası ile görünen sırası aynı değildir.
- Ok tuşları fiziksel yönü izler; klavye sözleşmeleri bu yüzden akış yönüyle yazılır.
- Denetim, fiziksel bildirim taşıyan bileşenleri listeler; bu liste bir dilin yayına
  alınmasından önce kapatılır.

#### Sonraki Adım

Yön ve biçim kararları arayüzün kabını dile uyarladı. Geriye kabın içine giren **veri**
kalıyor. Kullanıcıdan ad, adres ve kimlik bilgisi isteyen her form bir varsayım taşır: adın
iki parçadan oluştuğu, adresin bir posta koduyla tamamlandığı, kimliğin belli bir uzunlukta
olduğu. Bu varsayımların hepsi bir yerde kırılır. Kursun son dersi bu alanları ele alır ve
varsayımları kurala çevirir.

### Bölgesel Gereklilikler

Source: https://academia.sh/tr/kurslar/erisilebilir-kaliplar/bolgesel-gereklilikler

Ad, adres ve kimlik alanlarındaki varsayımların sayılması, esnek alan kurulumunun bütün kayıtları alması, zorunlulukların ülke tablosundan okunması ve kimlik alanının metin olarak saklanması.

Yön ve biçim kararları arayüzün kabını dile uyarladı. Geriye kabın içine giren veri
kalıyor. Kütüphane üyeliği formu üç şey ister: ad, adres ve bir kimlik numarası. Bu üç
alanın her biri bir varsayım taşır — adın iki parçadan oluştuğu, adresin bir posta koduyla
tamamlandığı, kimliğin belli bir uzunlukta olduğu.

Varsayımlar çoğu kayıtta görünmez, çünkü çoğu kayıt onlara uyar. Uymayan kayıt ise formu
dolduramaz: kullanıcı adını bir alana sığdıramaz, zorunlu posta kodu alanını boş
bırakamaz ve kimlik numarası kabul edilmez. Bu ders varsayımları sayar ve kurala çevirir.

#### Varsayımın Ölçülmesi

Aşağıdaki hesap katı bir formu — iki adlı ad alanı, zorunlu posta kodu ve bölge, sabit
uzunlukta kimlik — örnek kayıtlar üzerinde çalıştırır ve kaçının geçemediğini sayar.

```js
// bolge.mjs — ad, adres ve kimlik alanlarindaki varsayimlarin kurala cevrilmesi

// 1. Ad alani: kati form iki alan varsayar (ad + soyad, ikisi de zorunlu).
const KATI_AD = { alanlar: ["ad", "soyad"], zorunlu: ["ad", "soyad"], enAz: 2 };
// Kayitlar: gercek dunyada karsilasilan ad yapilari (uydurma ornekler, yapi cesitliligi icin).
const ADLAR = [
  { kayit: "A1", parcalar: ["Elif", "Yılmaz"], not: "iki parca" },
  { kayit: "A2", parcalar: ["Ravi"], not: "tek parcali ad" },
  { kayit: "A3", parcalar: ["María", "del", "Carmen", "Ruiz", "García"], not: "iki soyadli, coklu ad" },
  { kayit: "A4", parcalar: ["Nguyễn", "Thị", "Hoa"], not: "aile adi once" },
  { kayit: "A5", parcalar: ["Björn", "Þórsson"], not: "soyadi baba adindan turer" },
  { kayit: "A6", parcalar: ["Jean", "-", "Pierre", "Le", "Roux"], not: "tireli ad, cok sozcuklu soyad" },
];
console.log("kayit  parca  kati forma sigar mi  kayip");
let sigmayan = 0;
for (const a of ADLAR) {
  const gercekParca = a.parcalar.filter((p) => p !== "-").length;
  const sigar = gercekParca === KATI_AD.enAz;
  if (!sigar) sigmayan++;
  const kayip = sigar ? "-" : gercekParca < 2 ? "zorunlu alan bos birakilamaz" : "fazla parcalar tek alana sikisir";
  console.log(`${a.kayit.padEnd(6)} ${String(gercekParca).padStart(5)}  ${(sigar ? "evet" : "HAYIR").padStart(18)}  ${kayip}`);
}
console.log(`kati forma sigmayan: ${sigmayan} / ${ADLAR.length}`);

// Esnek kural: tek serbest alan + istege bagli gorunen ad.
console.log("\nesnek kural: 'tam ad' tek alan (zorunlu) + 'hitap adi' (istege bagli)");
for (const a of ADLAR) {
  const tam = a.parcalar.join(" ").replace(" - ", "-");
  console.log(`  ${a.kayit}  tam ad: ${JSON.stringify(tam).padEnd(34)} (${a.not})`);
}

// 2. Adres alani: kati form posta kodu ve il/eyalet zorunlu varsayar.
const ADRESLER = [
  { kayit: "B1", satirlar: 2, postaKodu: "34710", bolge: "İstanbul", ulke: "TR" },
  { kayit: "B2", satirlar: 3, postaKodu: null,    bolge: "Dublin",   ulke: "IE" },
  { kayit: "B3", satirlar: 1, postaKodu: "1010",  bolge: null,       ulke: "AT" },
  { kayit: "B4", satirlar: 4, postaKodu: "100-0001", bolge: "Tokyo", ulke: "JP" },
  { kayit: "B5", satirlar: 2, postaKodu: "SW1A 1AA", bolge: null,    ulke: "GB" },
];
const KATI_ADRES = { postaKoduZorunlu: true, bolgeZorunlu: true, satirSiniri: 2 };
console.log("\nkayit  satir  posta kodu  bolge     kati formda dusen kural");
let adresDusen = 0;
for (const b of ADRESLER) {
  const dusenler = [];
  if (KATI_ADRES.postaKoduZorunlu && !b.postaKodu) dusenler.push("posta kodu zorunlu");
  if (KATI_ADRES.bolgeZorunlu && !b.bolge) dusenler.push("bolge zorunlu");
  if (b.satirlar > KATI_ADRES.satirSiniri) dusenler.push(`satir sayisi ${b.satirlar} > ${KATI_ADRES.satirSiniri}`);
  if (dusenler.length) adresDusen++;
  console.log(
    `${b.kayit.padEnd(6)} ${String(b.satirlar).padStart(5)}  ${String(b.postaKodu ?? "-").padEnd(11)} ${String(b.bolge ?? "-").padEnd(9)} ${dusenler.join("; ") || "-"}`
  );
}
console.log(`kati adres formunda dusen kayit: ${adresDusen} / ${ADRESLER.length}`);

// 3. Kimlik alani: uzunluk ve bicim varsayimi.
const KIMLIKLER = [
  { kayit: "C1", deger: "12345678901", tur: "11 hane" },
  { kayit: "C2", deger: "AB-9931",     tur: "harf + rakam" },
  { kayit: "C3", deger: "0012",        tur: "basta sifir" },
  { kayit: "C4", deger: "9987654321234", tur: "13 hane" },
];
const KATI_KIMLIK = { uzunluk: 11, yalnizRakam: true };
console.log("\nkayit  deger           kati kurala uyar mi  neden");
for (const c of KIMLIKLER) {
  const uzunlukTamam = c.deger.length === KATI_KIMLIK.uzunluk;
  const rakamTamam = /^\d+$/.test(c.deger);
  const uyar = uzunlukTamam && rakamTamam;
  const neden = uyar ? "-" : [!uzunlukTamam && `uzunluk ${c.deger.length}`, !rakamTamam && "rakam disi karakter"].filter(Boolean).join(", ");
  console.log(`${c.kayit.padEnd(6)} ${c.deger.padEnd(15)} ${(uyar ? "evet" : "HAYIR").padStart(18)}  ${neden}`);
}
console.log("kimlik alani sayi olarak saklanirsa bastaki sifirlar kaybolur; alan metindir.");

// 4. Sonuc: iki form tasarimi karsilastirmasi.
const kayitToplam = ADLAR.length + ADRESLER.length;
const dusenToplam = sigmayan + adresDusen;
console.log(`\nkati form: ${dusenToplam} / ${kayitToplam} kayit dogrudan girilemiyor (%${Math.round((100 * dusenToplam) / kayitToplam)})`);
console.log("esnek form: tam ad tek alan, adres cok satirli serbest metin + ulke,");
console.log("            posta kodu ve bolge zorunlulugu ulkeye gore acilip kapaniyor.");

// 5. Ulkeye gore alan kurali: tek bir tabloda toplanir.
const ULKE_KURALI = {
  TR: { postaKodu: "zorunlu", bolge: "zorunlu", postaKoduBicimi: "5 hane" },
  IE: { postaKodu: "istege bagli", bolge: "zorunlu", postaKoduBicimi: "serbest" },
  AT: { postaKodu: "zorunlu", bolge: "istege bagli", postaKoduBicimi: "4 hane" },
  JP: { postaKodu: "zorunlu", bolge: "zorunlu", postaKoduBicimi: "3-4 hane" },
  GB: { postaKodu: "zorunlu", bolge: "istege bagli", postaKoduBicimi: "serbest" },
};
console.log("\nulke  posta kodu     bolge          bicim");
for (const [u, k] of Object.entries(ULKE_KURALI)) {
  console.log(`  ${u}  ${k.postaKodu.padEnd(14)} ${k.bolge.padEnd(14)} ${k.postaKoduBicimi}`);
}
console.log("kural tablosu veridir; forma gomulmez, ulke secimiyle okunur.");
```

```
kayit  parca  kati forma sigar mi  kayip
A1         2                evet  -
A2         1               HAYIR  zorunlu alan bos birakilamaz
A3         5               HAYIR  fazla parcalar tek alana sikisir
A4         3               HAYIR  fazla parcalar tek alana sikisir
A5         2                evet  -
A6         4               HAYIR  fazla parcalar tek alana sikisir
kati forma sigmayan: 4 / 6

esnek kural: 'tam ad' tek alan (zorunlu) + 'hitap adi' (istege bagli)
  A1  tam ad: "Elif Yılmaz"                      (iki parca)
  A2  tam ad: "Ravi"                             (tek parcali ad)
  A3  tam ad: "María del Carmen Ruiz García"     (iki soyadli, coklu ad)
  A4  tam ad: "Nguyễn Thị Hoa"                   (aile adi once)
  A5  tam ad: "Björn Þórsson"                    (soyadi baba adindan turer)
  A6  tam ad: "Jean-Pierre Le Roux"              (tireli ad, cok sozcuklu soyad)

kayit  satir  posta kodu  bolge     kati formda dusen kural
B1         2  34710       İstanbul  -
B2         3  -           Dublin    posta kodu zorunlu; satir sayisi 3 > 2
B3         1  1010        -         bolge zorunlu
B4         4  100-0001    Tokyo     satir sayisi 4 > 2
B5         2  SW1A 1AA    -         bolge zorunlu
kati adres formunda dusen kayit: 4 / 5

kayit  deger           kati kurala uyar mi  neden
C1     12345678901                   evet  -
C2     AB-9931                      HAYIR  uzunluk 7, rakam disi karakter
C3     0012                         HAYIR  uzunluk 4
C4     9987654321234                HAYIR  uzunluk 13
kimlik alani sayi olarak saklanirsa bastaki sifirlar kaybolur; alan metindir.

kati form: 8 / 11 kayit dogrudan girilemiyor (%73)
esnek form: tam ad tek alan, adres cok satirli serbest metin + ulke,
            posta kodu ve bolge zorunlulugu ulkeye gore acilip kapaniyor.

ulke  posta kodu     bolge          bicim
  TR  zorunlu        zorunlu        5 hane
  IE  istege bagli   zorunlu        serbest
  AT  zorunlu        istege bagli   4 hane
  JP  zorunlu        zorunlu        3-4 hane
  GB  zorunlu        istege bagli   serbest
kural tablosu veridir; forma gomulmez, ulke secimiyle okunur.
```

#### Ad İki Parça Değildir

Çıktının ilk bölümü altı ad kaydının dördünün katı forma sığmadığını gösteriyor. Kayıp iki
biçimde oluşuyor: tek parçalı bir ad zorunlu ikinci alanı dolduramıyor, çok parçalı bir ad
ise fazla parçalarını tek alana sıkıştırıyor. Sıkıştırma kayıp gibi görünmez ama veriyi
bozar: hangi parçanın aile adı olduğu bilgisi kaybolur.

İkinci bölüm esnek kuralı gösteriyor: tek bir zorunlu **tam ad** alanı, isteğe bağlı bir hitap
adı. Bu kurulum altı kaydın altısını da olduğu gibi alır. Hitap adı ayrı sorulur, çünkü arayüzün
"Merhaba X" derken kullanacağı ad tam addan türetilemez — tek parçalı ad dışında türetme bir sıra
varsayımı gerektirir ve hangi kaydın tek parçalı olduğu önceden bilinemez.

Ad alanının ikinci kuralı sıralamadır: liste görünümünde adların hangi parçaya göre
sıralanacağı bir arayüz kararıdır ve veriye gömülemez. Sıralama, dile duyarlı karşılaştırma
ile yapılır — Tablolar dersindeki kuralın aynısı.

#### Adresin Zorunlu Alanları Ülkeye Bağlıdır

Üçüncü bölüm beş adres kaydının dördünün katı formdan düştüğünü gösteriyor. Düşme nedenleri
ayrı ayrı: bazı yerlerde posta kodu yoktur, bazı yerlerde bölge/eyalet alanı kullanılmaz,
bazı adresler iki satıra sığmaz.

Son bölümdeki tablo çözümü veriyor: zorunluluk kuralı forma gömülmez, **ülkeye göre okunan
bir tablodan** gelir. Kullanıcı ülkeyi seçtiğinde hangi alanların zorunlu olduğu, posta
kodunun hangi biçimi alacağı ve alan etiketlerinin ne olacağı o tablodan belirlenir. Tablo
veridir; yeni bir ülke eklendiğinde kod değişmez, satır eklenir.

Adres alanının üçüncü kuralı satır sayısıdır: çok satırlı serbest metin alanı, yapılandırılmış
alanlardan daha az bilgi kaybeder. Yapılandırma yalnız gerçekten gerekli olduğunda —
kargo hesabı, vergi kaydı — istenir ve o zaman da ülkeye göre değişir.

#### Kimlik Alanı Metindir

Dördüncü bölüm kimlik numarasının üç ayrı biçimde katı kuraldan düştüğünü gösteriyor:
farklı uzunluk, rakam dışı karakter ve baştaki sıfır. Sonuncusu en sinsi olanıdır: alan
sayı olarak saklandığında baştaki sıfırlar kaybolur ve değer sessizce bozulur.

Kural şudur: kimlik alanı bir sayı değil, bir **metindir**. Üzerinde aritmetik yapılmaz,
biçimi ülkeye göre değişir ve doğrulaması bir kalıp denetimidir. Doğrulama kalıbı da adres
kuralları gibi ülke tablosundan okunur; forma gömülen tek bir düzenli ifade, ilk yabancı
kayıtta kırılır.

#### Ölçünün Anlamı

Beşinci bölüm toplamı veriyor: on bir kaydın sekizi katı formu geçemiyor. Bu sayı örnek
kümesine bağlıdır ve bir istatistik değildir; işlevi, varsayımların **her birinin** bir
kayıt sınıfını dışarıda bıraktığını göstermektir.

Esnek form aynı kayıtların hepsini alır ve karşılığında iki şey ister: alanların serbest
bırakılması ve zorunlulukların veriden okunması. Maliyeti de vardır — serbest metin
alanları üzerinde otomatik çözümleme yapmak zorlaşır. Karar, hangi kaybın kabul
edilebilir olduğuna göre verilir: bir kütüphane üyeliğinde kullanıcıyı dışarıda bırakmak,
adres verisini yapılandıramamaktan daha ağır bir kayıptır.

#### Ölçülebilir Kısıtlar

- Ad alanı tek zorunlu serbest alandır; parçalara ayırma yalnız gerçekten gerekliyse ve
  isteğe bağlı olarak yapılır.
- Adres zorunlulukları ülke tablosundan okunur; tablo koda gömülmez.
- Kimlik ve posta kodu alanları metin olarak saklanır; baştaki sıfır korunur.
- Alan etiketleri ve doğrulama iletileri de ülkeye göre değişir; hata iletisi kuralı Hata
  Mesajı Yazımı dersindeki yapıyı izler.
- Zorunlu alan işareti ve hata bildirimi Erişilebilir Hata Sunumu dersindeki gibi alanla
  ilişkilendirilir.

#### Sık Yapılan Hata

En sık hata, ad alanını iki zorunlu parçaya bölmektir. İkincisi, posta kodunu her ülke için
zorunlu tutmaktır. Üçüncüsü, kimlik ve posta kodu alanlarını sayı olarak saklamaktır.
Dördüncüsü, bu kuralları ülke tablosu yerine koşullu kodla yazmaktır: her yeni ülke bir kod
değişikliği gerektirir ve kural kümesi zamanla okunamaz hâle gelir.

#### Özet

- Ad, adres ve kimlik alanlarının her biri bir varsayım taşır; modeldeki on bir kaydın
  sekizi katı formu geçemiyor.
- Tek zorunlu tam ad alanı bütün ad yapılarını alır; hitap adı ayrı ve isteğe bağlı sorulur,
  çünkü tam addan türetilemez.
- Adres zorunlulukları ve posta kodu biçimi ülkeye göre okunan bir tablodan gelir; tablo
  veridir, forma gömülmez.
- Kimlik ve posta kodu metindir: sayı olarak saklandığında baştaki sıfır kaybolur.
- Esnek formun bedeli otomatik çözümlemenin zorlaşmasıdır; karar hangi kaybın kabul
  edilebilir olduğuna göre verilir.

#### Kurs Kapanışı

Bu kurs, bileşenleri görünüşleriyle değil **belirtimleriyle** ele aldı. Temel Bileşenler
konusu düğmeyi, girdiyi, seçim denetimini ve açılır listeyi yerel öge önce kuralıyla
kurdu; her birinin rolünü, adını, durumunu ve klavye sözleşmesini yazdı. Kapsayıcı ve
Gezinme Bileşenleri konusu kartı, sekmeyi, akordeonu, menüyü, konum bildirimini, tabloyu ve
otomatik geçişli şeridi aynı disiplinle belirtti ve her birinde odak ile sekme sırasının
maliyetini saydı. Katman ve Geri Bildirim Bileşenleri konusu akışın üstüne çıkan bileşenleri
— kip pencere, araç ipucu, bildirim, yükleme göstergesi, iskelet, boş ve hata durumu, rozet
— odak devri ve duyuru politikasıyla ele aldı. Metin ve Yerelleştirme konusu ise arayüz
metnini bileşenin parçası hâline getirdi: rehber kuralları, hata iletisinin yapısı,
biçimlemenin yerel ayardan türetilmesi, yön duyarlı düzen ve bu dersteki alan varsayımları.

Kurs boyunca sabit kalan üç ölçüt vardı. Yerel öge varken ARIA yazılmaz. Her durum bir
kanalda değil en az iki kanalda bildirilir. Ve her karar ölçülebilir bir kısıta bağlanır —
hedef boyutu, kontrast oranı, süre sınırı ya da sekme durağı sayısı.

M15 müfredatı burada tamamlanıyor. Arayüz Temelleri görsel kararları algısal ilkelere ve
ölçülebilir eşiklere bağladı. Kullanıcı Deneyimi ve Davranış Tasarımı bu kararların
kullanıcıdan nasıl türetileceğini ve etik sınırının nerede olduğunu kurdu. Tasarım
Sistemleri kararları belirteçlere, kataloğa ve yönetişime çevirerek ölçeklenebilir hâle
getirdi. Erişilebilir Bileşen Kalıpları ise sistemin her parçasının klavye, odak ve ekran
okuyucu davranışını belirtim düzeyinde yazdı.

Bu müfredat, M14 Frontend müfredatındaki uygulama katmanıyla birlikte okunmak üzere
kuruldu: orada bileşenin nasıl **çalıştığı**, burada nasıl **olması gerektiği** anlatıldı.
İki müfredatın kesiştiği yer, bir arayüzün hem doğru çalışması hem de doğru davranmasıdır.

