@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.
- package/dist/cdn/moonui.css +2 -2
- package/dist/cdn/moonui.esm.js +12 -12
- package/dist/cdn/moonui.global.js +12 -12
- package/dist/index.d.mts +24 -6
- package/dist/index.d.ts +24 -6
- package/dist/index.js +448 -93
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +449 -95
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/issue-544-kontrast.test.tsx +609 -0
- package/src/__tests__/issue-645-overlay-tab-focus.test.tsx +262 -0
- package/src/__tests__/issue-646-dark-scope-override.test.tsx +297 -0
- package/src/__tests__/issue-661-a11y.test.tsx +113 -0
- package/src/__tests__/issue-661-token-kontrast.test.tsx +82 -0
- package/src/components/ui/__tests__/accent-foreground-contrast.test.ts +197 -0
- package/src/components/ui/__tests__/accordion.test.tsx +6 -4
- package/src/components/ui/__tests__/alert.test.tsx +4 -1
- package/src/components/ui/__tests__/badge.test.tsx +5 -1
- package/src/components/ui/__tests__/button.test.tsx +6 -1
- package/src/components/ui/__tests__/dark-token-contrast.test.ts +18 -18
- package/src/components/ui/__tests__/heading-level-policy.test.tsx +4 -4
- package/src/components/ui/__tests__/issue-643-card-token-parite.test.tsx +113 -0
- package/src/components/ui/__tests__/issue-649-button-aschild.test.tsx +199 -0
- package/src/components/ui/__tests__/issue-664-separator-dekoratif-rol.test.tsx +41 -0
- package/src/components/ui/__tests__/preset-color-shadowing.test.ts +10 -10
- package/src/components/ui/__tests__/primary-subtle-token.test.ts +7 -7
- package/src/components/ui/__tests__/progress-secondary-contrast.test.ts +38 -38
- package/src/components/ui/__tests__/progress-warning-error-contrast.test.ts +64 -64
- package/src/components/ui/__tests__/quality-group-de.test.tsx +2 -2
- package/src/components/ui/__tests__/responsive-color-invariance.test.ts +20 -20
- package/src/components/ui/__tests__/select.test.tsx +6 -1
- package/src/components/ui/__tests__/semantic-token-usage.test.ts +23 -23
- package/src/components/ui/__tests__/size-scale-convention.test.ts +10 -10
- package/src/components/ui/__tests__/slider-accent-contrast.test.ts +142 -136
- package/src/components/ui/__tests__/slider-secondary-contrast.test.ts +60 -60
- package/src/components/ui/__tests__/slider-warning-error-contrast.test.ts +100 -100
- package/src/components/ui/__tests__/switch-variant-contrast.test.tsx +103 -103
- package/src/components/ui/__tests__/tooltip.test.tsx +122 -0
- package/src/components/ui/accordion.tsx +16 -3
- package/src/components/ui/alert.tsx +1 -1
- package/src/components/ui/badge.tsx +31 -4
- package/src/components/ui/button.tsx +88 -21
- package/src/components/ui/card.tsx +59 -5
- package/src/components/ui/dropdown-menu.tsx +99 -5
- package/src/components/ui/file-upload.tsx +4 -0
- package/src/components/ui/index.ts +4 -1
- package/src/components/ui/popover-pro.tsx +41 -0
- package/src/components/ui/popover.tsx +162 -7
- package/src/components/ui/rating.tsx +24 -2
- package/src/components/ui/select.tsx +4 -4
- package/src/components/ui/simple-editor.tsx +3 -18
- package/src/components/ui/tabs.tsx +16 -1
- package/src/components/ui/textarea.tsx +6 -1
- package/src/components/ui/toast.tsx +17 -2
- package/src/components/ui/tooltip.tsx +154 -2
- 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(
|
|
49
|
-
const
|
|
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
|
|
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
|
|
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 =
|
|
93
|
-
const lb =
|
|
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
|
-
|
|
130
|
+
background: [number, number, number]
|
|
131
131
|
): [number, number, number] {
|
|
132
132
|
return [
|
|
133
|
-
Math.round(alfa * ust[0] + (1 - alfa) *
|
|
134
|
-
Math.round(alfa * ust[1] + (1 - alfa) *
|
|
135
|
-
Math.round(alfa * ust[2] + (1 - alfa) *
|
|
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
|
|
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
|
|
147
|
-
return hslTokenRgb(tokenOku(
|
|
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
|
|
151
|
+
function backgrounds(blok: 'light' | 'dark'): Array<[string, [number, number, number]]> {
|
|
152
152
|
const z: Array<[string, [number, number, number]]> = [
|
|
153
|
-
['--background',
|
|
154
|
-
['--card',
|
|
153
|
+
['--background', semanticRgb('background', blok)],
|
|
154
|
+
['--card', semanticRgb('card', blok)],
|
|
155
155
|
]
|
|
156
|
-
if (blok === 'dark') z.push(['gerçek kart #111827',
|
|
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
|
|
165
|
-
const iTrack =
|
|
166
|
-
const iRange =
|
|
167
|
-
const iThumb =
|
|
168
|
-
const iSon =
|
|
169
|
-
const TRACK_BLOK =
|
|
170
|
-
const RANGE_BLOK =
|
|
171
|
-
const THUMB_BLOK =
|
|
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
|
|
175
|
-
const m = blok.match(new RegExp(`\\n\\s*${
|
|
176
|
-
if (!m) throw new Error(`${
|
|
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
|
|
182
|
-
const
|
|
183
|
-
if (
|
|
184
|
-
const
|
|
185
|
-
if (
|
|
186
|
-
throw new Error(`çözülemeyen sınıf: ${
|
|
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
|
|
194
|
+
function appliedColor(
|
|
195
195
|
cvaBlok: string,
|
|
196
|
-
|
|
196
|
+
variant: string,
|
|
197
197
|
onek: 'bg' | 'border',
|
|
198
198
|
blok: 'light' | 'dark'
|
|
199
199
|
): [number, number, number] {
|
|
200
|
-
const
|
|
200
|
+
const classNames = variantClass(cvaBlok, variant)
|
|
201
201
|
.split(/\s+/)
|
|
202
202
|
.filter((s) => s.replace(/^dark:/, '').startsWith(`${onek}-`))
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
if (!
|
|
206
|
-
return
|
|
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') =>
|
|
210
|
-
const
|
|
211
|
-
|
|
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(
|
|
215
|
-
const
|
|
216
|
-
const m =
|
|
217
|
-
if (!m) throw new Error(`track sınıfı çözülemedi: ${
|
|
218
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
250
|
-
|
|
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
|
|
265
|
+
const oldA = (blok: 'light' | 'dark') =>
|
|
266
266
|
trackler('accent', blok).map(([, t]) =>
|
|
267
|
-
Number(rgbKontrast(
|
|
267
|
+
Number(rgbKontrast(semanticRgb('accent', blok), t).toFixed(2))
|
|
268
268
|
)
|
|
269
|
-
expect(
|
|
270
|
-
expect(
|
|
271
|
-
expect(
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
Number(rgbKontrast(
|
|
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(
|
|
277
|
-
expect(
|
|
278
|
-
expect(
|
|
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
|
|
309
|
-
expect(rgbKontrast(
|
|
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İ —
|
|
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
|
-
|
|
319
|
-
|
|
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
|
|
333
|
+
const whites = [...presetler.matchAll(/accentForeground:\s*"([^"]+)"/g)].filter(
|
|
328
334
|
(m) => m[1] === '0 0% 100%'
|
|
329
335
|
)
|
|
330
|
-
expect(
|
|
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
|
|
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
|
-
|
|
347
|
+
backgrounds(blok).every(([, z]) => rgbKontrast(c, z) >= 3.0)
|
|
342
348
|
)
|
|
343
349
|
})
|
|
344
|
-
expect(
|
|
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
|
|
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
|
|
364
|
+
const threshold =
|
|
359
365
|
trackler('accent', 'dark').every(([, t]) => rgbKontrast(c, t) >= 3.0) &&
|
|
360
|
-
|
|
361
|
-
const ayirt =
|
|
362
|
-
return
|
|
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(
|
|
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
|
|
374
|
+
const score = (k: number) => {
|
|
369
375
|
const c = rgbTokenRgb(tokenOku(`secondary-${k}`, 'light')!)
|
|
370
|
-
return Math.min(...
|
|
376
|
+
return Math.min(...siblingColors('light').map(([, r]) => deltaE(c, r)))
|
|
371
377
|
}
|
|
372
|
-
const
|
|
378
|
+
const suitable = KADEMELER.filter((k) => {
|
|
373
379
|
const c = rgbTokenRgb(tokenOku(`secondary-${k}`, 'light')!)
|
|
374
|
-
const
|
|
380
|
+
const threshold =
|
|
375
381
|
trackler('accent', 'light').every(([, t]) => rgbKontrast(c, t) >= 3.0) &&
|
|
376
|
-
|
|
377
|
-
return
|
|
382
|
+
backgrounds('light').every(([, z]) => rgbKontrast(c, z) >= 3.0)
|
|
383
|
+
return threshold && score(k) >= DELTAE_THRESHOLD
|
|
378
384
|
})
|
|
379
|
-
expect(
|
|
380
|
-
expect(Math.max(...
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
405
|
-
expect(
|
|
406
|
-
expect(
|
|
407
|
-
expect(
|
|
408
|
-
expect(
|
|
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(
|
|
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
|
|
419
|
-
const
|
|
420
|
-
.readdirSync(
|
|
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(
|
|
428
|
+
.map((f) => fs.readFileSync(path.join(styleDir, f), 'utf8'))
|
|
423
429
|
.join('\n')
|
|
424
|
-
expect(
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 =
|
|
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 =
|
|
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
|
|
491
|
-
for (const [
|
|
492
|
-
const oran = rgbKontrast(
|
|
493
|
-
expect({
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
|
505
|
-
for (const [
|
|
506
|
-
const oran = rgbKontrast(
|
|
507
|
-
expect({
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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
|
|
525
|
+
const old = semanticRgb('accent', blok)
|
|
520
526
|
for (const [, track] of trackler('accent', blok)) {
|
|
521
|
-
expect(rgbKontrast(
|
|
527
|
+
expect(rgbKontrast(old, track)).toBeLessThan(3.0)
|
|
522
528
|
}
|
|
523
|
-
for (const [,
|
|
524
|
-
expect(rgbKontrast(
|
|
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(
|
|
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
|
|
537
|
-
for (const [
|
|
538
|
-
expect({ kardes:
|
|
539
|
-
kardes:
|
|
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
|
}
|