@moontra/moonui 6.23.0 → 6.25.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 (57) 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 +448 -93
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +449 -95
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +1 -1
  11. package/src/__tests__/issue-544-kontrast.test.tsx +609 -0
  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__/badge.test.tsx +5 -1
  20. package/src/components/ui/__tests__/button.test.tsx +6 -1
  21. package/src/components/ui/__tests__/dark-token-contrast.test.ts +18 -18
  22. package/src/components/ui/__tests__/heading-level-policy.test.tsx +4 -4
  23. package/src/components/ui/__tests__/issue-643-card-token-parite.test.tsx +113 -0
  24. package/src/components/ui/__tests__/issue-649-button-aschild.test.tsx +199 -0
  25. package/src/components/ui/__tests__/issue-664-separator-dekoratif-rol.test.tsx +41 -0
  26. package/src/components/ui/__tests__/preset-color-shadowing.test.ts +10 -10
  27. package/src/components/ui/__tests__/primary-subtle-token.test.ts +7 -7
  28. package/src/components/ui/__tests__/progress-secondary-contrast.test.ts +38 -38
  29. package/src/components/ui/__tests__/progress-warning-error-contrast.test.ts +64 -64
  30. package/src/components/ui/__tests__/quality-group-de.test.tsx +2 -2
  31. package/src/components/ui/__tests__/responsive-color-invariance.test.ts +20 -20
  32. package/src/components/ui/__tests__/select.test.tsx +6 -1
  33. package/src/components/ui/__tests__/semantic-token-usage.test.ts +23 -23
  34. package/src/components/ui/__tests__/size-scale-convention.test.ts +10 -10
  35. package/src/components/ui/__tests__/slider-accent-contrast.test.ts +142 -136
  36. package/src/components/ui/__tests__/slider-secondary-contrast.test.ts +60 -60
  37. package/src/components/ui/__tests__/slider-warning-error-contrast.test.ts +100 -100
  38. package/src/components/ui/__tests__/switch-variant-contrast.test.tsx +103 -103
  39. package/src/components/ui/__tests__/tooltip.test.tsx +122 -0
  40. package/src/components/ui/accordion.tsx +16 -3
  41. package/src/components/ui/alert.tsx +1 -1
  42. package/src/components/ui/badge.tsx +31 -4
  43. package/src/components/ui/button.tsx +88 -21
  44. package/src/components/ui/card.tsx +59 -5
  45. package/src/components/ui/dropdown-menu.tsx +99 -5
  46. package/src/components/ui/file-upload.tsx +4 -0
  47. package/src/components/ui/index.ts +4 -1
  48. package/src/components/ui/popover-pro.tsx +41 -0
  49. package/src/components/ui/popover.tsx +162 -7
  50. package/src/components/ui/rating.tsx +24 -2
  51. package/src/components/ui/select.tsx +4 -4
  52. package/src/components/ui/simple-editor.tsx +3 -18
  53. package/src/components/ui/tabs.tsx +16 -1
  54. package/src/components/ui/textarea.tsx +6 -1
  55. package/src/components/ui/toast.tsx +17 -2
  56. package/src/components/ui/tooltip.tsx +154 -2
  57. package/src/styles/tokens.css +34 -2
@@ -45,12 +45,12 @@ const KOK = path.resolve(__dirname, '../../..')
45
45
  const tokens = fs.readFileSync(path.join(KOK, 'styles/tokens.css'), 'utf8')
46
46
 
47
47
  /** "H S% L%" biçimindeki token değerini oku (light = :root, dark = .dark bloğu) */
48
- function tokenOku(ad: string, blok: 'light' | 'dark'): string | null {
49
- const kaynak =
48
+ function tokenOku(name: string, blok: 'light' | 'dark'): string | null {
49
+ const source =
50
50
  blok === 'dark'
51
51
  ? tokens.slice(tokens.indexOf('.dark'))
52
52
  : tokens.slice(0, tokens.indexOf('.dark'))
53
- return kaynak.match(new RegExp(`--${ad}:\\s*([^;]+);`))?.[1].trim() ?? null
53
+ return source.match(new RegExp(`--${name}:\\s*([^;]+);`))?.[1].trim() ?? null
54
54
  }
55
55
 
56
56
  function hslToRgb(h: number, s: number, l: number): [number, number, number] {
@@ -80,7 +80,7 @@ const rgbTokenRgb = (v: string): [number, number, number] => {
80
80
  return [p[0], p[1], p[2]]
81
81
  }
82
82
 
83
- function bagilParlaklik([r, g, b]: [number, number, number]): number {
83
+ function relativeLuminance([r, g, b]: [number, number, number]): number {
84
84
  const c = (v: number) => {
85
85
  const x = v / 255
86
86
  return x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4)
@@ -89,8 +89,8 @@ function bagilParlaklik([r, g, b]: [number, number, number]): number {
89
89
  }
90
90
 
91
91
  function rgbKontrast(a: [number, number, number], b: [number, number, number]): number {
92
- const la = bagilParlaklik(a)
93
- const lb = bagilParlaklik(b)
92
+ const la = relativeLuminance(a)
93
+ const lb = relativeLuminance(b)
94
94
  const [hi, lo] = la > lb ? [la, lb] : [lb, la]
95
95
  return (hi + 0.05) / (lo + 0.05)
96
96
  }
@@ -127,12 +127,12 @@ function deltaE(a: [number, number, number], b: [number, number, number]): numbe
127
127
  function alfaBileske(
128
128
  ust: [number, number, number],
129
129
  alfa: number,
130
- zemin: [number, number, number]
130
+ background: [number, number, number]
131
131
  ): [number, number, number] {
132
132
  return [
133
- Math.round(alfa * ust[0] + (1 - alfa) * zemin[0]),
134
- Math.round(alfa * ust[1] + (1 - alfa) * zemin[1]),
135
- Math.round(alfa * ust[2] + (1 - alfa) * zemin[2]),
133
+ Math.round(alfa * ust[0] + (1 - alfa) * background[0]),
134
+ Math.round(alfa * ust[1] + (1 - alfa) * background[1]),
135
+ Math.round(alfa * ust[2] + (1 - alfa) * background[2]),
136
136
  ]
137
137
  }
138
138
 
@@ -140,20 +140,20 @@ function alfaBileske(
140
140
  * Docs kabuğunda ölçülen GERÇEK koyu kart zemini (#111827 = rgb(17,24,39)) —
141
141
  * #284/#368 konvansiyonu. Bu bulguda DARBOĞAZ: en düşük dark oranlarını o veriyor.
142
142
  */
143
- const KART_GERCEK_DARK: [number, number, number] = [17, 24, 39]
143
+ const CARD_ACTUAL_DARK: [number, number, number] = [17, 24, 39]
144
144
 
145
145
  /** Semantik token'ın ilgili temadaki değeri — `.dark` ezmesi YOKSA light'a düşer */
146
- function semantikRgb(ad: string, blok: 'light' | 'dark'): [number, number, number] {
147
- return hslTokenRgb(tokenOku(ad, blok) ?? tokenOku(ad, 'light')!)
146
+ function semanticRgb(name: string, blok: 'light' | 'dark'): [number, number, number] {
147
+ return hslTokenRgb(tokenOku(name, blok) ?? tokenOku(name, 'light')!)
148
148
  }
149
149
 
150
150
  /** O temada slider'ın oturabileceği tüm zeminler */
151
- function zeminler(blok: 'light' | 'dark'): Array<[string, [number, number, number]]> {
151
+ function backgrounds(blok: 'light' | 'dark'): Array<[string, [number, number, number]]> {
152
152
  const z: Array<[string, [number, number, number]]> = [
153
- ['--background', semantikRgb('background', blok)],
154
- ['--card', semantikRgb('card', blok)],
153
+ ['--background', semanticRgb('background', blok)],
154
+ ['--card', semanticRgb('card', blok)],
155
155
  ]
156
- if (blok === 'dark') z.push(['gerçek kart #111827', KART_GERCEK_DARK])
156
+ if (blok === 'dark') z.push(['gerçek kart #111827', CARD_ACTUAL_DARK])
157
157
  return z
158
158
  }
159
159
 
@@ -161,29 +161,29 @@ function zeminler(blok: 'light' | 'dark'): Array<[string, [number, number, numbe
161
161
  // PR #440'ın dersi: ölçümü "-700/-400" VARSAYIMINA göre yaparsan, biri değeri
162
162
  // eskiye döndürdüğünde kriter testleri yeşil kalır (boş-yeşil). Bu yüzden renkler
163
163
  // slider.tsx'in O ANKİ CVA sınıf dizgelerinden çözülür — track alfası dahil.
164
- const kaynak = fs.readFileSync(path.join(KOK, 'components/ui/slider.tsx'), 'utf8')
165
- const iTrack = kaynak.indexOf('sliderTrackVariants')
166
- const iRange = kaynak.indexOf('sliderRangeVariants')
167
- const iThumb = kaynak.indexOf('sliderThumbVariants')
168
- const iSon = kaynak.indexOf('SliderBaseProps')
169
- const TRACK_BLOK = kaynak.slice(iTrack, iRange)
170
- const RANGE_BLOK = kaynak.slice(iRange, iThumb)
171
- const THUMB_BLOK = kaynak.slice(iThumb, iSon)
164
+ const source = fs.readFileSync(path.join(KOK, 'components/ui/slider.tsx'), 'utf8')
165
+ const iTrack = source.indexOf('sliderTrackVariants')
166
+ const iRange = source.indexOf('sliderRangeVariants')
167
+ const iThumb = source.indexOf('sliderThumbVariants')
168
+ const iSon = source.indexOf('SliderBaseProps')
169
+ const TRACK_BLOK = source.slice(iTrack, iRange)
170
+ const RANGE_BLOK = source.slice(iRange, iThumb)
171
+ const THUMB_BLOK = source.slice(iThumb, iSon)
172
172
 
173
173
  /** CVA bloğundan `<varyant>: "<sınıflar>"` değerini oku */
174
- function varyantSinifi(blok: string, varyant: string): string {
175
- const m = blok.match(new RegExp(`\\n\\s*${varyant}:\\s*"([^"]+)"`))
176
- if (!m) throw new Error(`${varyant} varyantı bulunamadı`)
174
+ function variantClass(blok: string, variant: string): string {
175
+ const m = blok.match(new RegExp(`\\n\\s*${variant}:\\s*"([^"]+)"`))
176
+ if (!m) throw new Error(`${variant} varyantı bulunamadı`)
177
177
  return m[1]
178
178
  }
179
179
 
180
180
  /** Tek bir Tailwind sınıfını renge çevir (arbitrary skala VEYA semantik token) */
181
- function sinifRengi(sinif: string, blok: 'light' | 'dark'): [number, number, number] {
182
- const skala = sinif.match(/^(?:bg|border)-\[rgb\(var\(--([a-z0-9-]+)\)\)\]$/)
183
- if (skala) return rgbTokenRgb(tokenOku(skala[1], 'light')!) // skala yalnız :root'ta
184
- const semantik = sinif.match(/^(?:bg|border)-([a-z-]+)$/)
185
- if (semantik) return semantikRgb(semantik[1], blok)
186
- throw new Error(`çözülemeyen sınıf: ${sinif}`)
181
+ function classColor(cls: string, blok: 'light' | 'dark'): [number, number, number] {
182
+ const scale = cls.match(/^(?:bg|border)-\[rgb\(var\(--([a-z0-9-]+)\)\)\]$/)
183
+ if (scale) return rgbTokenRgb(tokenOku(scale[1], 'light')!) // skala yalnız :root'ta
184
+ const semantic = cls.match(/^(?:bg|border)-([a-z-]+)$/)
185
+ if (semantic) return semanticRgb(semantic[1], blok)
186
+ throw new Error(`çözülemeyen sınıf: ${cls}`)
187
187
  }
188
188
 
189
189
  /**
@@ -191,47 +191,47 @@ function sinifRengi(sinif: string, blok: 'light' | 'dark'): [number, number, num
191
191
  * `dark:` önekli sınıf dark temada kazanır; `onek` ile eksen seçilir (thumb dizgesi
192
192
  * `border-*` yanında `bg-background` da taşır).
193
193
  */
194
- function uygulananRenk(
194
+ function appliedColor(
195
195
  cvaBlok: string,
196
- varyant: string,
196
+ variant: string,
197
197
  onek: 'bg' | 'border',
198
198
  blok: 'light' | 'dark'
199
199
  ): [number, number, number] {
200
- const siniflar = varyantSinifi(cvaBlok, varyant)
200
+ const classNames = variantClass(cvaBlok, variant)
201
201
  .split(/\s+/)
202
202
  .filter((s) => s.replace(/^dark:/, '').startsWith(`${onek}-`))
203
- const darkSinif = siniflar.find((s) => s.startsWith('dark:'))?.slice('dark:'.length)
204
- const lightSinif = siniflar.find((s) => !s.startsWith('dark:'))
205
- if (!lightSinif) throw new Error(`${varyant}/${onek}: temel (light) sınıf yok`)
206
- return sinifRengi(blok === 'dark' ? darkSinif ?? lightSinif : lightSinif, blok)
203
+ const darkClass = classNames.find((s) => s.startsWith('dark:'))?.slice('dark:'.length)
204
+ const lightClass = classNames.find((s) => !s.startsWith('dark:'))
205
+ if (!lightClass) throw new Error(`${variant}/${onek}: temel (light) sınıf yok`)
206
+ return classColor(blok === 'dark' ? darkClass ?? lightClass : lightClass, blok)
207
207
  }
208
208
 
209
- const rangeRengi = (v: string, blok: 'light' | 'dark') => uygulananRenk(RANGE_BLOK, v, 'bg', blok)
210
- const thumbKenarRengi = (v: string, blok: 'light' | 'dark') =>
211
- uygulananRenk(THUMB_BLOK, v, 'border', blok)
209
+ const rangeRengi = (v: string, blok: 'light' | 'dark') => appliedColor(RANGE_BLOK, v, 'bg', blok)
210
+ const thumbBorderColor = (v: string, blok: 'light' | 'dark') =>
211
+ appliedColor(THUMB_BLOK, v, 'border', blok)
212
212
 
213
213
  /** Track'in her zemin üstündeki bileşkesi — alfa da KAYNAKTAN okunur */
214
- function trackler(varyant: string, blok: 'light' | 'dark') {
215
- const sinif = varyantSinifi(TRACK_BLOK, varyant)
216
- const m = sinif.match(/^bg-([a-z-]+)\/(\d+)$/)
217
- if (!m) throw new Error(`track sınıfı çözülemedi: ${sinif}`)
218
- const taban = semantikRgb(m[1], blok)
214
+ function trackler(variant: string, blok: 'light' | 'dark') {
215
+ const cls = variantClass(TRACK_BLOK, variant)
216
+ const m = cls.match(/^bg-([a-z-]+)\/(\d+)$/)
217
+ if (!m) throw new Error(`track sınıfı çözülemedi: ${cls}`)
218
+ const base = semanticRgb(m[1], blok)
219
219
  const alfa = Number(m[2]) / 100
220
- return zeminler(blok).map(([ad, z]) => [ad, alfaBileske(taban, alfa, z)] as const)
220
+ return backgrounds(blok).map(([name, z]) => [name, alfaBileske(base, alfa, z)] as const)
221
221
  }
222
222
 
223
223
  /** `accent` DIŞINDAKİ slider varyantlarının o temada gerçekten uygulanan dolgu renkleri */
224
224
  const KARDESLER = ['default', 'primary', 'secondary', 'success', 'warning', 'error'] as const
225
- const kardesRenkleri = (blok: 'light' | 'dark') =>
225
+ const siblingColors = (blok: 'light' | 'dark') =>
226
226
  KARDESLER.map((v) => [v, rangeRengi(v, blok)] as const)
227
227
 
228
228
  const KADEMELER = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]
229
229
  /** Ayırt edilebilirlik eşiği (CIE76). Eşiğin yeri sonucu DEĞİŞTİRMİYOR: elenen
230
230
  * tek-orta-ton adayı 500, kardeşine ΔE = 0.0 ile birebir AYNI renk. */
231
- const DELTAE_ESIK = 10
231
+ const DELTAE_THRESHOLD = 10
232
232
 
233
233
  /** Slider DOM'u: kök > .moonui-theme > track > range; thumb'lar track'in KARDEŞİ */
234
- function parcalar(container: HTMLElement) {
234
+ function parts(container: HTMLElement) {
235
235
  const kabuk = container.querySelector('.moonui-theme') as HTMLElement
236
236
  expect(kabuk).not.toBeNull()
237
237
  const track = kabuk.firstElementChild as HTMLElement
@@ -246,8 +246,8 @@ describe('#448 hesaplayıcı çapaları', () => {
246
246
  it('kontrast hesaplayıcısı DOĞRU — belgelenmiş token çifti beklenen oranı veriyor', () => {
247
247
  // tokens.css `--info-subtle` çiftini 9.52:1 diye belgeliyor.
248
248
  const oran = rgbKontrast(
249
- semantikRgb('info-subtle', 'light'),
250
- semantikRgb('info-subtle-foreground', 'light')
249
+ semanticRgb('info-subtle', 'light'),
250
+ semanticRgb('info-subtle-foreground', 'light')
251
251
  )
252
252
  expect(oran).toBeGreaterThan(9.3)
253
253
  expect(oran).toBeLessThan(9.7)
@@ -262,20 +262,20 @@ describe('#448 hesaplayıcı çapaları', () => {
262
262
  // Çapraz doğrulama: issue tablosu (A: 1.12/1.30/1.16 · B: 1.15/1.36/1.20) bu
263
263
  // dosyadan BAĞIMSIZ olarak PR #446 çalışmasında ölçülmüştü. Model onları
264
264
  // tutturamıyorsa aşağıdaki tüm oranlar anlamsızdır.
265
- const eskiA = (blok: 'light' | 'dark') =>
265
+ const oldA = (blok: 'light' | 'dark') =>
266
266
  trackler('accent', blok).map(([, t]) =>
267
- Number(rgbKontrast(semantikRgb('accent', blok), t).toFixed(2))
267
+ Number(rgbKontrast(semanticRgb('accent', blok), t).toFixed(2))
268
268
  )
269
- expect(eskiA('light')[0]).toBe(1.12)
270
- expect(eskiA('dark')[0]).toBe(1.3)
271
- expect(eskiA('dark')[2]).toBe(1.16) // gerçek docs kartı
272
- const eskiB = (blok: 'light' | 'dark') =>
273
- zeminler(blok).map(([, z]) =>
274
- Number(rgbKontrast(semantikRgb('accent', blok), z).toFixed(2))
269
+ expect(oldA('light')[0]).toBe(1.12)
270
+ expect(oldA('dark')[0]).toBe(1.3)
271
+ expect(oldA('dark')[2]).toBe(1.16) // gerçek docs kartı
272
+ const oldB = (blok: 'light' | 'dark') =>
273
+ backgrounds(blok).map(([, z]) =>
274
+ Number(rgbKontrast(semanticRgb('accent', blok), z).toFixed(2))
275
275
  )
276
- expect(eskiB('light')[0]).toBe(1.15)
277
- expect(eskiB('dark')[0]).toBe(1.36)
278
- expect(eskiB('dark')[2]).toBe(1.2)
276
+ expect(oldB('light')[0]).toBe(1.15)
277
+ expect(oldB('dark')[0]).toBe(1.36)
278
+ expect(oldB('dark')[2]).toBe(1.2)
279
279
  })
280
280
  })
281
281
 
@@ -305,43 +305,49 @@ describe('#448 kök neden — `--accent` YÜZEY token’ı, skalası YOK', () =>
305
305
  // Track'i değiştirerek onarmanın neden imkânsız olduğunun kanıtı: dolgu rengi
306
306
  // zeminden ayrışmıyorsa, altına konacak hiçbir track 3:1 üretemez.
307
307
  for (const blok of ['light', 'dark'] as const) {
308
- for (const [, z] of zeminler(blok)) {
309
- expect(rgbKontrast(semantikRgb('accent', blok), z)).toBeLessThan(1.5)
308
+ for (const [, z] of backgrounds(blok)) {
309
+ expect(rgbKontrast(semanticRgb('accent', blok), z)).toBeLessThan(1.5)
310
310
  }
311
311
  }
312
312
  })
313
313
  })
314
314
 
315
315
  describe('#448 reddedilen alternatifler (seçimin gerekçesi)', () => {
316
- it('`bg-accent-foreground` ELENDİ — iki temada da `--primary` ile BİREBİR aynı', () => {
316
+ it('`bg-accent-foreground` ELENDİ — dark birebir, light NEREDEYSE aynı `--primary` klonu', () => {
317
317
  // #389'un `secondary` için verdiği kararın aynısı: varyant primary klonuna dönerdi.
318
- for (const blok of ['light', 'dark'] as const) {
319
- expect(tokenOku('accent-foreground', blok)).toBe(tokenOku('primary', blok))
320
- }
318
+ // #647: light'ta `--accent-foreground` AA için 51%→49% oldu (--primary 51%te
319
+ // kaldı) — `slider-secondary-contrast.test.ts`'teki aynı desen (ΔL=%2, kontrast
320
+ // ~1.07, ayırt edilemez ama artık bit-bit eşit değil). Dark hiç dokunulmadı.
321
+ const lightOran = rgbKontrast(
322
+ hslTokenRgb(tokenOku('accent-foreground', 'light')!),
323
+ hslTokenRgb(tokenOku('primary', 'light')!)
324
+ )
325
+ expect(lightOran).toBeLessThan(1.3) // ayırt edilemez — near-identical
326
+ expect(tokenOku('accent-foreground', 'dark')).toBe(tokenOku('primary', 'dark'))
321
327
  })
322
328
 
323
329
  it('`bg-accent-foreground` AYRICA tek başına dolgu olamaz — preset’lerin 2’sinde BEYAZ', () => {
324
330
  // Creative + Nature preset'lerinde accentForeground "0 0% 100%": beyaz sayfada
325
331
  // beyaz dolgu. Token, accent YÜZEYİNİN mürekkebi; bağımsız dolgu rengi değil.
326
332
  const presetler = fs.readFileSync(path.join(KOK, 'lib/theme-presets.ts'), 'utf8')
327
- const beyazlar = [...presetler.matchAll(/accentForeground:\s*"([^"]+)"/g)].filter(
333
+ const whites = [...presetler.matchAll(/accentForeground:\s*"([^"]+)"/g)].filter(
328
334
  (m) => m[1] === '0 0% 100%'
329
335
  )
330
- expect(beyazlar.length).toBeGreaterThanOrEqual(2)
336
+ expect(whites.length).toBeGreaterThanOrEqual(2)
331
337
  })
332
338
 
333
339
  it('slate ailesinde TEMA-KÖR geçen tek kademe 500 — o da `secondary` varyantının KLONU', () => {
334
340
  // #385/#406/#389'un çözüm şeklinin burada neden uygulanamadığının kanıtı.
335
- const gecenler = KADEMELER.filter((k) => {
341
+ const passingOnes = KADEMELER.filter((k) => {
336
342
  const ham = tokenOku(`secondary-${k}`, 'light')
337
343
  if (!ham) return false
338
344
  const c = rgbTokenRgb(ham)
339
345
  return (['light', 'dark'] as const).every((blok) =>
340
346
  trackler('accent', blok).every(([, t]) => rgbKontrast(c, t) >= 3.0) &&
341
- zeminler(blok).every(([, z]) => rgbKontrast(c, z) >= 3.0)
347
+ backgrounds(blok).every(([, z]) => rgbKontrast(c, z) >= 3.0)
342
348
  )
343
349
  })
344
- expect(gecenler).toEqual([500])
350
+ expect(passingOnes).toEqual([500])
345
351
  // ...ve 500, `secondary` varyantının o anki rengiyle BİREBİR aynı (ΔE = 0).
346
352
  const k500 = rgbTokenRgb(tokenOku('secondary-500', 'light')!)
347
353
  expect(deltaE(k500, rangeRengi('secondary', 'light'))).toBeCloseTo(0, 6)
@@ -351,49 +357,49 @@ describe('#448 reddedilen alternatifler (seçimin gerekçesi)', () => {
351
357
  it('DARK temada eşiği geçen VE kardeşlerden ayırt edilebilen TEK kademe 400', () => {
352
358
  // Seçimin zorunlu olduğunun kanıtı: dark tarafta serbestlik YOK.
353
359
  // ≤300 `default`/`primary` ile çakışır, 500 `secondary` klonu, ≥600 eşiğin altı.
354
- const uygun = KADEMELER.filter((k) => {
360
+ const suitable = KADEMELER.filter((k) => {
355
361
  const ham = tokenOku(`secondary-${k}`, 'light')
356
362
  if (!ham) return false
357
363
  const c = rgbTokenRgb(ham)
358
- const esik =
364
+ const threshold =
359
365
  trackler('accent', 'dark').every(([, t]) => rgbKontrast(c, t) >= 3.0) &&
360
- zeminler('dark').every(([, z]) => rgbKontrast(c, z) >= 3.0)
361
- const ayirt = kardesRenkleri('dark').every(([, r]) => deltaE(c, r) >= DELTAE_ESIK)
362
- return esik && ayirt
366
+ backgrounds('dark').every(([, z]) => rgbKontrast(c, z) >= 3.0)
367
+ const ayirt = siblingColors('dark').every(([, r]) => deltaE(c, r) >= DELTAE_THRESHOLD)
368
+ return threshold && ayirt
363
369
  })
364
- expect(uygun).toEqual([400])
370
+ expect(suitable).toEqual([400])
365
371
  })
366
372
 
367
373
  it('LIGHT temada {600,700,800} uygun; 700 kardeş ayrımını MAKSİMİZE ediyor', () => {
368
- const skor = (k: number) => {
374
+ const score = (k: number) => {
369
375
  const c = rgbTokenRgb(tokenOku(`secondary-${k}`, 'light')!)
370
- return Math.min(...kardesRenkleri('light').map(([, r]) => deltaE(c, r)))
376
+ return Math.min(...siblingColors('light').map(([, r]) => deltaE(c, r)))
371
377
  }
372
- const uygun = KADEMELER.filter((k) => {
378
+ const suitable = KADEMELER.filter((k) => {
373
379
  const c = rgbTokenRgb(tokenOku(`secondary-${k}`, 'light')!)
374
- const esik =
380
+ const threshold =
375
381
  trackler('accent', 'light').every(([, t]) => rgbKontrast(c, t) >= 3.0) &&
376
- zeminler('light').every(([, z]) => rgbKontrast(c, z) >= 3.0)
377
- return esik && skor(k) >= DELTAE_ESIK
382
+ backgrounds('light').every(([, z]) => rgbKontrast(c, z) >= 3.0)
383
+ return threshold && score(k) >= DELTAE_THRESHOLD
378
384
  })
379
- expect(uygun).toEqual([600, 700, 800])
380
- expect(Math.max(...uygun.map(skor))).toBeCloseTo(skor(700), 6)
385
+ expect(suitable).toEqual([600, 700, 800])
386
+ expect(Math.max(...suitable.map(score))).toBeCloseTo(score(700), 6)
381
387
  })
382
388
  })
383
389
 
384
390
  describe('#448 kaynak sözleşmesi — slider.tsx', () => {
385
391
  it('track `accent` sınıfı `/20` alfasını KORUYOR (yedi varyantın ortak deseni)', () => {
386
- expect(varyantSinifi(TRACK_BLOK, 'accent')).toBe('bg-accent/20')
392
+ expect(variantClass(TRACK_BLOK, 'accent')).toBe('bg-accent/20')
387
393
  })
388
394
 
389
395
  it('EKSEN A: range `accent` light’ta -700, dark’ta -400', () => {
390
- expect(varyantSinifi(RANGE_BLOK, 'accent')).toBe(
396
+ expect(variantClass(RANGE_BLOK, 'accent')).toBe(
391
397
  'bg-[rgb(var(--secondary-700))] dark:bg-[rgb(var(--secondary-400))]'
392
398
  )
393
399
  })
394
400
 
395
401
  it('EKSEN B: thumb `accent` kenarlığı light’ta -700, dark’ta -400', () => {
396
- expect(varyantSinifi(THUMB_BLOK, 'accent')).toBe(
402
+ expect(variantClass(THUMB_BLOK, 'accent')).toBe(
397
403
  'border-[rgb(var(--secondary-700))] dark:border-[rgb(var(--secondary-400))] bg-background'
398
404
  )
399
405
  })
@@ -401,27 +407,27 @@ describe('#448 kaynak sözleşmesi — slider.tsx', () => {
401
407
  it('KAPSAM KİLİDİ: diğer altı varyant DOKUNULMADI', () => {
402
408
  // #424/#442 (warning/error) ve #389/#407 (secondary) kararları korunuyor;
403
409
  // primary/success/default zaten eşiği geçiyor. Gereksiz değişiklik = regresyon riski.
404
- expect(varyantSinifi(RANGE_BLOK, 'default')).toBe('bg-foreground')
405
- expect(varyantSinifi(RANGE_BLOK, 'primary')).toBe('bg-primary')
406
- expect(varyantSinifi(RANGE_BLOK, 'secondary')).toBe('bg-[rgb(var(--secondary-500))]')
407
- expect(varyantSinifi(RANGE_BLOK, 'success')).toBe('bg-success')
408
- expect(varyantSinifi(RANGE_BLOK, 'warning')).toBe(
410
+ expect(variantClass(RANGE_BLOK, 'default')).toBe('bg-foreground')
411
+ expect(variantClass(RANGE_BLOK, 'primary')).toBe('bg-primary')
412
+ expect(variantClass(RANGE_BLOK, 'secondary')).toBe('bg-[rgb(var(--secondary-500))]')
413
+ expect(variantClass(RANGE_BLOK, 'success')).toBe('bg-success')
414
+ expect(variantClass(RANGE_BLOK, 'warning')).toBe(
409
415
  'bg-[rgb(var(--warning-700))] dark:bg-warning'
410
416
  )
411
- expect(varyantSinifi(RANGE_BLOK, 'error')).toBe('bg-[rgb(var(--error-700))] dark:bg-error')
417
+ expect(variantClass(RANGE_BLOK, 'error')).toBe('bg-[rgb(var(--error-700))] dark:bg-error')
412
418
  })
413
419
 
414
420
  it('zemin denkliği: `.moonui-theme` paket CSS’inde token yeniden tanımlamıyor', () => {
415
421
  // Hesap, track'in/tutamacın altındaki yüzeyin --background/--card olduğunu
416
422
  // VARSAYAR. Biri `.moonui-theme`i token kapsamına çevirirse o varsayım sessizce
417
423
  // yanlışlanır — o an burası kırmızı olur (#389 deseni).
418
- const stilDizini = path.join(KOK, 'styles')
419
- const stiller = fs
420
- .readdirSync(stilDizini)
424
+ const styleDir = path.join(KOK, 'styles')
425
+ const styles = fs
426
+ .readdirSync(styleDir)
421
427
  .filter((f) => f.endsWith('.css'))
422
- .map((f) => fs.readFileSync(path.join(stilDizini, f), 'utf8'))
428
+ .map((f) => fs.readFileSync(path.join(styleDir, f), 'utf8'))
423
429
  .join('\n')
424
- expect(stiller).not.toMatch(/\.moonui-theme[^{]*\{[^}]*--(accent|secondary|background|card)/)
430
+ expect(styles).not.toMatch(/\.moonui-theme[^{]*\{[^}]*--(accent|secondary|background|card)/)
425
431
  })
426
432
  })
427
433
 
@@ -430,7 +436,7 @@ describe('#448 render sözleşmesi — <Slider />', () => {
430
436
  const { container } = render(
431
437
  React.createElement(Slider, { trackVariant: 'accent', rangeVariant: 'accent' })
432
438
  )
433
- const { track, range } = parcalar(container)
439
+ const { track, range } = parts(container)
434
440
  expect(track).toHaveClass('bg-accent/20')
435
441
  expect(range.className).toContain('bg-[rgb(var(--secondary-700))]')
436
442
  expect(range.className).toContain('dark:bg-[rgb(var(--secondary-400))]')
@@ -440,7 +446,7 @@ describe('#448 render sözleşmesi — <Slider />', () => {
440
446
 
441
447
  it('accent: thumb kenarlığı yeni ifadeyle iniyor, dolgusu bg-background kalıyor', () => {
442
448
  const { container } = render(React.createElement(Slider, { thumbVariant: 'accent' }))
443
- const { thumb } = parcalar(container)
449
+ const { thumb } = parts(container)
444
450
  expect(thumb.className).toContain('border-[rgb(var(--secondary-700))]')
445
451
  expect(thumb.className).toContain('dark:border-[rgb(var(--secondary-400))]')
446
452
  expect(thumb.className).toContain('bg-background')
@@ -451,7 +457,7 @@ describe('#448 render sözleşmesi — <Slider />', () => {
451
457
  // `trackVariant || thumbVariant || "default"` ve `rangeVariant || thumbVariant ||
452
458
  // "primary"` düşüşleri değişmedi; en yaygın kullanım bu yoldan geçer.
453
459
  const { container } = render(React.createElement(Slider, { thumbVariant: 'accent' }))
454
- const { track, range, thumb } = parcalar(container)
460
+ const { track, range, thumb } = parts(container)
455
461
  expect(track).toHaveClass('bg-accent/20')
456
462
  expect(range.className).toContain('bg-[rgb(var(--secondary-700))]')
457
463
  expect(thumb.className).toContain('border-[rgb(var(--secondary-700))]')
@@ -461,7 +467,7 @@ describe('#448 render sözleşmesi — <Slider />', () => {
461
467
  const { container } = render(
462
468
  React.createElement(Slider, { thumbVariant: 'accent', rangeVariant: 'success' })
463
469
  )
464
- const { range, thumb } = parcalar(container)
470
+ const { range, thumb } = parts(container)
465
471
  expect(range.className).toContain('bg-success')
466
472
  expect(range.className).not.toContain('--secondary-700')
467
473
  // Eksenler bağımsız: tutamaç kendi varyantını korur.
@@ -472,13 +478,13 @@ describe('#448 render sözleşmesi — <Slider />', () => {
472
478
  const { container: c1 } = render(
473
479
  React.createElement(Slider, { rangeVariant: 'secondary', thumbVariant: 'secondary' })
474
480
  )
475
- const p1 = parcalar(c1)
481
+ const p1 = parts(c1)
476
482
  expect(p1.range.className).toContain('bg-[rgb(var(--secondary-500))]')
477
483
  expect(p1.range.className).not.toContain('--secondary-700')
478
484
  const { container: c2 } = render(
479
485
  React.createElement(Slider, { rangeVariant: 'primary', thumbVariant: 'primary' })
480
486
  )
481
- const p2 = parcalar(c2)
487
+ const p2 = parts(c2)
482
488
  expect(p2.range.className).toContain('bg-primary')
483
489
  expect(p2.thumb.className).toContain('border-primary')
484
490
  })
@@ -487,28 +493,28 @@ describe('#448 render sözleşmesi — <Slider />', () => {
487
493
  describe.each(['light', 'dark'] as const)('#448 %s tema — kabul kriteri ≥3:1 (WCAG 1.4.11)', (blok) => {
488
494
  it('EKSEN A (range↔track): TÜM zeminlerde geçiyor', () => {
489
495
  // Ölçülen: light 10.081 · dark 7.533 · gerçek docs kartı 6.717
490
- const dolgu = rangeRengi('accent', blok)
491
- for (const [ad, track] of trackler('accent', blok)) {
492
- const oran = rgbKontrast(dolgu, track)
493
- expect({ eksen: 'A', tema: blok, zemin: ad, gecti: oran >= 3.0 }).toEqual({
494
- eksen: 'A',
495
- tema: blok,
496
- zemin: ad,
497
- gecti: true,
496
+ const fill = rangeRengi('accent', blok)
497
+ for (const [name, track] of trackler('accent', blok)) {
498
+ const oran = rgbKontrast(fill, track)
499
+ expect({ axis: 'A', theme: blok, background: name, didPass: oran >= 3.0 }).toEqual({
500
+ axis: 'A',
501
+ theme: blok,
502
+ background: name,
503
+ didPass: true,
498
504
  })
499
505
  }
500
506
  })
501
507
 
502
508
  it('EKSEN B (thumb kenarlığı↔zemin): TÜM zeminlerde geçiyor', () => {
503
509
  // Ölçülen: light 10.355 · dark 7.857 · gerçek docs kartı 6.919
504
- const kenar = thumbKenarRengi('accent', blok)
505
- for (const [ad, zemin] of zeminler(blok)) {
506
- const oran = rgbKontrast(kenar, zemin)
507
- expect({ eksen: 'B', tema: blok, zemin: ad, gecti: oran >= 3.0 }).toEqual({
508
- eksen: 'B',
509
- tema: blok,
510
- zemin: ad,
511
- gecti: true,
510
+ const border = thumbBorderColor('accent', blok)
511
+ for (const [name, background] of backgrounds(blok)) {
512
+ const oran = rgbKontrast(border, background)
513
+ expect({ axis: 'B', theme: blok, background: name, didPass: oran >= 3.0 }).toEqual({
514
+ axis: 'B',
515
+ theme: blok,
516
+ background: name,
517
+ didPass: true,
512
518
  })
513
519
  }
514
520
  })
@@ -516,27 +522,27 @@ describe.each(['light', 'dark'] as const)('#448 %s tema — kabul kriteri ≥3:1
516
522
  it('ESKİ değer (`bg-accent`/`border-accent`) BU temada başarısızdı — bug’ın belgesi', () => {
517
523
  // Düzeltmenin GEREKLİLİĞİNİ sabitler. warning/error'dan farkı: orada yalnız
518
524
  // light kırıktı, burada İKİ tema da.
519
- const eski = semantikRgb('accent', blok)
525
+ const old = semanticRgb('accent', blok)
520
526
  for (const [, track] of trackler('accent', blok)) {
521
- expect(rgbKontrast(eski, track)).toBeLessThan(3.0)
527
+ expect(rgbKontrast(old, track)).toBeLessThan(3.0)
522
528
  }
523
- for (const [, zemin] of zeminler(blok)) {
524
- expect(rgbKontrast(eski, zemin)).toBeLessThan(3.0)
529
+ for (const [, background] of backgrounds(blok)) {
530
+ expect(rgbKontrast(old, background)).toBeLessThan(3.0)
525
531
  }
526
532
  })
527
533
 
528
534
  it('TEK RENK: tutamaç kenarlığı ile range dolgusu AYNI renk', () => {
529
535
  // #407'nin gerekçesi: "tutamaç ve dolgu tek renkte okunur".
530
- expect(thumbKenarRengi('accent', blok)).toEqual(rangeRengi('accent', blok))
536
+ expect(thumbBorderColor('accent', blok)).toEqual(rangeRengi('accent', blok))
531
537
  })
532
538
 
533
539
  it('KLON DEĞİL: accent, altı kardeş varyanttan da ayırt edilebiliyor (ΔE ≥ 10)', () => {
534
540
  // Bu iş sırasında `--secondary-500`e eşlemenin neden REDDEDİLDİĞİNİN kilidi.
535
541
  // Biri ileride accent'i secondary'nin kademesine çekerse burası kırmızı olur.
536
- const dolgu = rangeRengi('accent', blok)
537
- for (const [ad, renk] of kardesRenkleri(blok)) {
538
- expect({ kardes: ad, ayirt: deltaE(dolgu, renk) >= DELTAE_ESIK }).toEqual({
539
- kardes: ad,
542
+ const fill = rangeRengi('accent', blok)
543
+ for (const [name, color] of siblingColors(blok)) {
544
+ expect({ kardes: name, ayirt: deltaE(fill, color) >= DELTAE_THRESHOLD }).toEqual({
545
+ kardes: name,
540
546
  ayirt: true,
541
547
  })
542
548
  }