# Arayüz bileşenleri

Devoplus One, sık kullanılan ekranlar için hazır bileşenler verir: liste (`<one-table>`), form (`<one-form>`),
süzgeç çubuğu (`<one-filter>`), kişi seçici (`<one-person-picker>`), dosya yükleme (`<one-file-upload>`), grafik
(`<one-chart>`), gösterge kartı (`<one-stat>`), aranabilir açılır liste (`<one-select>`), etiket girişi (`<one-tag-input>`), pencere (`<one-dialog>`),
ipucu (`<one-tooltip>`), eylem menüsü (`<one-menu>`), avatar (`<one-avatar>`), sekmeler (`<one-tabs>`) ve akordeon
(`<one-accordion>`); rozet, anahtar, radyo grubu, ilerleme çubuğu ve yükleniyor göstergesi de `/one-ui.css` sınıflarıdır.
Bileşenler Devoplus görsel dilindedir, Portal'ın tema ve dil değişimini kendiliğinden izler, Türkçe ve
İngilizce metin taşır, klavyeyle kullanılır ve platformun güvenlik kurallarına (satır içi betik yok, istek yalnız
uygulamanın kendi adresine) uyar.

**Kural:** ekranda bir liste, kayıt formu, süzgeç, kişi seçimi, dosya yükleme, grafik, açılır liste, etiket, pencere,
ipucu, eylem menüsü, sekme, akordeon, rozet ya da anahtar gerekiyorsa önce bu bileşenleri ve sınıfları kullanın; aynı işi
elle yazmayın. Elle yazılan tablo/form sıralama, sayfalama, doğrulama, erişilebilirlik ve
dil desteğini çoğu zaman eksik bırakır.

⚠️ Kullanıcıya teknik soru sormayın. "Hangi kolonlar görünsün", "hangi alanlar zorunlu", "kim seçilebilsin" gibi
soruları iş diliyle sorun (keşif görüşmesi), cevabı kolon ve alan tanımına siz çevirin.

## Hangi ekranda hangi bileşen

| Kullanıcının istediği | Bileşen | Not |
|---|---|---|
| "Taleplerin listesi", "kayıtları görmek", "tablo" | `<one-table src="/api/<varlık>">` | Sıralama, arama, sayfalama, satır seçimi hazır |
| "Listeyi Excel'e almak", "rapor indirmek" | `<one-table export="xlsx csv">` | Dosyayı sunucu üretir (aynı süzgeçle `?format=xlsx`) |
| "Duruma / tarihe göre süzmek" | `<one-filter for="<tablo kimliği>">` | Metin, seçim, tarih, tarih aralığı, evet/hayır |
| "Yeni talep formu", "kaydı düzenlemek" | `<one-dialog>` içinde `<one-form action="/api/<varlık>">` | Form liste ekranına çizilmez, pencerede açılır; kaydedince pencere kendiliğinden kapanır |
| "Onaylayan kişiyi seçmek", "sorumlu atamak" | `<one-person-picker>` (form alanında `type: 'person'`) | Sunucuda `peopleSearchHandler` + `orgschema.read` |
| "Belge / fotoğraf eklemek" | `<one-file-upload>` (form alanında `type: 'file'`) | Dosya deposu açık olmalı (`services.files`) |
| "Aylık özet grafiği", "dağılım" | `<one-chart type="bar|line|area|pie|doughnut">` | Portaldaki grafiklerle aynı kütüphane (`/one-charts.js`); sayıları sunucu hesaplar |
| "Kaç tane açık", "toplam tutar", "özet bilgi" | `<one-stat>` kartları (`<div class="one-stat-grid">` içinde) | Özet cümle olarak yazılmaz; her sayı bir kart |
| Satıra tıklayınca ayrıntı açılsın | `<one-table clickable>` + `rowclick` olayı | Görünüm değişince `one.setRoute(yol)` |
| "Listeden seçsin", "birden çok seçebilsin" | `<one-select>` (`multiple`) | Yazarak arar; sabit, küçük seçenek listesi için |
| "Etiket / anahtar kelime eklesin" | `<one-tag-input>` | Serbest metin etiketleri; isteğe bağlı öneri listesi |
| "Açılır pencerede düzenlesin" | `<one-dialog>` + `show()` | Portal da arka planı karartır |
| "Silsin" (her zaman onayla) | `one.remove(adres, { message })` | Önce onay sorar, kabulde `DELETE` gönderir; vazgeçilirse istek gitmez |
| "Pasife alsın" | `<one-form>` alanı `active` (ya da `confirmOff`) | Kayıt düzenlenirken açıktan kapalıya çevrilince form onay sorar |
| "Başka bir işlemden önce sorsun" | `one.confirm(...)` ya da `<one-dialog accept-label reject-label>` | Portal içinde Portal'ın onay penceresi açılır |
| "Kaydedildi yazsın" | `one.toast(...)` | Portal içinde Portal çizer |
| "Kısa açıklama / yardım" | `<one-tooltip text="...">` | Tetikleyici odaklanabilir olmalı |
| "Satır işlemleri (düzenle, sil gibi)" | `<one-menu compact>` + `select` olayı | Tehlikeli işlem `danger: true` |
| "Kişinin fotoğrafı / baş harfleri" | `<one-avatar name="..." src="...">` | |
| "Bölümler halinde göster" | `<one-tabs>` ya da `<one-accordion>` | Sekme uygulama menüsü DEĞİLDİR |
| "Durumu renkli göster" | `<span class="one-badge one-badge-success">` | Durum metni de yazılır |
| "Açık/kapalı ayarı" | `<input type="checkbox" role="switch" class="one-switch">` | |
| "İlerleme / yükleniyor" | `<progress class="one-progress">`, `<span class="one-spinner">` | |

## Sayfaya ekleme

Sayfa dosyaları uygulamanın kendi adresinden yükler; bileşen betiği köprüden **sonra** gelir:

```html
<link rel="stylesheet" href="/one-ui.css">
<one-filter for="talepler"></one-filter>
<one-table id="talepler" src="/api/requests" export="xlsx csv" clickable caption="Talepler"></one-table>
<script src="/one-bridge.js"></script>
<script src="/one-charts.js"></script>
<script src="/one-components.js"></script>
<script src="/shell.js"></script>
```

`/one-charts.js` portaldaki grafiklerle aynı grafik kütüphanesidir; köprüden sonra, bileşen betiğinden önce yüklenir.
Yüklenmezse `<one-chart>` yedek çizimle görünür ve yayın kapısı uyarır.

Sunucu tarafında `clientAssetResponse(pathname, request)` bu dosyaların hepsini döndürür; ek kod gerekmez. Bileşen kullanılıp
`/one-components.js` yüklenmezse yayın kapısı uyarır (bileşenler boş görünürdü).

Kolon, alan ve veri tanımları **JavaScript özelliğidir** (`/shell.js` içinde atanır), öznitelik değildir. Etiketler
`{ tr, en }` biçiminde iki dillidir. Bütün metinler düz metin olarak yazılır; HTML yorumlanmaz.

## Liste: `<one-table>`

```js
var table = document.getElementById('talepler')
table.columns = [
  { key: 'title', label: { tr: 'Başlık', en: 'Title' } },
  { key: 'requester', label: { tr: 'Talep eden', en: 'Requester' }, type: 'person' },
  { key: 'amount', label: { tr: 'Tutar', en: 'Amount' }, type: 'money' },
  { key: 'createdAt', label: { tr: 'Tarih', en: 'Date' }, type: 'date' },
  { key: 'status', label: { tr: 'Durum', en: 'Status' }, type: 'choice', options: [
    { value: 'open', label: { tr: 'Açık', en: 'Open' } },
    { value: 'closed', label: { tr: 'Kapalı', en: 'Closed' } },
  ] },
]
table.addEventListener('rowclick', function (e) { showDetail(e.detail.row) })
```

- **Uzak liste:** `src` verilince bileşen `GET <src>?limit=&cursor=&sort=&order=&q=&<alan>=` ister ve
  `{ items, nextCursor }` bekler (veri modeli ucunun sayfalı listesiyle aynı). Sonraki sayfa `nextCursor`'la gelir.
  Sayfa numaralı uç için `paging="page"` (`page=` gönderir, yanıttaki `total` sayfa sayısını verir).
- **Yerel veri:** küçük ve sabit listelerde `table.rows = [...]`; sıralama, arama ve sayfalama tarayıcıda yapılır.
- `selectable` (çoklu) ya da `selectable="single"` verilirse `selectionchange` olayı ve `table.selection` kullanılır.
- `export="xlsx csv"` verilirse "Excel'e aktar" / "CSV olarak indir" düğmeleri çıkar; uç aynı süzgeç ve sırayla
  `?format=xlsx|csv` isteğine dosya döndürmelidir (sayfalama yok). Dosya Portal'ın indirmesiyle kaydedilir.
- Yükleniyor, boş ("Kayıt yok."; `table.emptyText` ile değişir), arama sonucu boş ve hata ("Liste getirilemedi." +
  Yeniden dene) durumları ayrı gösterilir; sunucunun hata metni (`detail`) olduğu gibi görünür.

## Süzgeç: `<one-filter>`

```js
document.querySelector('one-filter').fields = [
  { key: 'q', label: { tr: 'Ara', en: 'Search' }, type: 'text' },
  { key: 'status', label: { tr: 'Durum', en: 'Status' }, type: 'choice', options: table.columns[4].options },
  { key: 'createdAt', label: { tr: 'Oluşturma', en: 'Created' }, type: 'dateRange' },
]
```

`for` özniteliğindeki tabloya kendiliğinden bağlanır; ayrıca `change` olayı `{ values, params }` yayar. Parametreler:
metin için `q`, seçim ve evet/hayır için alan adı, tarih aralığı için `<alan>From` ve `<alan>To` (uç farklı ad bekliyorsa alanda
`fromParam` / `toParam`). Başlangıç tarihi bitişten sonraysa süzgeç uygulanmaz ve kullanıcıya söylenir.

## Form: `<one-form>`

```js
var form = document.querySelector('one-form')
form.fields = [
  { key: 'title', label: { tr: 'Başlık', en: 'Title' }, type: 'text', required: true, maxLength: 120 },
  { key: 'description', label: { tr: 'Açıklama', en: 'Description' }, type: 'longText' },
  { key: 'amount', label: { tr: 'Tutar', en: 'Amount' }, type: 'money', min: 0 },
  { key: 'dueDate', label: { tr: 'Son tarih', en: 'Due date' }, type: 'date' },
  { key: 'approver', label: { tr: 'Onaylayan', en: 'Approver' }, type: 'person', required: true },
  { key: 'attachment', label: { tr: 'Belge', en: 'Document' }, type: 'file', accept: '.pdf,image/*' },
]
form.value = existingRecord          // düzenleme ekranında; yeni kayıtta atamayın
form.addEventListener('saved', function () { table.reload() })
```

- Alan türleri: `text`, `longText`, `number`, `money`, `date`, `datetime`, `boolean`, `choice`, `person`,
  `file` (veri modelindeki türlerle aynı). Kurallar: `required`, `minLength`, `maxLength`, `min`, `max`, `pattern`,
  `options`, `multiple`, `help`, `placeholder`, `readOnly`.
- `action` verilirse form değeri JSON olarak gönderilir (`method` `POST` / `PUT` / `PATCH`); sunucu doğrulama
  hatalarını RFC 9457 `errors` alanında döndürürse her ileti kendi alanının altında görünür. `action` yoksa formu siz
  gönderirsiniz: `save` olayını dinleyin (`e.detail.value`). Kaydı kendiniz gönderdiğinizde (`action` yok ya da `save`
  olayında `preventDefault()` çağırdınız) kayıt başarılı olunca gönderdiğiniz değeri forma geri atayın
  (`form.value = e.detail.value`); pencereyi kapatıyorsanız bunu `close` çağrısından önce yapın. Atanmazsa form
  kaydedilmemiş sayılır ve Portal, kullanıcı ayrılırken uyarmayı sürdürür. Kayıt başarısızsa atamayın: kullanıcının
  düzenlemesi korunur.
- Kaydedilmemiş değişiklik Portal'a bildirilir (kullanıcı ayrılırken uyarılır); Vazgeç değişiklik varsa onay sorar.
- Vekalet / Yerine Görüntüle oturumunda Kaydet kapalıdır ve nedeni yazılır. Uygulama bu oturumlarda yazmayı bilinçli
  olarak açtıysa (manifest `actingSessions: "write"`) forma `acting-write` özniteliğini ekleyin.
- Sunucu doğrulaması ZORUNLUDUR: tarayıcıdaki doğrulama kullanıcıya kolaylıktır, güvenlik sınırı değildir.

Ekleme ve düzenleme formu liste ekranına çizilmez; `<one-dialog>` penceresinin içine konur (aşağıda "Ekleme ve
düzenleme penceresi"). Liste (`<one-table>`) ile aynı dosyada pencere dışında duran bir `<one-form>` için yayın hattı
uyarı verir (yayını durdurmaz).

## Kişi seçici: `<one-person-picker>`

Bileşen uygulamanın kendi ucuna gider; ucu SDK hazırlar. Manifest `scopes` içinde `orgschema.read` olmalıdır.

```ts
import { PEOPLE_SEARCH_PATH, peopleSearchHandler } from '@devoplus/one-sdk'
if (pathname === PEOPLE_SEARCH_PATH && method === 'GET') return peopleSearchHandler(request, env, ctx)
```

Sonuç yalnız ad, unvan ve departman taşır (e-posta ve telefon dönmez). Seçilen kişinin `id`'si organizasyon şeması
kimliğidir; bildirim gönderirken `toOrgUserIds` alanına yazın. Kullanıcının kendi kimliğiyle (`ctx.user.sub`)
karıştırmayın.

## Dosya yükleme: `<one-file-upload>`

Varsayılan olarak `POST /api/files` ucuna yükler (yeni uygulama iskeletindeki uç). `accept` ve `max-size` (bayt)
yüklemeden önce denetlenir; sunucu kendi sınırını yine uygular. Değer `{ key, name, size, contentType }`'tır.

## Grafik: `<one-chart>`

```js
var chart = document.querySelector('one-chart')
chart.label = { tr: 'Aylık talepler', en: 'Monthly requests' }
chart.data = { labels: ['Ocak', 'Şubat'], series: [{ name: { tr: 'Açılan', en: 'Opened' }, values: [12, 30] }] }
```

Toplam, ortalama ve yüzde gibi değerleri sunucuda hesaplayın; grafik yalnız çizer. Grafik ekran okuyucuya veri tablosu
olarak da okunur.

- Türler: `bar` (çubuk), `line` (çizgi), `area` (dolgulu çizgi), `pie` (pasta), `doughnut` (halka). Pasta ve halka yalnız
  **ilk seriyi** çizer (gizli veri tablosu da yalnız onu gösterir); birden çok seri için `bar` ya da `line` kullanın. `stacked` çubukları
  üst üste, `horizontal` yatay çizer; `size="sm"` (220 px) ya da `size="lg"` (420 px) yüksekliği değiştirir (varsayılan 320
  px); `unit="adet"` değerlerin yanına yazılır.
- Grafik portaldaki grafiklerle aynı kütüphaneyle (`/one-charts.js`) çizilir; renkler ve yazı tipi Devoplus görsel
  dilinden gelir, tema değişince grafik yeniden çizilir.

## Gösterge kartı: `<one-stat>`

Sayı ve durum özetleri (kaç kayıt açık, toplam tutar, süresi geçenler) **cümle olarak yazılmaz**; her sayı bir gösterge
kartıdır. Kartlar `one-stat-grid` sınıflı bir kutuda dizilir ve dar ekranda alt alta geçer.

```html
<div class="one-stat-grid">
  <one-stat id="aktif" label="Aktif poliçe" unit="adet"></one-stat>
  <one-stat id="suresiGecen" label="Süresi geçen" tone="danger"></one-stat>
  <one-stat id="prim" label="Toplam prim" format="money" delta-label="geçen aya göre" delta-polarity="up-good"></one-stat>
</div>
```

```js
document.getElementById('aktif').value = ozet.aktif
document.getElementById('suresiGecen').value = ozet.suresiGecen
var prim = document.getElementById('prim')
prim.value = ozet.toplamPrim
prim.setAttribute('delta', String(ozet.primDegisimYuzde))   // 12.5 gösterilir: "+%12,5"
```

- `format`: `number` (varsayılan), `money` (`currency`, varsayılan TRY), `percent`, `text`; `decimals` ondalık sayısı.
- `delta` değişimi oklu simgeyle gösterir (`delta-format="number"` ile yüzde yerine sayı). Değişim varsayılan olarak
  nötr renktedir; artış iyiyse `delta-polarity="up-good"`, azalış iyiyse `down-good` yazın (yeşil/kırmızı).
- `tone` kartın vurgusunu verir: `primary`, `success`, `warn`, `danger`, `info` (yalnız anlamı olan kartta kullanın).
- `loading` veri gelene kadar yükleniyor göstergesi çizer; değer yoksa kart "-" gösterir.
- `clickable` kartı düğmeye çevirir ve `select` olayı yayar (ör. ilgili listeye süzgeçle gitmek için).
- Etiketler iki dilliyse `label`, `caption` ve `deltaLabel` özelliklerine `{ tr, en }` atayın.

## Açılır liste: `<one-select>`

```js
var city = document.querySelector('one-select')
city.options = [
  { value: 'ist', label: { tr: 'İstanbul', en: 'Istanbul' } },
  { value: 'ank', label: 'Ankara', description: 'Başkent' },
  { value: 'izm', label: 'İzmir', disabled: true },
]
city.value = 'ank'            // multiple ile dizi: ['ist', 'ank']
city.addEventListener('change', function (e) { console.log(e.detail.value) })
```

Yazınca Türkçe harf duyarsız süzer, ok tuşları ve Enter ile seçilir. Kişi seçmek için `<one-person-picker>`, uzak ve
büyük listeler için `<one-table>` + `<one-filter>` kullanın; `<one-select>` en çok 200 seçeneği gösterir.

## Etiket girişi: `<one-tag-input>`

```js
var tags = document.querySelector('one-tag-input')
tags.suggestions = ['Bütçe', 'Bordro', 'İzin']   // isteğe bağlı
tags.value = ['Bütçe']
```

Enter ya da virgül etiket ekler; aynı etiket ikinci kez eklenmez; `max` (adet) ve `max-length` (karakter) sınırlarının
nedeni kullanıcıya yazılır.

## Pencere: `<one-dialog>`

```html
<one-dialog id="duzenle" label="Talebi düzenle">
  <one-form action="/api/requests/42" method="PUT"></one-form>
  <button class="one-btn" type="button" data-one-close data-one-dialog-footer>Kapat</button>
</one-dialog>
```

```js
var result = await document.getElementById('duzenle').show()   // '' = vazgeçildi
```

- Pencere iframe'in görünen kısmında açılır; açıkken Portal da kendi ekranını karartır. Kullanıcı Portal'ın karartılmış
  alanına tıklarsa ya da Escape'e basarsa pencere "vazgeçildi" diye kapanır (`cancel` olayı, `reason: "portal"`).
  Penceredeki `<one-form>`'da kaydedilmemiş değişiklik varsa soruyu bileşen kendisi sorar (aşağıdaki madde) ve
  `cancel` ancak kullanıcı değişiklikleri atmayı kabul ettikten sonra gelir: `cancel` olayında ikinci bir soru sormayın,
  `preventDefault()` çağırmayın (kabul edilen kapanmayı geri çevirir). `preventDefault()` yalnız `<one-form>` dışındaki
  bir durum için (ör. süren bir yükleme) pencereyi açık tutar.
- **Ekleme ve düzenleme penceresi:** `<one-form>` pencerenin içine konur. Form kaydedilince pencere `'saved'` ile,
  formda Vazgeç'e basılınca `''` ile kendiliğinden kapanır (`keep-open` özniteliği bunu kapatır). Kayıt sürerken formda
  yeni bir değişiklik yapılmışsa pencere kapanmaz: o değişiklik gönderilmemiştir ve görünür kalır. Formda kaydedilmemiş
  değişiklik varken pencere kapatılmak istenirse (Escape, kapat düğmesi, karartmaya tıklama) önce "değişiklikler
  kaybolacak" onayı sorulur; kabul edilirse form son kaydedilen haline döner. Altbilgideki değersiz `data-one-close`
  düğmesi ("Kapat") ve değersiz düğmeyle gönderilen `<form method="dialog">` de vazgeçme sayılır ve aynı soruyu sorar;
  değerli olan (`data-one-close="tamam"`) sonuç olarak doğrudan kapatır.

  ```js
  var penc = document.getElementById('talepPenceresi'), form = document.getElementById('talepFormu')
  document.getElementById('yeniTalep').addEventListener('click', function () {
    form.setAttribute('action', '/api/requests'); form.setAttribute('method', 'POST')
    form.value = {}
    penc.label = { tr: 'Yeni talep', en: 'New request' }
    penc.show()
  })
  table.addEventListener('rowclick', function (e) {
    form.setAttribute('action', '/api/requests/' + encodeURIComponent(e.detail.row.id)); form.setAttribute('method', 'PATCH')
    form.value = e.detail.row
    penc.label = { tr: 'Talebi düzenle', en: 'Edit request' }
    penc.show()
  })
  form.addEventListener('saved', function () { table.reload() })   // pencere kendiliğinden kapanır
  ```
- **Onay penceresi:** `accept-label` ve `reject-label` verin (`severity="danger"` silme gibi işlemler için); `show()`
  `'accept'` ya da `'reject'` döner. Portal içindeyken bu pencere Portal'ın kendi onay penceresiyle sorulur ve içerik
  DÜZ METİN olarak gider; onay iletisini kısa ve düz yazın. Biçimli içerikli bir soru gerekiyorsa onay öznitelikleri
  olmadan genel pencere kullanın, düğmeleri `data-one-close="evet"` gibi kendiniz koyun.
- Basit evet/hayır için `await one.confirm({ message, severity: 'danger', acceptLabel: 'Sil' })` yeterlidir.

## Silme ve pasife alma (zorunlu kural)

One uygulamalarında **silme her zaman yumuşaktır** ve **silme ile pasife alma her zaman onay penceresi ister**.
`one-ci` bu iki kuralı yayında tarar ve uymayan kalıbı **uyarı** olarak gösterir; yayın sürer (D-134). Kural yine
zorunludur: tarama bir sözdizimi taramasıdır, her biçimi yakalamaz ve bazen yanlış uyarı verir, bu yüzden yayını
durdurmaz. Uyarıyı yok saymayın; yanlış uyarıysa aşağıdaki gerekçeli yorumla kapatın.

- **Silme:** kaydı yok etmeyin, `deleted_at` alanını doldurun. Hazır veri katmanı (`createCrudHandler`) bunu kendisi
  yapar. Kodda ya da `migrations/` altındaki bir SQL dosyasında `DELETE FROM` görülürse (büyük ya da küçük harf; iki
  sözcük arasında SQL yorumu olsa da, dize parçalarına bölünse de, tablo adı kodla kurulsa da:
  `` `DELETE FROM ${table}` ``) uyarı verilir.
- **Silme isteği:** `one.remove('/api/kayitlar/' + id, { message: 'Kayıt silinecek. Devam etmek istiyor musunuz?' })`
  kullanın. Önce Portal'ın onay penceresini açar, kullanıcı kabul ederse `DELETE` gönderir ve yanıtı döner; vazgeçerse
  `null` döner ve istek gitmez. Onay sorulmadan gönderilen `DELETE` isteği uyarı verir. Kendi onayınızı soruyorsanız
  isteği onayın SONUCUNA bağlayın: ya ret dalı işlevden çıksın (`if (!(await one.confirm({ ... }))) return`, istek
  aynı blokta sonra gelir) ya da istek kabul dalının İÇİNDE olsun (`one.confirm({ ... }).then(function (ok) { if (ok) ... })`,
  `if (await one.confirm({ ... })) { ... }`). Sonucu beklenmeyen, yok sayılan ya da isteği yönetmeyen onay sayılmaz:
  `if (await one.confirm(...)) audit(); fetch(...)` geçmez, çünkü istek kabul dalının dışındadır; `ok && audit(), fetch(...)`
  de geçmez, çünkü virgülden sonraki istek onaydan bağımsız çalışır. Ret dalı istekten ÖNCE bitmelidir:
  `if (!ok) return fetch(...)` isteği tam da vazgeçilince gönderir ve geçmez. Onay sonucu yerel bir değişkende tutulur
  (`state.ok = await one.confirm(...)` aynı adlı yerel değişkeni onaylamaz) ve koşulsuz sorulur (`if (x) ok = await
  one.confirm(...)` sayılmaz) ve sonradan değiştirilmez (geri çağrıdaki istek için istekten SONRA gelen `ok = true` da
  sayılır); `.then(ok => ...)` parametresi yalnız kendi geri çağrısının içinde geçerlidir. Ret dalı
  çıkmıyorsa istek `else` bloğunda olabilir (`if (!ok) { ... } else { ... }`). `one.remove` onayı
  kendisi sorar, başka bir isteği onaylamış sayılmaz. Sonucu bir değişkende tutuyorsanız o değişkene sonradan başka
  bir değer atamayın (`ok = true` sonrasındaki koşul onay sayılmaz), onayın sonucuna bir işlem eklemeyin
  (`await one.confirm(...) || true`) ve aynı adı iç bir işlevin parametresi olarak kullanmayın
  (`items.forEach(ok => ...)` içindeki `ok` onay sonucu değildir); `.then(...)` yalnız onay çağrısına bitişikse
  (`one.confirm(...).then(...)`) onayındır; koşul yalnız kendisinden ÖNCE sorulan onaya bağlanır. Karşılaştırmalı yazım (`if (ok === false) return`,
  `if (ok !== true) return`, `if (ok === true) { ... }`) kabul edilir. HTTP istemcisinin silme yöntemi de (`axios.delete(...)`,
  `ky.delete(...)`, `api.delete(...)`, `axios?.delete(...)`, `client.delete?.(...)`, `axios['delete'](...)`, `this.api.delete(...)` gibi) ve
  yöntemi bir değişkenden alan istek de (`const method = 'DELETE'` ya da `const method: string = 'DELETE'` sonrası
  `fetch(url, { method })`; bir blok içinde `method = 'DELETE'` yazılması da sayılır) ve yöntemi parçalardan birleşen istek
  de (`method: 'DE' + 'LETE'`) silme isteğidir. Beklenen onay parantez içinde olabilir (`if (await (one.confirm(...)))`).
  Sunucudaki yol kaydı (`router('DELETE', ...)`, `{ method: 'DELETE', handler }`) istek sayılmaz; `handler` anahtarı
  nesnenin kendi düzeyinde olmalıdır (gövdedeki iç içe bir nesne sayılmaz) ve `fetch(...)`, `axios(...)`, `ky(...)` gibi
  HTTP istemcilerine verilen seçenek nesnesi her zaman istektir. Şablon dizgisinde yazılan sayfa betiğinin TAMAMI yorum olan satırı (`// ...`, `/* ... */`) yorum sayılır;
  `${...}` içindeki ifade yorum değildir.
- **Pasife alma:** `<one-form>` düzenlenen kayıtta (`method` `PATCH` ya da `PUT`) `active` ya da `is_active` adlı açık/kapalı
  alanı açıktan kapalıya çevrilince kaydetmeden önce onay sorar. Başka adlı bir alan için alan tanımına
  `confirmOff: { tr: '...', en: '...' }` yazın. Pasife almayı kendi düğmenizle yapıyorsanız önce `one.confirm(...)` sorun.
- **İstisna:** kalıcı silme ya da onaysız istek gerçekten gerekiyorsa (ya da uyarı yanlışsa) aynı ya da bir önceki satıra
  gerekçeli YORUM yazın; yorum uyarıyı kapatır:
  `// one-allow-hard-delete: <en az 10 karakter gerekçe>` ya da `// one-allow-unconfirmed-delete: <gerekçe>`; migration
  dosyasında `-- one-allow-hard-delete: <gerekçe>`. İşaret yalnız yorumda geçerlidir ve gerekçe yorumun kendi metninden
  sayılır (bir dizenin değerindeki işaret ya da yorumdan sonra gelen kod sayılmaz); karakterler harf harf sayılır,
  yalnız boşluk ya da beş emoji on karakter değildir. Sayfa betiği bir şablon dizgisinin
  içinde yazılıysa, TAMAMI yorum olan satır (`// one-allow-...`) da sayılır. Uygulanmış bir migration'a yalnız yorum
  eklemek o migration'ı değişmiş saymaz. Veri modelinde (`one.data.json` ya da `createCrudHandler`'a koddan verilen model)
  kalıcı silme `"softDelete": false` ile birlikte `"hardDeleteReason": "<gerekçe>"` ister; gerekçesiz model kayıt uçlarında
  yapılandırma hatası verir ve `one data generate` durur. Bu kural uyarı DEĞİLDİR: model bir beyandır, tarama değildir.

## Simgeler

Simge olarak emoji ya da ok/üçgen karakteri (çift yönlü ok, `▲`, `▼`, `×`, `✓`) kullanmayın: bazı platformlarda renkli emoji olarak
çizilir ve Portal'la uyumsuz görünür. Hazır bileşenler kendi simgelerini SVG olarak çizer; kendi simgeniz gerekiyorsa
satır içi `<svg>` (çizgi rengi `currentColor`) kullanın. `one-ci` tek başına simge olarak kullanılan bu karakterleri uyarır;
CSS'te `content: '×'` gibi çizilen karakter de aynı uyarıyı alır.

## Bildirim

Ayrı bileşen yoktur: `one.toast({ severity: 'success', summary: 'Kaydedildi' })`. Portal içinde Portal çizer; yerel
geliştirmede sayfanın köşesinde görünür.

## İpucu, eylem menüsü, avatar

```html
<one-tooltip text="En çok 20 karakter" text-en="At most 20 characters"><button class="one-btn one-btn-text" type="button">?</button></one-tooltip>
<one-menu id="satir" compact label="Satır işlemleri"></one-menu>
<one-avatar name="Şule Yıldız" src="/api/photo/42"></one-avatar>
```

```js
var menu = document.getElementById('satir')
menu.items = [{ id: 'edit', label: { tr: 'Düzenle', en: 'Edit' } }, { separator: true }, { id: 'delete', label: { tr: 'Sil', en: 'Delete' }, danger: true }]
menu.addEventListener('select', function (e) { if (e.detail.id === 'delete') removeRow() })
```

İpucunun tetikleyicisi odaklanabilir olmalıdır (düğme, bağlantı); önemli bilgiyi ipucuna koymayın.

## Sekmeler ve akordeon

```html
<one-tabs label="Talep ayrıntısı">
  <section data-label="Genel" data-label-en="General">...</section>
  <section data-label="Geçmiş" data-label-en="History" data-tab="history">...</section>
</one-tabs>
<one-accordion>
  <div data-label="Kimler görebilir?" data-open>...</div>
  <div data-label="Veriler nerede?">...</div>
</one-accordion>
```

Sekmeler içerik içi gruplama içindir; uygulamanın sayfaları için Portal menüsünü (`navigation`) kullanın.

## Ayrıntı

Bütün özellikler, öznitelikler, olaylar ve tipler: [SDK başvurusu](/docs/sdk.md) içinde "Hazır arayüz bileşenleri".
Görsel dil ve köprü: aynı belgede "Portal köprüsü ve görsel dil".
