colbrush 1.4.0 → 1.6.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/cli.cjs +41 -97
- package/dist/client.cjs +7 -11
- package/dist/client.js +7 -11
- package/package.json +3 -2
package/dist/cli.cjs
CHANGED
|
@@ -75,16 +75,17 @@ var DEFAULT_SCALE = {
|
|
|
75
75
|
|
|
76
76
|
// src/core/utils/colorScale.ts
|
|
77
77
|
var CLAMP01 = (x) => Math.max(0, Math.min(1, x));
|
|
78
|
+
var Color = import_colorjs.default.default ?? import_colorjs.default;
|
|
78
79
|
function hexToOKLCH(hex) {
|
|
79
|
-
const c = new
|
|
80
|
+
const c = new Color(hex);
|
|
80
81
|
const o = c.to("oklch");
|
|
81
82
|
return { l: o.l, c: o.c, h: o.h ?? 0 };
|
|
82
83
|
}
|
|
83
84
|
function oklchToHex(l, c, h) {
|
|
84
|
-
const color = new
|
|
85
|
+
const color = new Color("oklch", [l, c, h]);
|
|
85
86
|
const srgb = color.to("srgb");
|
|
86
87
|
const r = CLAMP01(srgb.r), g = CLAMP01(srgb.g), b = CLAMP01(srgb.b);
|
|
87
|
-
return new
|
|
88
|
+
return new Color("srgb", [r, g, b]).toString({ format: "hex" });
|
|
88
89
|
}
|
|
89
90
|
function buildScaleFromBaseHex(baseHex, opts) {
|
|
90
91
|
const keys = opts?.keys ?? DEFAULT_KEYS;
|
|
@@ -190,72 +191,48 @@ function inferRich(varName, baseHex) {
|
|
|
190
191
|
|
|
191
192
|
// src/cli/colorTransform.ts
|
|
192
193
|
var import_chroma_js = __toESM(require("chroma-js"), 1);
|
|
193
|
-
var
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
return candidates;
|
|
209
|
-
}
|
|
210
|
-
function findOptimalColorCombination(colorKeys, baseColorsArray, candidateList, vision) {
|
|
211
|
-
const n = baseColorsArray.length;
|
|
212
|
-
let bestColors = null;
|
|
213
|
-
let minDeltaSum = Infinity;
|
|
214
|
-
let blind;
|
|
215
|
-
switch (vision) {
|
|
216
|
-
// 적색맹
|
|
217
|
-
case "protanopia":
|
|
218
|
-
blind = import_color_blind.default.protanopia;
|
|
219
|
-
break;
|
|
220
|
-
// 녹색맹
|
|
221
|
-
case "deuteranopia":
|
|
222
|
-
blind = import_color_blind.default.deuteranopia;
|
|
223
|
-
break;
|
|
224
|
-
// 청색맹
|
|
225
|
-
case "tritanopia":
|
|
226
|
-
blind = import_color_blind.default.tritanopia;
|
|
227
|
-
break;
|
|
228
|
-
default:
|
|
229
|
-
throw new Error("Invalid color blindness option");
|
|
230
|
-
}
|
|
231
|
-
function dfs(index, currentColors, totalDelta) {
|
|
232
|
-
if (index === n) {
|
|
233
|
-
if (isValidColors(currentColors, blind)) {
|
|
234
|
-
if (totalDelta < minDeltaSum) {
|
|
235
|
-
minDeltaSum = totalDelta;
|
|
236
|
-
bestColors = [...currentColors];
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return;
|
|
194
|
+
var ALPHA = 1.5;
|
|
195
|
+
function colorTranslate(colorKeys, baseColorsArray, vision) {
|
|
196
|
+
const labArray = baseColorsArray.map((hex) => (0, import_chroma_js.default)(hex).lab());
|
|
197
|
+
const newLabArray = labArray.map((lab) => {
|
|
198
|
+
const newLab = [...lab];
|
|
199
|
+
switch (vision) {
|
|
200
|
+
case "protanopia":
|
|
201
|
+
newLab[2] += ALPHA * lab[1];
|
|
202
|
+
break;
|
|
203
|
+
case "deuteranopia":
|
|
204
|
+
newLab[2] += ALPHA * lab[1];
|
|
205
|
+
break;
|
|
206
|
+
case "tritanopia":
|
|
207
|
+
newLab[1] += ALPHA * 0.2 * lab[2];
|
|
208
|
+
break;
|
|
240
209
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
currentColors.push(candidate);
|
|
245
|
-
dfs(index + 1, currentColors, totalDelta + delta);
|
|
246
|
-
currentColors.pop();
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
dfs(0, [], 0);
|
|
250
|
-
const finalColors = bestColors ?? [...baseColorsArray];
|
|
210
|
+
return newLab;
|
|
211
|
+
});
|
|
212
|
+
const newColorArray = newLabArray.map((lab) => import_chroma_js.default.lab(lab[0], lab[1], lab[2]).hex());
|
|
251
213
|
return colorKeys.reduce(
|
|
252
214
|
(acc, key, idx) => {
|
|
253
|
-
acc[key] =
|
|
215
|
+
acc[key] = newColorArray[idx];
|
|
254
216
|
return acc;
|
|
255
217
|
},
|
|
256
218
|
{}
|
|
257
219
|
);
|
|
258
220
|
}
|
|
221
|
+
function buildThemeForVision(colorKeys, baseColorsArray, vision) {
|
|
222
|
+
const colors = colorTranslate(
|
|
223
|
+
colorKeys,
|
|
224
|
+
baseColorsArray,
|
|
225
|
+
vision
|
|
226
|
+
);
|
|
227
|
+
const variables = Object.entries(colors).reduce(
|
|
228
|
+
(acc, [varName, baseHex]) => {
|
|
229
|
+
acc[varName] = inferRich(varName, baseHex);
|
|
230
|
+
return acc;
|
|
231
|
+
},
|
|
232
|
+
{}
|
|
233
|
+
);
|
|
234
|
+
return { vision, variables };
|
|
235
|
+
}
|
|
259
236
|
function prepareCandidates(variables, progress) {
|
|
260
237
|
const scaleGroups = {};
|
|
261
238
|
const filteredVariables = {};
|
|
@@ -282,30 +259,7 @@ function prepareCandidates(variables, progress) {
|
|
|
282
259
|
}
|
|
283
260
|
const colorKeys = Object.keys(filteredVariables);
|
|
284
261
|
const baseColorsArray = Object.values(filteredVariables).map((v) => v.base);
|
|
285
|
-
|
|
286
|
-
const candidateList = [];
|
|
287
|
-
for (let i = 0; i < baseColorsArray.length; i++) {
|
|
288
|
-
candidateList[i] = generateCandidates(baseColorsArray[i]);
|
|
289
|
-
progress?.update((i + 1) / baseColorsArray.length * 100);
|
|
290
|
-
}
|
|
291
|
-
progress?.finishSection("Done");
|
|
292
|
-
return { colorKeys, baseColorsArray, candidateList };
|
|
293
|
-
}
|
|
294
|
-
function buildThemeForVision(colorKeys, baseColorsArray, candidateList, vision) {
|
|
295
|
-
const colors = findOptimalColorCombination(
|
|
296
|
-
colorKeys,
|
|
297
|
-
baseColorsArray,
|
|
298
|
-
candidateList,
|
|
299
|
-
vision
|
|
300
|
-
);
|
|
301
|
-
const variables = Object.entries(colors).reduce(
|
|
302
|
-
(acc, [varName, baseHex]) => {
|
|
303
|
-
acc[varName] = inferRich(varName, baseHex);
|
|
304
|
-
return acc;
|
|
305
|
-
},
|
|
306
|
-
{}
|
|
307
|
-
);
|
|
308
|
-
return { vision, variables };
|
|
262
|
+
return { colorKeys, baseColorsArray };
|
|
309
263
|
}
|
|
310
264
|
function getMiddleScaleKey(keys) {
|
|
311
265
|
const scaleNumbers = keys.map((k) => {
|
|
@@ -319,15 +273,6 @@ function getMiddleScaleKey(keys) {
|
|
|
319
273
|
const middleKey = keys.find((k) => k.endsWith(midNumber.toString())) || null;
|
|
320
274
|
return middleKey;
|
|
321
275
|
}
|
|
322
|
-
function isValidColors(colors, blind) {
|
|
323
|
-
for (let i = 0; i < colors.length; i++) {
|
|
324
|
-
for (let j = i + 1; j < colors.length; j++) {
|
|
325
|
-
if (import_chroma_js.default.deltaE(blind(colors[i]), blind(colors[j])) < THRESHOLD)
|
|
326
|
-
return false;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
return true;
|
|
330
|
-
}
|
|
331
276
|
|
|
332
277
|
// src/cli/runThemeApply.ts
|
|
333
278
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
@@ -338,7 +283,7 @@ function removeExistingThemeBlocks(content) {
|
|
|
338
283
|
let cleaned = content;
|
|
339
284
|
for (const vision of visions) {
|
|
340
285
|
const pattern = new RegExp(
|
|
341
|
-
|
|
286
|
+
`\\[data-theme=['"]${vision}['"]\\][^}]*}\\s*`,
|
|
342
287
|
"gm"
|
|
343
288
|
);
|
|
344
289
|
cleaned = cleaned.replace(pattern, "");
|
|
@@ -418,7 +363,7 @@ async function runThemeApply(cssPath, progress) {
|
|
|
418
363
|
progress?.finishSection("Done");
|
|
419
364
|
const visions = ["deuteranopia", "protanopia", "tritanopia"];
|
|
420
365
|
try {
|
|
421
|
-
const { colorKeys, baseColorsArray
|
|
366
|
+
const { colorKeys, baseColorsArray } = prepareCandidates(
|
|
422
367
|
variables,
|
|
423
368
|
progress
|
|
424
369
|
);
|
|
@@ -429,7 +374,6 @@ async function runThemeApply(cssPath, progress) {
|
|
|
429
374
|
const themeData = buildThemeForVision(
|
|
430
375
|
colorKeys,
|
|
431
376
|
baseColorsArray,
|
|
432
|
-
candidateList,
|
|
433
377
|
vision
|
|
434
378
|
);
|
|
435
379
|
progress?.update(70, "Applying CSS...");
|
package/dist/client.cjs
CHANGED
|
@@ -118,9 +118,8 @@ var SvgLogo = (props) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
|
118
118
|
"svg",
|
|
119
119
|
{
|
|
120
120
|
xmlns: "http://www.w3.org/2000/svg",
|
|
121
|
-
width: 39,
|
|
122
|
-
height: 40,
|
|
123
121
|
fill: "none",
|
|
122
|
+
viewBox: "0 0 39 40",
|
|
124
123
|
...props,
|
|
125
124
|
children: [
|
|
126
125
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { fill: "#CE78A9", d: "m.5 17.805 1.516-3.514 16.812-7.924-1.309 3.308z" }),
|
|
@@ -184,9 +183,8 @@ var SvgUs = (props) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
|
184
183
|
"svg",
|
|
185
184
|
{
|
|
186
185
|
xmlns: "http://www.w3.org/2000/svg",
|
|
187
|
-
width: 28,
|
|
188
|
-
height: 21,
|
|
189
186
|
fill: "none",
|
|
187
|
+
viewBox: "0 0 28 21",
|
|
190
188
|
...props,
|
|
191
189
|
children: [
|
|
192
190
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("g", { clipPath: "url(#a)", filter: "url(#b)", children: [
|
|
@@ -263,9 +261,8 @@ var SvgKr = (props) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
|
263
261
|
"svg",
|
|
264
262
|
{
|
|
265
263
|
xmlns: "http://www.w3.org/2000/svg",
|
|
266
|
-
width: 24,
|
|
267
|
-
height: 17,
|
|
268
264
|
fill: "none",
|
|
265
|
+
viewBox: "0 0 24 17",
|
|
269
266
|
...props,
|
|
270
267
|
children: [
|
|
271
268
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { clipPath: "url(#a)", filter: "url(#b)", children: [
|
|
@@ -384,9 +381,8 @@ var SvgX = (props) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
|
384
381
|
"svg",
|
|
385
382
|
{
|
|
386
383
|
xmlns: "http://www.w3.org/2000/svg",
|
|
387
|
-
width: 30,
|
|
388
|
-
height: 30,
|
|
389
384
|
fill: "none",
|
|
385
|
+
viewBox: "0 0 30 30",
|
|
390
386
|
...props,
|
|
391
387
|
children: [
|
|
392
388
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
@@ -648,7 +644,7 @@ function ThemeSwitcher({ options }) {
|
|
|
648
644
|
{
|
|
649
645
|
role: "menu",
|
|
650
646
|
"aria-label": "Select theme",
|
|
651
|
-
className: "fixed bottom-[100px] right-[25px] flex-col bg-[#ffffff] rounded-[18px] w-[220px] gap-[11px]",
|
|
647
|
+
className: "fixed bottom-[100px] right-[25px] flex-col bg-[#ffffff] rounded-[18px] w-[220px] gap-[11px] filter drop-shadow-[0_0_1.3px_rgba(0,0,0,0.25)]",
|
|
652
648
|
children: [
|
|
653
649
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { children: list.map((opt) => {
|
|
654
650
|
const Icon = THEME_ICON[opt.key];
|
|
@@ -693,7 +689,7 @@ function ThemeSwitcher({ options }) {
|
|
|
693
689
|
className: `hover:cursor-pointer flex text-[18px] text-[#3D4852] gap-[8px] items-center justify-center`,
|
|
694
690
|
onClick: () => updateLanguage("Korean"),
|
|
695
691
|
children: [
|
|
696
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Us_default, {}),
|
|
692
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Us_default, { width: 20 }),
|
|
697
693
|
"English"
|
|
698
694
|
]
|
|
699
695
|
}
|
|
@@ -703,7 +699,7 @@ function ThemeSwitcher({ options }) {
|
|
|
703
699
|
className: `hover:cursor-pointer flex text-[18px] text-[#3D4852] gap-[8px] items-center justify-center `,
|
|
704
700
|
onClick: () => updateLanguage("English"),
|
|
705
701
|
children: [
|
|
706
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Kr_default, {}),
|
|
702
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Kr_default, { width: 20 }),
|
|
707
703
|
"Korean"
|
|
708
704
|
]
|
|
709
705
|
}
|
package/dist/client.js
CHANGED
|
@@ -96,9 +96,8 @@ var SvgLogo = (props) => /* @__PURE__ */ jsxs(
|
|
|
96
96
|
"svg",
|
|
97
97
|
{
|
|
98
98
|
xmlns: "http://www.w3.org/2000/svg",
|
|
99
|
-
width: 39,
|
|
100
|
-
height: 40,
|
|
101
99
|
fill: "none",
|
|
100
|
+
viewBox: "0 0 39 40",
|
|
102
101
|
...props,
|
|
103
102
|
children: [
|
|
104
103
|
/* @__PURE__ */ jsx2("path", { fill: "#CE78A9", d: "m.5 17.805 1.516-3.514 16.812-7.924-1.309 3.308z" }),
|
|
@@ -162,9 +161,8 @@ var SvgUs = (props) => /* @__PURE__ */ jsxs2(
|
|
|
162
161
|
"svg",
|
|
163
162
|
{
|
|
164
163
|
xmlns: "http://www.w3.org/2000/svg",
|
|
165
|
-
width: 28,
|
|
166
|
-
height: 21,
|
|
167
164
|
fill: "none",
|
|
165
|
+
viewBox: "0 0 28 21",
|
|
168
166
|
...props,
|
|
169
167
|
children: [
|
|
170
168
|
/* @__PURE__ */ jsxs2("g", { clipPath: "url(#a)", filter: "url(#b)", children: [
|
|
@@ -241,9 +239,8 @@ var SvgKr = (props) => /* @__PURE__ */ jsxs3(
|
|
|
241
239
|
"svg",
|
|
242
240
|
{
|
|
243
241
|
xmlns: "http://www.w3.org/2000/svg",
|
|
244
|
-
width: 24,
|
|
245
|
-
height: 17,
|
|
246
242
|
fill: "none",
|
|
243
|
+
viewBox: "0 0 24 17",
|
|
247
244
|
...props,
|
|
248
245
|
children: [
|
|
249
246
|
/* @__PURE__ */ jsxs3("g", { clipPath: "url(#a)", filter: "url(#b)", children: [
|
|
@@ -362,9 +359,8 @@ var SvgX = (props) => /* @__PURE__ */ jsxs4(
|
|
|
362
359
|
"svg",
|
|
363
360
|
{
|
|
364
361
|
xmlns: "http://www.w3.org/2000/svg",
|
|
365
|
-
width: 30,
|
|
366
|
-
height: 30,
|
|
367
362
|
fill: "none",
|
|
363
|
+
viewBox: "0 0 30 30",
|
|
368
364
|
...props,
|
|
369
365
|
children: [
|
|
370
366
|
/* @__PURE__ */ jsx5(
|
|
@@ -626,7 +622,7 @@ function ThemeSwitcher({ options }) {
|
|
|
626
622
|
{
|
|
627
623
|
role: "menu",
|
|
628
624
|
"aria-label": "Select theme",
|
|
629
|
-
className: "fixed bottom-[100px] right-[25px] flex-col bg-[#ffffff] rounded-[18px] w-[220px] gap-[11px]",
|
|
625
|
+
className: "fixed bottom-[100px] right-[25px] flex-col bg-[#ffffff] rounded-[18px] w-[220px] gap-[11px] filter drop-shadow-[0_0_1.3px_rgba(0,0,0,0.25)]",
|
|
630
626
|
children: [
|
|
631
627
|
/* @__PURE__ */ jsx10("div", { children: list.map((opt) => {
|
|
632
628
|
const Icon = THEME_ICON[opt.key];
|
|
@@ -671,7 +667,7 @@ function ThemeSwitcher({ options }) {
|
|
|
671
667
|
className: `hover:cursor-pointer flex text-[18px] text-[#3D4852] gap-[8px] items-center justify-center`,
|
|
672
668
|
onClick: () => updateLanguage("Korean"),
|
|
673
669
|
children: [
|
|
674
|
-
/* @__PURE__ */ jsx10(Us_default, {}),
|
|
670
|
+
/* @__PURE__ */ jsx10(Us_default, { width: 20 }),
|
|
675
671
|
"English"
|
|
676
672
|
]
|
|
677
673
|
}
|
|
@@ -681,7 +677,7 @@ function ThemeSwitcher({ options }) {
|
|
|
681
677
|
className: `hover:cursor-pointer flex text-[18px] text-[#3D4852] gap-[8px] items-center justify-center `,
|
|
682
678
|
onClick: () => updateLanguage("English"),
|
|
683
679
|
children: [
|
|
684
|
-
/* @__PURE__ */ jsx10(Kr_default, {}),
|
|
680
|
+
/* @__PURE__ */ jsx10(Kr_default, { width: 20 }),
|
|
685
681
|
"Korean"
|
|
686
682
|
]
|
|
687
683
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "colbrush",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "A React theme switching library that makes it easy to apply color-blind accessible UI themes",
|
|
5
5
|
"homepage": "https://colbrush.vercel.app",
|
|
6
6
|
"repository": {
|
|
@@ -72,8 +72,9 @@
|
|
|
72
72
|
"typescript": "^5.8.3"
|
|
73
73
|
},
|
|
74
74
|
"scripts": {
|
|
75
|
-
"svgr": "svgr src/assets --out-dir src/icons --
|
|
75
|
+
"svgr": "svgr src/assets --out-dir src/icons --config-file svgr.config.cjs",
|
|
76
76
|
"build:css": "tailwindcss -i ./src/styles.css -o ./dist/styles.css --minify",
|
|
77
|
+
"build:icons": "svgr src/assets --out-dir src/icons",
|
|
77
78
|
"build": "pnpm svgr && tsup --config tsup.config.ts && pnpm build:css",
|
|
78
79
|
"dev": "tsup --config tsup.config.ts --watch",
|
|
79
80
|
"lint": "eslint ."
|