Ders 14 / 25
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.
İçindekiler
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, 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, 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.
// 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.
Sekizinci 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.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.