@moontra/moonui 6.24.0 → 6.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/cdn/moonui.css +2 -2
  2. package/dist/cdn/moonui.esm.js +12 -12
  3. package/dist/cdn/moonui.global.js +12 -12
  4. package/dist/index.d.mts +24 -6
  5. package/dist/index.d.ts +24 -6
  6. package/dist/index.js +436 -93
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +437 -95
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +1 -1
  11. package/src/__tests__/issue-544-kontrast.test.tsx +153 -145
  12. package/src/__tests__/issue-645-overlay-tab-focus.test.tsx +262 -0
  13. package/src/__tests__/issue-646-dark-scope-override.test.tsx +297 -0
  14. package/src/__tests__/issue-661-a11y.test.tsx +113 -0
  15. package/src/__tests__/issue-661-token-kontrast.test.tsx +82 -0
  16. package/src/components/ui/__tests__/accent-foreground-contrast.test.ts +197 -0
  17. package/src/components/ui/__tests__/accordion.test.tsx +6 -4
  18. package/src/components/ui/__tests__/alert.test.tsx +4 -1
  19. package/src/components/ui/__tests__/button.test.tsx +6 -1
  20. package/src/components/ui/__tests__/dark-token-contrast.test.ts +18 -18
  21. package/src/components/ui/__tests__/heading-level-policy.test.tsx +4 -4
  22. package/src/components/ui/__tests__/issue-643-card-token-parite.test.tsx +113 -0
  23. package/src/components/ui/__tests__/issue-649-button-aschild.test.tsx +199 -0
  24. package/src/components/ui/__tests__/issue-664-separator-dekoratif-rol.test.tsx +41 -0
  25. package/src/components/ui/__tests__/preset-color-shadowing.test.ts +10 -10
  26. package/src/components/ui/__tests__/primary-subtle-token.test.ts +7 -7
  27. package/src/components/ui/__tests__/progress-secondary-contrast.test.ts +38 -38
  28. package/src/components/ui/__tests__/progress-warning-error-contrast.test.ts +64 -64
  29. package/src/components/ui/__tests__/quality-group-de.test.tsx +2 -2
  30. package/src/components/ui/__tests__/responsive-color-invariance.test.ts +20 -20
  31. package/src/components/ui/__tests__/select.test.tsx +6 -1
  32. package/src/components/ui/__tests__/semantic-token-usage.test.ts +23 -23
  33. package/src/components/ui/__tests__/size-scale-convention.test.ts +10 -10
  34. package/src/components/ui/__tests__/slider-accent-contrast.test.ts +142 -136
  35. package/src/components/ui/__tests__/slider-secondary-contrast.test.ts +60 -60
  36. package/src/components/ui/__tests__/slider-warning-error-contrast.test.ts +100 -100
  37. package/src/components/ui/__tests__/switch-variant-contrast.test.tsx +103 -103
  38. package/src/components/ui/__tests__/tooltip.test.tsx +122 -0
  39. package/src/components/ui/accordion.tsx +16 -3
  40. package/src/components/ui/alert.tsx +1 -1
  41. package/src/components/ui/badge.tsx +12 -1
  42. package/src/components/ui/button.tsx +95 -24
  43. package/src/components/ui/card.tsx +59 -5
  44. package/src/components/ui/dropdown-menu.tsx +99 -5
  45. package/src/components/ui/file-upload.tsx +4 -0
  46. package/src/components/ui/index.ts +4 -1
  47. package/src/components/ui/popover-pro.tsx +41 -0
  48. package/src/components/ui/popover.tsx +162 -7
  49. package/src/components/ui/rating.tsx +24 -2
  50. package/src/components/ui/select.tsx +4 -4
  51. package/src/components/ui/simple-editor.tsx +3 -18
  52. package/src/components/ui/tabs.tsx +16 -1
  53. package/src/components/ui/textarea.tsx +6 -1
  54. package/src/components/ui/toast.tsx +17 -2
  55. package/src/components/ui/tooltip.tsx +154 -2
  56. package/src/styles/tokens.css +34 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moontra/moonui",
3
- "version": "6.24.0",
3
+ "version": "6.26.0",
4
4
  "description": "Premium React component library with modern design and customization",
5
5
  "author": "MoonUI",
6
6
  "license": "MIT",
@@ -49,21 +49,21 @@ import * as path from 'path'
49
49
  import { Badge } from '../components/ui/badge'
50
50
  import { Button } from '../components/ui/button'
51
51
 
52
- const PAKET = path.resolve(__dirname, '..')
52
+ const PKG = path.resolve(__dirname, '..')
53
53
  const DEPO = path.resolve(__dirname, '../../../..')
54
- const tokens = fs.readFileSync(path.join(PAKET, 'styles/tokens.css'), 'utf8')
55
- const badgeSrc = fs.readFileSync(path.join(PAKET, 'components/ui/badge.tsx'), 'utf8')
56
- const buttonSrc = fs.readFileSync(path.join(PAKET, 'components/ui/button.tsx'), 'utf8')
54
+ const tokens = fs.readFileSync(path.join(PKG, 'styles/tokens.css'), 'utf8')
55
+ const badgeSrc = fs.readFileSync(path.join(PKG, 'components/ui/badge.tsx'), 'utf8')
56
+ const buttonSrc = fs.readFileSync(path.join(PKG, 'components/ui/button.tsx'), 'utf8')
57
57
 
58
58
  type RGB = [number, number, number]
59
59
  type Blok = 'light' | 'dark'
60
60
 
61
61
  // ─── Renk aritmetiği (switch-variant-contrast.test.tsx ile birebir aynı yöntem) ───
62
62
 
63
- function tokenOku(ad: string, blok: Blok): string | null {
64
- const kaynak =
63
+ function tokenOku(name: string, blok: Blok): string | null {
64
+ const source =
65
65
  blok === 'dark' ? tokens.slice(tokens.indexOf('.dark')) : tokens.slice(0, tokens.indexOf('.dark'))
66
- return kaynak.match(new RegExp(`--${ad}:\\s*([^;]+);`))?.[1].trim() ?? null
66
+ return source.match(new RegExp(`--${name}:\\s*([^;]+);`))?.[1].trim() ?? null
67
67
  }
68
68
 
69
69
  function hslToRgb(h: number, s: number, l: number): RGB {
@@ -88,12 +88,12 @@ const rgbTokenRgb = (v: string): RGB => {
88
88
  return [p[0], p[1], p[2]]
89
89
  }
90
90
  /** Semantik token'ın ilgili temadaki değeri — `.dark` ezmesi YOKSA light'a düşer */
91
- const semantikRgb = (ad: string, blok: Blok): RGB =>
92
- hslTokenRgb(tokenOku(ad, blok) ?? tokenOku(ad, 'light')!)
91
+ const semanticRgb = (name: string, blok: Blok): RGB =>
92
+ hslTokenRgb(tokenOku(name, blok) ?? tokenOku(name, 'light')!)
93
93
  /** Skala kademeleri tema-BAĞIMSIZ: `.dark` bloğu onları ezmiyor (preset yorumu) */
94
- const skalaRgb = (ad: string): RGB => rgbTokenRgb(tokenOku(ad, 'light')!)
94
+ const scaleRgb = (name: string): RGB => rgbTokenRgb(tokenOku(name, 'light')!)
95
95
 
96
- function bagilParlaklik([r, g, b]: RGB): number {
96
+ function relativeLuminance([r, g, b]: RGB): number {
97
97
  const c = (v: number) => {
98
98
  const x = v / 255
99
99
  return x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4)
@@ -101,8 +101,8 @@ function bagilParlaklik([r, g, b]: RGB): number {
101
101
  return 0.2126 * c(r) + 0.7152 * c(g) + 0.0722 * c(b)
102
102
  }
103
103
  function kontrast(a: RGB, b: RGB): number {
104
- const la = bagilParlaklik(a)
105
- const lb = bagilParlaklik(b)
104
+ const la = relativeLuminance(a)
105
+ const lb = relativeLuminance(b)
106
106
  const [hi, lo] = la > lb ? [la, lb] : [lb, la]
107
107
  return (hi + 0.05) / (lo + 0.05)
108
108
  }
@@ -114,94 +114,94 @@ const hex = ([r, g, b]: RGB): string =>
114
114
  '#' + [r, g, b].map((x) => Math.round(x).toString(16).padStart(2, '0')).join('')
115
115
 
116
116
  // ─── Zeminler — HER ÖLÇÜMDE ADIYLA ANILIR ───────────────────────────────────────
117
- const BEYAZ: RGB = [255, 255, 255]
117
+ const WHITE: RGB = [255, 255, 255]
118
118
  /** Docs kabuğunda ölçülen GERÇEK koyu kart (#284/#368/#448/#454 konvansiyonu) */
119
- const KART_GERCEK_DARK: RGB = [17, 24, 39]
119
+ const CARD_ACTUAL_DARK: RGB = [17, 24, 39]
120
120
 
121
- const AA_METIN = 4.5
122
- const AA_METIN_DISI = 3.0
121
+ const AA_TEXT = 4.5
122
+ const AA_NON_TEXT = 3.0
123
123
 
124
124
  // ─── Varyant sınıf dizgesini KAYNAKTAN çöz (renkler VARSAYILMAZ) ────────────────
125
125
 
126
126
  /** cva `variant` bloğundaki dizi biçimli varyant girdisini birleşik sınıf dizgesi olarak ver */
127
- function varyantSinifi(kaynak: string, varyant: string): string {
128
- const m = kaynak.match(new RegExp(`\\n\\s*${varyant}:\\s*\\[([\\s\\S]*?)\\n\\s*\\]`))
129
- if (!m) throw new Error(`${varyant} varyantı bulunamadı`)
127
+ function variantClass(source: string, variantVal: string): string {
128
+ const m = source.match(new RegExp(`\\n\\s*${variantVal}:\\s*\\[([\\s\\S]*?)\\n\\s*\\]`))
129
+ if (!m) throw new Error(`${variantVal} varyantı bulunamadı`)
130
130
  return (m[1].match(/"([^"]*)"/g) ?? []).map((s) => s.slice(1, -1)).join(' ')
131
131
  }
132
132
 
133
133
  /** `bg-error-600` / `bg-error` / `bg-error/90` / `text-white` → RGB (alfa varsa zemine bindirir) */
134
- function sinifRengi(sinif: string, blok: Blok, zemin: RGB): RGB {
134
+ function classColor(cls: string, blok: Blok, background: RGB): RGB {
135
135
  // ⚠️ `+` ŞART: `dark:hover:bg-error-700` gibi ÇOK önekli sınıflarda tek tur
136
136
  // soyma yetmez ve sınıf "çözülemez" diye PATLAR — testi kontrast ölçmeden
137
137
  // kırmızı gösterir (yanlış sebeple kırmızı = geçersiz ölçüm).
138
- const g = sinif.replace(/^(?:(?:dark|hover|active|focus-visible|group-hover):)+/, '')
138
+ const g = cls.replace(/^(?:(?:dark|hover|active|focus-visible|group-hover):)+/, '')
139
139
  const m = g.match(/^(?:bg|text|border|ring)-([a-z-]+?)(?:-(\d{2,3}))?(?:\/(\d{1,3}))?$/)
140
- if (!m) throw new Error(`çözülemeyen sınıf: ${sinif}`)
141
- const [, ad, kademe, alfa] = m
142
- if (ad === 'white') return alfa ? komposit(BEYAZ, zemin, Number(alfa) / 100) : BEYAZ
143
- const taban = kademe ? skalaRgb(`${ad}-${kademe}`) : semantikRgb(ad, blok)
144
- return alfa ? komposit(taban, zemin, Number(alfa) / 100) : taban
140
+ if (!m) throw new Error(`çözülemeyen sınıf: ${cls}`)
141
+ const [, name, kademe, alfa] = m
142
+ if (name === 'white') return alfa ? komposit(WHITE, background, Number(alfa) / 100) : WHITE
143
+ const base = kademe ? scaleRgb(`${name}-${kademe}`) : semanticRgb(name, blok)
144
+ return alfa ? komposit(base, background, Number(alfa) / 100) : base
145
145
  }
146
146
 
147
147
  /** Bir varyantın ilgili temadaki ETKİN dolgusu — `dark:` önekli sınıf dark'ta kazanır */
148
- function dolgu(kaynak: string, varyant: string, blok: Blok, zemin: RGB, onek = ''): RGB {
149
- const adaylar = varyantSinifi(kaynak, varyant)
148
+ function fill(source: string, variantVal: string, blok: Blok, background: RGB, onek = ''): RGB {
149
+ const adaylar = variantClass(source, variantVal)
150
150
  .split(/\s+/)
151
151
  .filter((s) => new RegExp(`^(?:dark:)?${onek}bg-`).test(s))
152
152
  const dark = adaylar.find((s) => s.startsWith('dark:'))
153
153
  const light = adaylar.find((s) => !s.startsWith('dark:'))
154
154
  const secilen = blok === 'dark' ? (dark ?? light) : light
155
- if (!secilen) throw new Error(`${varyant}/${blok}${onek ? ` (${onek})` : ''}: dolgu sınıfı yok`)
156
- return sinifRengi(secilen, blok, zemin)
155
+ if (!secilen) throw new Error(`${variantVal}/${blok}${onek ? ` (${onek})` : ''}: dolgu sınıfı yok`)
156
+ return classColor(secilen, blok, background)
157
157
  }
158
158
 
159
159
  /** Varyantın metin rengi (`text-*`), `bg-*` HARİÇ */
160
- function metin(kaynak: string, varyant: string, blok: Blok, zemin: RGB): RGB {
161
- const adaylar = varyantSinifi(kaynak, varyant)
160
+ function text(source: string, variantVal: string, blok: Blok, background: RGB): RGB {
161
+ const adaylar = variantClass(source, variantVal)
162
162
  .split(/\s+/)
163
163
  .filter((s) => /^(?:dark:)?text-/.test(s))
164
164
  const dark = adaylar.find((s) => s.startsWith('dark:'))
165
165
  const light = adaylar.find((s) => !s.startsWith('dark:'))
166
166
  const secilen = blok === 'dark' ? (dark ?? light) : light
167
- if (!secilen) throw new Error(`${varyant}/${blok}: metin sınıfı yok`)
168
- return sinifRengi(secilen, blok, zemin)
167
+ if (!secilen) throw new Error(`${variantVal}/${blok}: metin sınıfı yok`)
168
+ return classColor(secilen, blok, background)
169
169
  }
170
170
 
171
171
  /** Dolgu opaksa zeminden bağımsızdır; yarı saydamsa zemine göre değişir → ikisini de dene */
172
- function dolguMetinOrani(kaynak: string, varyant: string, blok: Blok, zemin: RGB) {
173
- const d = dolgu(kaynak, varyant, blok, zemin)
174
- return { oran: kontrast(d, metin(kaynak, varyant, blok, d)), dolguHex: hex(d) }
172
+ function fillTextRatio(source: string, variantVal: string, blok: Blok, background: RGB) {
173
+ const d = fill(source, variantVal, blok, background)
174
+ return { oran: kontrast(d, text(source, variantVal, blok, d)), fillHex: hex(d) }
175
175
  }
176
176
 
177
177
  // ════════════════════════════════════════════════════════════════════════════════
178
178
  describe('#544/#545 — hesaplayıcı çapaları (sessiz-yeşil koruması)', () => {
179
179
  it('ÇAPA: tokens.css `--info-subtle` çifti belgelenen ~9.5 oranını veriyor', () => {
180
- const oran = kontrast(semantikRgb('info-subtle', 'light'), semantikRgb('info-subtle-foreground', 'light'))
180
+ const oran = kontrast(semanticRgb('info-subtle', 'light'), semanticRgb('info-subtle-foreground', 'light'))
181
181
  expect(oran).toBeGreaterThan(9.3)
182
182
  expect(oran).toBeLessThan(9.7)
183
183
  })
184
184
 
185
185
  it('ÇAPA: siyah↔beyaz tam 21.00', () => {
186
- expect(kontrast([0, 0, 0], BEYAZ)).toBeCloseTo(21, 2)
186
+ expect(kontrast([0, 0, 0], WHITE)).toBeCloseTo(21, 2)
187
187
  })
188
188
 
189
189
  it('ÇAPA: hesaplayıcı, issue #544 gövdesindeki GERÇEK TARAYICI ölçümlerini üretiyor', () => {
190
190
  // Bu tutmuyorsa aşağıdaki tüm oranlar anlamsızdır.
191
191
  // Badge light: zemin YOK (opak dolgu) — çift doğrudan #ffffff / #ef4343
192
- expect(kontrast(semantikRgb('error', 'light'), BEYAZ)).toBeCloseTo(3.78, 2)
192
+ expect(kontrast(semanticRgb('error', 'light'), WHITE)).toBeCloseTo(3.78, 2)
193
193
  // Button dark: #f8fafc / #ef4343
194
194
  expect(
195
- kontrast(semantikRgb('destructive', 'dark'), semantikRgb('destructive-foreground', 'dark'))
195
+ kontrast(semanticRgb('destructive', 'dark'), semanticRgb('destructive-foreground', 'dark'))
196
196
  ).toBeCloseTo(3.61, 2)
197
197
  })
198
198
 
199
199
  it('NEGATİF KONTROL: eşik mantığı ayrım yapıyor — eski değerler GERÇEKTEN kalıyor', () => {
200
200
  // "Her şey yeşil" tuzağına karşı: ölçüm başarısızlığı tespit edebiliyor mu?
201
- expect(kontrast(semantikRgb('error', 'light'), BEYAZ)).toBeLessThan(AA_METIN)
202
- expect(kontrast(skalaRgb('error-500'), BEYAZ)).toBeLessThan(AA_METIN)
201
+ expect(kontrast(semanticRgb('error', 'light'), WHITE)).toBeLessThan(AA_TEXT)
202
+ expect(kontrast(scaleRgb('error-500'), WHITE)).toBeLessThan(AA_TEXT)
203
203
  // ve gerçekten geçen bir değeri geçmiş sayıyor
204
- expect(kontrast(skalaRgb('error-700'), BEYAZ)).toBeGreaterThan(AA_METIN)
204
+ expect(kontrast(scaleRgb('error-700'), WHITE)).toBeGreaterThan(AA_TEXT)
205
205
  })
206
206
 
207
207
  it('ÇAPA: skala token’ları tema-BAĞIMSIZ — `.dark` bloğu onları EZMİYOR', () => {
@@ -219,8 +219,8 @@ describe('#544 — KAPSAM KİLİDİ: token değerleri DEĞİŞMEDİ', () => {
219
219
  ['error', 'light', '0 84% 60%'],
220
220
  ['destructive', 'light', '0 84.2% 48%'],
221
221
  ['destructive', 'dark', '0 84% 60%'],
222
- ])('--%s (%s) sabit: %s', (ad, blok, beklenen) => {
223
- expect(tokenOku(ad, blok as Blok)).toBe(beklenen)
222
+ ])('--%s (%s) sabit: %s', (name, blok, expected) => {
223
+ expect(tokenOku(name, blok as Blok)).toBe(expected)
224
224
  })
225
225
 
226
226
  it('`--error` hâlâ `.dark` bloğunda EZİLMİYOR (#424/#442’nin kök-neden dayanağı)', () => {
@@ -232,27 +232,27 @@ describe('#544 — KABUL KRİTERİ: Badge destructive dolgusu ≥ 4.5:1', () =>
232
232
  // Badge `text-white` taşır. Dolgu bugün YARI SAYDAM (`dark:bg-error/90`) olduğu
233
233
  // için dark ölçümü ZEMİNE bağlı — iki gerçek zeminde birden ölçülür.
234
234
  it('LIGHT — zemin YOK (opak dolgu), çift: dolgu ↔ text-white', () => {
235
- const { oran, dolguHex } = dolguMetinOrani(badgeSrc, 'destructive', 'light', BEYAZ)
236
- expect(`${dolguHex}: ${oran >= AA_METIN ? 'geçer' : oran.toFixed(2)}`).toBe(`${dolguHex}: geçer`)
235
+ const { oran, fillHex } = fillTextRatio(badgeSrc, 'destructive', 'light', WHITE)
236
+ expect(`${fillHex}: ${oran >= AA_TEXT ? 'geçer' : oran.toFixed(2)}`).toBe(`${fillHex}: geçer`)
237
237
  })
238
238
 
239
239
  it.each([
240
240
  ['koyu sayfa --background', 'dark'],
241
241
  ['gerçek koyu kart #111827', 'kart'],
242
- ])('DARK — zemin: %s', (zeminAdi, tur) => {
243
- const zemin = tur === 'kart' ? KART_GERCEK_DARK : semantikRgb('background', 'dark')
244
- const { oran, dolguHex } = dolguMetinOrani(badgeSrc, 'destructive', 'dark', zemin)
245
- expect(`[${zeminAdi}] ${dolguHex}: ${oran >= AA_METIN ? 'geçer' : oran.toFixed(2)}`).toBe(
246
- `[${zeminAdi}] ${dolguHex}: geçer`
242
+ ])('DARK — zemin: %s', (backgroundName, tur) => {
243
+ const background = tur === 'kart' ? CARD_ACTUAL_DARK : semanticRgb('background', 'dark')
244
+ const { oran, fillHex } = fillTextRatio(badgeSrc, 'destructive', 'dark', background)
245
+ expect(`[${backgroundName}] ${fillHex}: ${oran >= AA_TEXT ? 'geçer' : oran.toFixed(2)}`).toBe(
246
+ `[${backgroundName}] ${fillHex}: geçer`
247
247
  )
248
248
  })
249
249
 
250
250
  it('HOVER durumu da AA taşıyor (düzeltme tek duruma sıkışmasın)', () => {
251
251
  for (const blok of ['light', 'dark'] as const) {
252
- const zemin = blok === 'dark' ? KART_GERCEK_DARK : BEYAZ
253
- const d = dolgu(badgeSrc, 'destructive', blok, zemin, 'hover:')
254
- const oran = kontrast(d, metin(badgeSrc, 'destructive', blok, d))
255
- expect(`${blok} hover ${hex(d)}: ${oran >= AA_METIN ? 'geçer' : oran.toFixed(2)}`).toBe(
252
+ const background = blok === 'dark' ? CARD_ACTUAL_DARK : WHITE
253
+ const d = fill(badgeSrc, 'destructive', blok, background, 'hover:')
254
+ const oran = kontrast(d, text(badgeSrc, 'destructive', blok, d))
255
+ expect(`${blok} hover ${hex(d)}: ${oran >= AA_TEXT ? 'geçer' : oran.toFixed(2)}`).toBe(
256
256
  `${blok} hover ${hex(d)}: geçer`
257
257
  )
258
258
  }
@@ -264,22 +264,22 @@ describe('#544 — KABUL KRİTERİ: Button destructive dolgusu ≥ 4.5:1', () =>
264
264
  // Kırmızı fazda BİLEREK yeşil kalan test. İki işi var:
265
265
  // (1) düzeneğin topluca kırmızı olmadığını gösterir,
266
266
  // (2) #442'nin "geçen temayı oynatma, regresyon yüzeyini sıfır tut" kuralını kilitler.
267
- const { oran } = dolguMetinOrani(buttonSrc, 'destructive', 'light', BEYAZ)
268
- expect(oran).toBeGreaterThanOrEqual(AA_METIN)
267
+ const { oran } = fillTextRatio(buttonSrc, 'destructive', 'light', WHITE)
268
+ expect(oran).toBeGreaterThanOrEqual(AA_TEXT)
269
269
  expect(oran).toBeCloseTo(4.66, 1)
270
- expect(varyantSinifi(buttonSrc, 'destructive')).toContain('bg-destructive text-destructive-foreground')
270
+ expect(variantClass(buttonSrc, 'destructive')).toContain('bg-destructive text-destructive-foreground')
271
271
  })
272
272
 
273
273
  it('DARK — zemin YOK (opak dolgu), çift: dolgu ↔ text-destructive-foreground', () => {
274
- const { oran, dolguHex } = dolguMetinOrani(buttonSrc, 'destructive', 'dark', KART_GERCEK_DARK)
275
- expect(`${dolguHex}: ${oran >= AA_METIN ? 'geçer' : oran.toFixed(2)}`).toBe(`${dolguHex}: geçer`)
274
+ const { oran, fillHex } = fillTextRatio(buttonSrc, 'destructive', 'dark', CARD_ACTUAL_DARK)
275
+ expect(`${fillHex}: ${oran >= AA_TEXT ? 'geçer' : oran.toFixed(2)}`).toBe(`${fillHex}: geçer`)
276
276
  })
277
277
 
278
278
  it('HOVER + ACTIVE durumları da AA taşıyor (dark)', () => {
279
279
  for (const onek of ['hover:', 'active:']) {
280
- const d = dolgu(buttonSrc, 'destructive', 'dark', KART_GERCEK_DARK, onek)
281
- const oran = kontrast(d, metin(buttonSrc, 'destructive', 'dark', d))
282
- expect(`${onek}${hex(d)}: ${oran >= AA_METIN ? 'geçer' : oran.toFixed(2)}`).toBe(
280
+ const d = fill(buttonSrc, 'destructive', 'dark', CARD_ACTUAL_DARK, onek)
281
+ const oran = kontrast(d, text(buttonSrc, 'destructive', 'dark', d))
282
+ expect(`${onek}${hex(d)}: ${oran >= AA_TEXT ? 'geçer' : oran.toFixed(2)}`).toBe(
283
283
  `${onek}${hex(d)}: geçer`
284
284
  )
285
285
  }
@@ -296,8 +296,8 @@ describe('#544 — kimlik korunumu: “uyarı kırmızısı” algısı kayboldu
296
296
  const h = mx === r ? ((g - b) / d) % 6 : mx === g ? (b - r) / d + 2 : (r - g) / d + 4
297
297
  return Math.round((h * 60 + 360) % 360)
298
298
  }
299
- expect(hue(dolgu(badgeSrc, 'destructive', 'light', BEYAZ))).toBe(0)
300
- expect(hue(dolgu(buttonSrc, 'destructive', 'dark', KART_GERCEK_DARK))).toBe(0)
299
+ expect(hue(fill(badgeSrc, 'destructive', 'light', WHITE))).toBe(0)
300
+ expect(hue(fill(buttonSrc, 'destructive', 'dark', CARD_ACTUAL_DARK))).toBe(0)
301
301
  })
302
302
  })
303
303
 
@@ -306,43 +306,43 @@ describe('#544 — REGRESYON BEKÇİSİ: dokunulmayan varyantlar bit-bit aynı',
306
306
  // düzeltme kapsam dışına taşmış demektir.
307
307
  it('Badge: destructive DIŞINDAKİ varyant dizgeleri değişmedi', () => {
308
308
  // NOT: Badge'in VARSAYILAN varyantının adı `default` değil `primary`.
309
- expect(varyantSinifi(badgeSrc, 'primary')).toContain('bg-primary text-primary-foreground')
310
- expect(varyantSinifi(badgeSrc, 'secondary')).toContain('bg-secondary text-secondary-foreground')
311
- expect(varyantSinifi(badgeSrc, 'outline')).toContain('bg-transparent text-foreground')
312
- expect(varyantSinifi(badgeSrc, 'success')).toContain('bg-success text-success-foreground')
313
- expect(varyantSinifi(badgeSrc, 'warning')).toContain('bg-warning text-warning-foreground')
309
+ expect(variantClass(badgeSrc, 'primary')).toContain('bg-primary text-primary-foreground')
310
+ expect(variantClass(badgeSrc, 'secondary')).toContain('bg-secondary text-secondary-foreground')
311
+ expect(variantClass(badgeSrc, 'outline')).toContain('bg-transparent text-foreground')
312
+ expect(variantClass(badgeSrc, 'success')).toContain('bg-success text-success-foreground')
313
+ expect(variantClass(badgeSrc, 'warning')).toContain('bg-warning text-warning-foreground')
314
314
  })
315
315
 
316
316
  it('Button: destructive DIŞINDAKİ varyant dizgeleri değişmedi', () => {
317
- expect(varyantSinifi(buttonSrc, 'ghost')).toContain('text-gray-700')
318
- expect(varyantSinifi(buttonSrc, 'success')).toContain('bg-success text-success-foreground')
319
- expect(varyantSinifi(buttonSrc, 'outline')).toContain('border border-input')
317
+ expect(variantClass(buttonSrc, 'ghost')).toContain('text-gray-700')
318
+ expect(variantClass(buttonSrc, 'success')).toContain('bg-success text-success-foreground')
319
+ expect(variantClass(buttonSrc, 'outline')).toContain('border border-input')
320
320
  })
321
321
 
322
322
  it('KAPSAM KİLİDİ: `--error`/`--destructive` DEFAULT token’ının diğer tüketicileri etkilenmedi', () => {
323
323
  // Token değişmediği için `bg-error` yazan 35 başka yüzey ve `text-destructive`
324
324
  // yazan 94 yüzey bit-bit aynı kalır. Switch bunun canlı kanıtı.
325
- const switchSrc = fs.readFileSync(path.join(PAKET, 'components/ui/switch.tsx'), 'utf8')
325
+ const switchSrc = fs.readFileSync(path.join(PKG, 'components/ui/switch.tsx'), 'utf8')
326
326
  expect(switchSrc).toContain('data-[state=checked]:bg-error')
327
327
  expect(switchSrc).not.toContain('bg-error-600')
328
328
  })
329
329
  })
330
330
 
331
331
  describe('#544 — render sözleşmesi (<Badge /> / <Button />)', () => {
332
- const sinif = (jsx: React.ReactElement, sec: string) => {
332
+ const cls = (jsx: React.ReactElement, sec: string) => {
333
333
  const { container } = render(jsx)
334
334
  return (container.querySelector(sec) as HTMLElement).className
335
335
  }
336
336
 
337
337
  it('Badge destructive ARTIK yarı saydam `dark:bg-error/90` taşımıyor', () => {
338
338
  // Yarı saydam dolgu, kontrastı ZEMİNE bağımlı kılıyordu (gerçek kartta 4.43).
339
- const c = sinif(<Badge variant="destructive">x</Badge>, 'div')
339
+ const c = cls(<Badge variant="destructive">x</Badge>, 'div')
340
340
  expect(c).not.toContain('dark:bg-error/90')
341
341
  expect(c).toContain('text-white')
342
342
  })
343
343
 
344
344
  it('Button destructive dark dolgusu opak bir skala kademesi', () => {
345
- const c = sinif(<Button variant="destructive">x</Button>, 'button')
345
+ const c = cls(<Button variant="destructive">x</Button>, 'button')
346
346
  expect(c).not.toContain('dark:bg-destructive ')
347
347
  expect(c).toContain('text-destructive-foreground')
348
348
  })
@@ -350,11 +350,11 @@ describe('#544 — render sözleşmesi (<Badge /> / <Button />)', () => {
350
350
  it('TEK dolgu utility’si per durum — Tailwind sıra belirsizliği YOK', () => {
351
351
  // `dark:bg-destructive dark:bg-error-600` ikisi birden yazılırsa hangisinin
352
352
  // kazanacağı Tailwind'in İÇ sıralamasına kalır (sınıf dizgesi sırasına DEĞİL).
353
- for (const [kaynak, ad] of [[badgeSrc, 'badge'], [buttonSrc, 'button']] as const) {
354
- const siniflar = varyantSinifi(kaynak, 'destructive').split(/\s+/)
353
+ for (const [source, name] of [[badgeSrc, 'badge'], [buttonSrc, 'button']] as const) {
354
+ const classNames = variantClass(source, 'destructive').split(/\s+/)
355
355
  for (const onek of ['', 'dark:', 'hover:', 'dark:hover:', 'active:', 'dark:active:']) {
356
- const n = siniflar.filter((s) => new RegExp(`^${onek}bg-(?!.*shadow)`).test(s)).length
357
- expect(`${ad} "${onek}bg-*" adedi: ${n}`).toBe(`${ad} "${onek}bg-*" adedi: ${n > 1 ? 1 : n}`)
356
+ const n = classNames.filter((s) => new RegExp(`^${onek}bg-(?!.*shadow)`).test(s)).length
357
+ expect(`${name} "${onek}bg-*" adedi: ${n}`).toBe(`${name} "${onek}bg-*" adedi: ${n > 1 ? 1 : n}`)
358
358
  }
359
359
  }
360
360
  })
@@ -376,11 +376,11 @@ const CONFIGLER = {
376
376
  'error-boundary': 'src/docs-config/error-boundary.config.tsx',
377
377
  'pinch-zoom': 'src/docs-config/pinch-zoom.config.tsx',
378
378
  } as const
379
- const oku = (rel: string) => fs.readFileSync(path.join(DEPO, rel), 'utf8')
379
+ const read = (rel: string) => fs.readFileSync(path.join(DEPO, rel), 'utf8')
380
380
 
381
381
  /** Bir sınıfın kaç kez geçtiği — "kaç düğüm" iddiasını SAYIYLA kilitler */
382
- const sayac = (src: string, sinif: string) =>
383
- (src.match(new RegExp(`(?<![\\w-])${sinif.replace(/[/]/g, '\\/')}(?![\\w-])`, 'g')) ?? []).length
382
+ const counter = (src: string, cls: string) =>
383
+ (src.match(new RegExp(`(?<![\\w-])${cls.replace(/[/]/g, '\\/')}(?![\\w-])`, 'g')) ?? []).length
384
384
 
385
385
  describe('#545 — hesaplayıcı çapası: issue’nun tarayıcı ölçümleri birebir üretiliyor', () => {
386
386
  it('Tailwind paleti beklenen sürümden ve beklenen hex’lerde', () => {
@@ -392,33 +392,33 @@ describe('#545 — hesaplayıcı çapası: issue’nun tarayıcı ölçümleri b
392
392
  })
393
393
 
394
394
  it('altı ripple butonu: issue’daki 1.91-3.95 aralığı yeniden üretiliyor (zemin: opak dolgu ↔ #ffffff metin)', () => {
395
- const beklenen: Record<string, number> = {
395
+ const expected: Record<string, number> = {
396
396
  yellow: 1.92, green: 2.28, cyan: 2.43, blue: 3.68, red: 3.76, purple: 3.96,
397
397
  }
398
- for (const [aile, oran] of Object.entries(beklenen)) {
399
- expect(kontrast(hx(twColors[aile][500]), BEYAZ)).toBeCloseTo(oran, 1)
398
+ for (const [aile, oran] of Object.entries(expected)) {
399
+ expect(kontrast(hx(twColors[aile][500]), WHITE)).toBeCloseTo(oran, 1)
400
400
  }
401
401
  })
402
402
 
403
403
  it('ZEMİN DERSİ: `text-red-600` BEYAZA karşı geçer, GERÇEK zemininde (bg-red-50) KALIR', () => {
404
404
  // Issue'nun kaydettiği ölçüm hatası tam buydu; burası onu kalıcı kılar.
405
- expect(kontrast(hx(twColors.red[600]), BEYAZ)).toBeCloseTo(4.83, 2)
405
+ expect(kontrast(hx(twColors.red[600]), WHITE)).toBeCloseTo(4.83, 2)
406
406
  expect(kontrast(hx(twColors.red[600]), hx(twColors.red[50]))).toBeCloseTo(4.41, 2)
407
- expect(kontrast(hx(twColors.red[600]), hx(twColors.red[50]))).toBeLessThan(AA_METIN)
407
+ expect(kontrast(hx(twColors.red[600]), hx(twColors.red[50]))).toBeLessThan(AA_TEXT)
408
408
  })
409
409
 
410
410
  it('`text-green-600` beyaz zeminde 3.29 (issue’nun hiç ölçmediği kalem)', () => {
411
- expect(kontrast(hx(twColors.green[600]), BEYAZ)).toBeCloseTo(3.30, 1)
411
+ expect(kontrast(hx(twColors.green[600]), WHITE)).toBeCloseTo(3.30, 1)
412
412
  })
413
413
 
414
414
  it('semantik skala sınıfları ÖLÜ DEĞİL — preset onları açıyor ve hex’ler ham palete eşit', () => {
415
415
  // #307/#529 ölü-sınıf tuzağı: `text-success-700` CSS üretmiyorsa düzeltme
416
416
  // sessizce hiçbir şey yapmaz. Token tanımı + ham palet eşitliği ikisi de kanıt.
417
- const preset = fs.readFileSync(path.resolve(PAKET, '../tailwind-preset.js'), 'utf8')
417
+ const preset = fs.readFileSync(path.resolve(PKG, '../tailwind-preset.js'), 'utf8')
418
418
  for (const [aile, ham] of [['error', 'red'], ['success', 'green'], ['warning', 'yellow']] as const) {
419
419
  for (const k of [400, 700]) {
420
420
  expect(preset).toMatch(new RegExp(`\\b${aile}:\\s*\\{[^}]*\\b${k}:\\s*"rgb\\(var\\(--${aile}-${k}\\)`, 's'))
421
- expect(hex(skalaRgb(`${aile}-${k}`))).toBe(twColors[ham][k])
421
+ expect(hex(scaleRgb(`${aile}-${k}`))).toBe(twColors[ham][k])
422
422
  }
423
423
  }
424
424
  })
@@ -426,11 +426,11 @@ describe('#545 — hesaplayıcı çapası: issue’nun tarayıcı ölçümleri b
426
426
 
427
427
  describe('#545 — KABUL KRİTERİ: kalan ham literal SAYISI sıfır', () => {
428
428
  it('click-animations: altı `bg-{aile}-500` ripple butonu KALMADI (bugün 6)', () => {
429
- const src = oku(CONFIGLER['click-animations'])
430
- const kalan = ['blue', 'purple', 'green', 'red', 'yellow', 'cyan']
431
- .map((a) => [a, sayac(src, `bg-${a}-500`)] as const)
429
+ const src = read(CONFIGLER['click-animations'])
430
+ const remaining = ['blue', 'purple', 'green', 'red', 'yellow', 'cyan']
431
+ .map((a) => [a, counter(src, `bg-${a}-500`)] as const)
432
432
  .filter(([, n]) => n > 0)
433
- expect(kalan).toEqual([])
433
+ expect(remaining).toEqual([])
434
434
  })
435
435
 
436
436
  it('error-boundary: METİN rolündeki `-600` tonları KALMADI (ikonlar bilerek DURUYOR)', () => {
@@ -443,23 +443,23 @@ describe('#545 — KABUL KRİTERİ: kalan ham literal SAYISI sıfır', () => {
443
443
  * Bunları -700'e çekmek dark'ı 2.76'ya düşürüp EŞİĞİN ALTINA indirirdi — yani
444
444
  * bir ihlali başkasıyla takas ederdi. Metin rolündekiler (4.5 eşiği) dönüştü.
445
445
  */
446
- const src = oku(CONFIGLER['error-boundary'])
447
- expect(sayac(src, 'text-yellow-600')).toBe(0) // hem ikon hem metin ihlaldi (2.84)
446
+ const src = read(CONFIGLER['error-boundary'])
447
+ expect(counter(src, 'text-yellow-600')).toBe(0) // hem ikon hem metin ihlaldi (2.84)
448
448
  // Kalan `-600`lar YALNIZ ikon üzerinde olmalı
449
- const kalanKirmizi = [...src.matchAll(/text-red-600/g)].map((m) =>
449
+ const remainingRed = [...src.matchAll(/text-red-600/g)].map((m) =>
450
450
  src.slice(Math.max(0, m.index! - 90), m.index!)
451
451
  )
452
- expect(kalanKirmizi).toHaveLength(2) // :109 canlı + :475 code ikizi
453
- for (const once of kalanKirmizi) expect(once).toContain('<AlertTriangle')
454
- const kalanYesil = [...src.matchAll(/text-green-600/g)].map((m) =>
452
+ expect(remainingRed).toHaveLength(2) // :109 canlı + :475 code ikizi
453
+ for (const entry of remainingRed) expect(entry).toContain('<AlertTriangle')
454
+ const remainingGreen = [...src.matchAll(/text-green-600/g)].map((m) =>
455
455
  src.slice(Math.max(0, m.index! - 90), m.index!)
456
456
  )
457
- expect(kalanYesil).toHaveLength(1) // :158 ikon
458
- expect(kalanYesil[0]).toContain('<CheckCircle')
457
+ expect(remainingGreen).toHaveLength(1) // :158 ikon
458
+ expect(remainingGreen[0]).toContain('<CheckCircle')
459
459
  })
460
460
 
461
461
  it('pinch-zoom: `text-green-600` KALMADI (bugün 2 — biri canlı JSX, biri code bloğu)', () => {
462
- expect(sayac(oku(CONFIGLER['pinch-zoom']), 'text-green-600')).toBe(0)
462
+ expect(counter(read(CONFIGLER['pinch-zoom']), 'text-green-600')).toBe(0)
463
463
  })
464
464
 
465
465
  it('moonui-pro error-boundary: dark ikizi OLMAYAN `text-red-600` KALMADI (latent kalem)', () => {
@@ -473,12 +473,12 @@ describe('#545 — KABUL KRİTERİ: kalan ham literal SAYISI sıfır', () => {
473
473
 
474
474
  describe('#545 — KABUL KRİTERİ: yeni tonlar İKİ TEMADA da eşiği geçiyor', () => {
475
475
  it('altı ripple butonu ≥4.5 (zemin: opak dolgu ↔ #ffffff metin, tema-bağımsız)', () => {
476
- const src = oku(CONFIGLER['click-animations'])
476
+ const src = read(CONFIGLER['click-animations'])
477
477
  for (const aile of ['blue', 'purple', 'green', 'red', 'yellow', 'cyan']) {
478
478
  const m = src.match(new RegExp(`bg-${aile}-(\\d{3})`))
479
479
  expect(`${aile}: sınıf var`).toBe(m ? `${aile}: sınıf var` : `${aile}: sınıf YOK`)
480
- const oran = kontrast(hx(twColors[aile][Number(m![1])]), BEYAZ)
481
- expect(`${aile}-${m![1]}: ${oran >= AA_METIN ? 'geçer' : oran.toFixed(2)}`).toBe(
480
+ const oran = kontrast(hx(twColors[aile][Number(m![1])]), WHITE)
481
+ expect(`${aile}-${m![1]}: ${oran >= AA_TEXT ? 'geçer' : oran.toFixed(2)}`).toBe(
482
482
  `${aile}-${m![1]}: geçer`
483
483
  )
484
484
  }
@@ -492,29 +492,29 @@ describe('#545 — KABUL KRİTERİ: yeni tonlar İKİ TEMADA da eşiği geçiyor
492
492
  [CONFIGLER['error-boundary'], 'text-warning-700'],
493
493
  [CONFIGLER['pinch-zoom'], 'text-success-700'],
494
494
  ]
495
- for (const [rel, sinif] of hedefler) {
496
- const src = oku(rel)
497
- const n = sayac(src, sinif)
498
- expect(`${rel} ${sinif} adedi > 0`).toBe(`${rel} ${sinif} adedi > ${n > 0 ? 0 : 'YOK'}`)
495
+ for (const [rel, cls] of hedefler) {
496
+ const src = read(rel)
497
+ const n = counter(src, cls)
498
+ expect(`${rel} ${cls} adedi > 0`).toBe(`${rel} ${cls} adedi > ${n > 0 ? 0 : 'YOK'}`)
499
499
  // her geçtiği yerde hemen ardından dark ikizi gelmeli
500
- const ikizsiz = [...src.matchAll(new RegExp(`${sinif}(?![\\w-])(\\s+dark:text-\\S+)?`, 'g'))]
500
+ const ikizsiz = [...src.matchAll(new RegExp(`${cls}(?![\\w-])(\\s+dark:text-\\S+)?`, 'g'))]
501
501
  .filter((m) => !m[1])
502
- expect(`${rel} ${sinif} ikizsiz: ${ikizsiz.length}`).toBe(`${rel} ${sinif} ikizsiz: 0`)
502
+ expect(`${rel} ${cls} ikizsiz: ${ikizsiz.length}`).toBe(`${rel} ${cls} ikizsiz: 0`)
503
503
  }
504
504
  })
505
505
 
506
506
  it('yeni tonların ölçülen oranları — zemin ADIYLA', () => {
507
- const olcumler: Array<[string, RGB, RGB, number]> = [
508
- ['error-700 · zemin bg-red-50 #fef2f2', hx(twColors.red[700]), hx(twColors.red[50]), AA_METIN],
509
- ['success-700 · zemin kart #ffffff', hx(twColors.green[700]), BEYAZ, AA_METIN],
510
- ['success-400 · zemin koyu kart #111827', hx(twColors.green[400]), KART_GERCEK_DARK, AA_METIN],
511
- ['warning-700 · zemin bg-yellow-50 #fefce8', hx(twColors.yellow[700]), hx(twColors.yellow[50]), AA_METIN],
512
- ['warning-400 · zemin koyu kart #111827', hx(twColors.yellow[400]), KART_GERCEK_DARK, AA_METIN],
513
- ['error-400 · zemin koyu kart #111827', hx(twColors.red[400]), KART_GERCEK_DARK, AA_METIN],
507
+ const measurements: Array<[string, RGB, RGB, number]> = [
508
+ ['error-700 · zemin bg-red-50 #fef2f2', hx(twColors.red[700]), hx(twColors.red[50]), AA_TEXT],
509
+ ['success-700 · zemin kart #ffffff', hx(twColors.green[700]), WHITE, AA_TEXT],
510
+ ['success-400 · zemin koyu kart #111827', hx(twColors.green[400]), CARD_ACTUAL_DARK, AA_TEXT],
511
+ ['warning-700 · zemin bg-yellow-50 #fefce8', hx(twColors.yellow[700]), hx(twColors.yellow[50]), AA_TEXT],
512
+ ['warning-400 · zemin koyu kart #111827', hx(twColors.yellow[400]), CARD_ACTUAL_DARK, AA_TEXT],
513
+ ['error-400 · zemin koyu kart #111827', hx(twColors.red[400]), CARD_ACTUAL_DARK, AA_TEXT],
514
514
  ]
515
- for (const [ad, renk, zemin, esik] of olcumler) {
516
- const o = kontrast(renk, zemin)
517
- expect(`${ad}: ${o >= esik ? 'geçer' : o.toFixed(2)}`).toBe(`${ad}: geçer`)
515
+ for (const [name, color, background, threshold] of measurements) {
516
+ const o = kontrast(color, background)
517
+ expect(`${name}: ${o >= threshold ? 'geçer' : o.toFixed(2)}`).toBe(`${name}: geçer`)
518
518
  }
519
519
  })
520
520
  })
@@ -525,17 +525,17 @@ describe('#545 — `code:` ↔ canlı `component:` TUTARLILIĞI', () => {
525
525
  ((src.slice(0, idx).match(/`/g) ?? []).length) % 2 === 1
526
526
 
527
527
  it('error-boundary: düzeltilen sınıf HEM canlı JSX’te HEM code bloğunda var', () => {
528
- const src = oku(CONFIGLER['error-boundary'])
529
- for (const sinif of ['text-error-700', 'text-warning-700']) {
530
- const yerler = [...src.matchAll(new RegExp(`${sinif}(?![\\w-])`, 'g'))].map((m) => m.index!)
528
+ const src = read(CONFIGLER['error-boundary'])
529
+ for (const cls of ['text-error-700', 'text-warning-700']) {
530
+ const yerler = [...src.matchAll(new RegExp(`${cls}(?![\\w-])`, 'g'))].map((m) => m.index!)
531
531
  const canli = yerler.filter((i) => !codeBlogundaMi(src, i)).length
532
532
  const kod = yerler.filter((i) => codeBlogundaMi(src, i)).length
533
- expect(`${sinif} canlı:${canli > 0} kod:${kod > 0}`).toBe(`${sinif} canlı:true kod:true`)
533
+ expect(`${cls} canlı:${canli > 0} kod:${kod > 0}`).toBe(`${cls} canlı:true kod:true`)
534
534
  }
535
535
  })
536
536
 
537
537
  it('pinch-zoom: iki `In Stock` (canlı + code) AYNI sınıfı taşıyor', () => {
538
- const src = oku(CONFIGLER['pinch-zoom'])
538
+ const src = read(CONFIGLER['pinch-zoom'])
539
539
  const yerler = [...src.matchAll(/<span className="([^"]*)">In Stock<\/span>/g)].map((m) => m[1])
540
540
  expect(yerler).toHaveLength(2)
541
541
  expect(new Set(yerler).size).toBe(1)
@@ -545,20 +545,20 @@ describe('#545 — `code:` ↔ canlı `component:` TUTARLILIĞI', () => {
545
545
 
546
546
  describe('#545 — REGRESYON BEKÇİSİ: kapsam dışına taşma yok', () => {
547
547
  it('geçen tonlara DOKUNULMADI (ölçülen: eşiğin üstündeler)', () => {
548
- const src = oku(CONFIGLER['error-boundary'])
548
+ const src = read(CONFIGLER['error-boundary'])
549
549
  // -800 başlıklar (#991b1b, bg-red-50 üstünde 7.6) korunur
550
- expect(sayac(src, 'text-red-800')).toBeGreaterThan(0)
550
+ expect(counter(src, 'text-red-800')).toBeGreaterThan(0)
551
551
  // ikon rolündeki `text-red-600` (bg-red-50 üstünde 4.41 ≥ 3.0) metin eşiğine
552
552
  // tabi DEĞİL; dönüştürmemek bilinçli bir karardır
553
- expect(kontrast(hx(twColors.red[600]), hx(twColors.red[50]))).toBeGreaterThanOrEqual(AA_METIN_DISI)
553
+ expect(kontrast(hx(twColors.red[600]), hx(twColors.red[50]))).toBeGreaterThanOrEqual(AA_NON_TEXT)
554
554
  })
555
555
 
556
556
  it('vokabüler dönüşümü PİKSEL-BEDAVA: `error-300` = ham `red-300`', () => {
557
557
  // `dark:text-red-300` → `dark:text-error-300` görünümü DEĞİŞTİRMEZ; yalnız
558
558
  // ham paletten semantik skalaya geçer. Eşit olmasalardı sessiz bir renk
559
559
  // kayması olurdu — bu yüzden ölçülür, varsayılmaz.
560
- expect(hex(skalaRgb('error-300'))).toBe(twColors.red[300])
561
- expect(sayac(oku(CONFIGLER['error-boundary']), 'dark:text-error-300')).toBeGreaterThan(0)
560
+ expect(hex(scaleRgb('error-300'))).toBe(twColors.red[300])
561
+ expect(counter(read(CONFIGLER['error-boundary']), 'dark:text-error-300')).toBeGreaterThan(0)
562
562
  })
563
563
 
564
564
  it('moonui-pro: dark ikizi OLAN ikon (`:256`) bilerek DOKUNULMADI', () => {
@@ -578,24 +578,32 @@ describe('#545 — REGRESYON BEKÇİSİ: kapsam dışına taşma yok', () => {
578
578
  * bırakıldı; burası onları DONDURUR (yeni ihlal eklenirse kırmızı olur)
579
579
  * ve click-animations'ın sıfırlandığını ayrıca kilitler.
580
580
  */
581
- const TABAN: Record<string, number> = {
581
+ const BASELINE: Record<string, number> = {
582
582
  'bounce-effect.config.tsx': 16,
583
583
  'button.config.tsx': 1,
584
584
  'confetti.config.tsx': 1,
585
- 'focus-transitions.config.tsx': 6,
586
- 'glow-card.config.tsx': 1,
587
- 'slider.config.tsx': 4,
585
+ // focus-transitions.config.tsx: 6 → 0 (#663 — "Focus Colors" örneğindeki altı
586
+ // düğme beyaz metni `-500` tonuna basıyordu: blue 3.68 · purple 3.96 ·
587
+ // green 2.28 · red 3.76 · yellow 1.92 · cyan 2.43. Hue korunarak ton
588
+ // koyulaştırıldı (blue/purple/red -600, green/cyan -700); sarı hiçbir tonda
589
+ // beyazla 4.5'i geçmediği için tek istisna olarak polarite ters çevrildi
590
+ // (`bg-yellow-400 text-gray-900`, 11.90:1). Hepsi tema-değişmez.)
591
+ // glow-card.config.tsx: 1 → 0 (#594 — `bg-red-500 text-white` 3.76:1 idi,
592
+ // `bg-destructive` + `text-destructive-foreground` ile 4.66:1'e çekildi)
593
+ // slider.config.tsx: 4 → 0 (#661-15 — değer rozetleri: `bg-red-500`+beyaz
594
+ // 3.78:1 → `bg-red-600` 4.83:1; `bg-orange-500`+beyaz 2.80:1 →
595
+ // `bg-orange-700` 5.18:1. İkisi de tema-bağımsız: rozet kendi zeminini taşır.)
588
596
  'sparkles.config.tsx': 2,
589
597
  // click-animations.config.tsx: 6 → 0 (bu issue'nun kapsamı)
590
598
  }
591
- const dizin = path.join(DEPO, 'src/docs-config')
599
+ const dir = path.join(DEPO, 'src/docs-config')
592
600
  const bugun: Record<string, number> = {}
593
- for (const f of fs.readdirSync(dizin).filter((x) => x.endsWith('.config.tsx'))) {
594
- const src = fs.readFileSync(path.join(dizin, f), 'utf8')
601
+ for (const f of fs.readdirSync(dir).filter((x) => x.endsWith('.config.tsx'))) {
602
+ const src = fs.readFileSync(path.join(dir, f), 'utf8')
595
603
  const n = (src.match(/bg-(?:blue|purple|green|red|yellow|cyan|orange|pink|indigo)-500 text-white/g) ?? [])
596
604
  .length
597
605
  if (n > 0) bugun[f] = n
598
606
  }
599
- expect(bugun).toEqual(TABAN)
607
+ expect(bugun).toEqual(BASELINE)
600
608
  })
601
609
  })