İçeriğe geç
academia.sh

Ders 09 / 25

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.

İçindekiler

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.

<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

// 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.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat