colbrush 1.5.0 → 1.7.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/chunk-EFIJAMPH.js +104 -0
- package/dist/cli.cjs +41 -97
- package/dist/client.cjs +13 -2
- package/dist/client.d.cts +5 -1
- package/dist/client.d.ts +5 -1
- package/dist/client.js +84 -165
- package/dist/devtools.cjs +296 -0
- package/dist/devtools.d.cts +26 -0
- package/dist/devtools.d.ts +26 -0
- package/dist/devtools.js +260 -0
- package/package.json +12 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// src/react/ThemeProvider.tsx
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useState
|
|
8
|
+
} from "react";
|
|
9
|
+
import { jsx } from "react/jsx-runtime";
|
|
10
|
+
var THEME_KEYS = [
|
|
11
|
+
"default",
|
|
12
|
+
"protanopia",
|
|
13
|
+
"deuteranopia",
|
|
14
|
+
"tritanopia"
|
|
15
|
+
];
|
|
16
|
+
var THEME_LABEL = {
|
|
17
|
+
English: {
|
|
18
|
+
default: "default",
|
|
19
|
+
protanopia: "protanopia",
|
|
20
|
+
deuteranopia: "deuteranopia",
|
|
21
|
+
tritanopia: "tritanopia"
|
|
22
|
+
},
|
|
23
|
+
Korean: {
|
|
24
|
+
default: "\uAE30\uBCF8",
|
|
25
|
+
protanopia: "\uC801\uC0C9\uB9F9",
|
|
26
|
+
deuteranopia: "\uB179\uC0C9\uB9F9",
|
|
27
|
+
tritanopia: "\uCCAD\uC0C9\uB9F9"
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var getThemeOptions = (lang) => THEME_KEYS.map((key) => ({ key, label: THEME_LABEL[lang][key] }));
|
|
31
|
+
var KEY = "theme";
|
|
32
|
+
var LANG_KEY = "theme_lang";
|
|
33
|
+
var ThemeContext = createContext({
|
|
34
|
+
theme: "default",
|
|
35
|
+
language: "English",
|
|
36
|
+
updateTheme: () => {
|
|
37
|
+
},
|
|
38
|
+
updateLanguage: () => {
|
|
39
|
+
},
|
|
40
|
+
simulationFilter: "none",
|
|
41
|
+
setSimulationFilter: () => {
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
var useTheme = () => useContext(ThemeContext);
|
|
45
|
+
function normalizeToKey(value) {
|
|
46
|
+
if (!value) return "default";
|
|
47
|
+
if (THEME_KEYS.includes(value))
|
|
48
|
+
return value;
|
|
49
|
+
const reverse = {};
|
|
50
|
+
["English", "Korean"].forEach((lang) => {
|
|
51
|
+
Object.entries(THEME_LABEL[lang]).forEach(
|
|
52
|
+
([k, label]) => {
|
|
53
|
+
reverse[label] = k;
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
return reverse[value] ?? "default";
|
|
58
|
+
}
|
|
59
|
+
function ThemeProvider({ children }) {
|
|
60
|
+
const [theme, setTheme] = useState("default");
|
|
61
|
+
const [simulationFilter, setSimulationFilter] = useState("none");
|
|
62
|
+
const [language, setLanguage] = useState("English");
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (typeof window === "undefined") return;
|
|
65
|
+
const storedTheme = normalizeToKey(localStorage.getItem(KEY));
|
|
66
|
+
const storedLang = localStorage.getItem(LANG_KEY) || "English";
|
|
67
|
+
setTheme(storedTheme);
|
|
68
|
+
setLanguage(storedLang);
|
|
69
|
+
document.documentElement.setAttribute("data-theme", storedTheme);
|
|
70
|
+
}, []);
|
|
71
|
+
const updateTheme = (k) => {
|
|
72
|
+
setTheme(k);
|
|
73
|
+
if (typeof window !== "undefined") {
|
|
74
|
+
localStorage.setItem(KEY, k);
|
|
75
|
+
document.documentElement.setAttribute("data-theme", k);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const updateLanguage = (t) => {
|
|
79
|
+
setLanguage(t);
|
|
80
|
+
if (typeof window !== "undefined") {
|
|
81
|
+
localStorage.setItem(LANG_KEY, t);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
const value = useMemo(
|
|
85
|
+
() => ({
|
|
86
|
+
theme,
|
|
87
|
+
language,
|
|
88
|
+
updateTheme,
|
|
89
|
+
updateLanguage,
|
|
90
|
+
simulationFilter,
|
|
91
|
+
setSimulationFilter
|
|
92
|
+
}),
|
|
93
|
+
[theme, language, simulationFilter]
|
|
94
|
+
);
|
|
95
|
+
return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
|
|
96
|
+
}
|
|
97
|
+
var THEMES = THEME_LABEL;
|
|
98
|
+
|
|
99
|
+
export {
|
|
100
|
+
getThemeOptions,
|
|
101
|
+
useTheme,
|
|
102
|
+
ThemeProvider,
|
|
103
|
+
THEMES
|
|
104
|
+
};
|
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
|
@@ -60,6 +60,9 @@ var ThemeContext = (0, import_react.createContext)({
|
|
|
60
60
|
updateTheme: () => {
|
|
61
61
|
},
|
|
62
62
|
updateLanguage: () => {
|
|
63
|
+
},
|
|
64
|
+
simulationFilter: "none",
|
|
65
|
+
setSimulationFilter: () => {
|
|
63
66
|
}
|
|
64
67
|
});
|
|
65
68
|
var useTheme = () => (0, import_react.useContext)(ThemeContext);
|
|
@@ -79,6 +82,7 @@ function normalizeToKey(value) {
|
|
|
79
82
|
}
|
|
80
83
|
function ThemeProvider({ children }) {
|
|
81
84
|
const [theme, setTheme] = (0, import_react.useState)("default");
|
|
85
|
+
const [simulationFilter, setSimulationFilter] = (0, import_react.useState)("none");
|
|
82
86
|
const [language, setLanguage] = (0, import_react.useState)("English");
|
|
83
87
|
(0, import_react.useEffect)(() => {
|
|
84
88
|
if (typeof window === "undefined") return;
|
|
@@ -102,8 +106,15 @@ function ThemeProvider({ children }) {
|
|
|
102
106
|
}
|
|
103
107
|
};
|
|
104
108
|
const value = (0, import_react.useMemo)(
|
|
105
|
-
() => ({
|
|
106
|
-
|
|
109
|
+
() => ({
|
|
110
|
+
theme,
|
|
111
|
+
language,
|
|
112
|
+
updateTheme,
|
|
113
|
+
updateLanguage,
|
|
114
|
+
simulationFilter,
|
|
115
|
+
setSimulationFilter
|
|
116
|
+
}),
|
|
117
|
+
[theme, language, simulationFilter]
|
|
107
118
|
);
|
|
108
119
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThemeContext.Provider, { value, children });
|
|
109
120
|
}
|
package/dist/client.d.cts
CHANGED
|
@@ -3,12 +3,16 @@ import { ReactNode } from 'react';
|
|
|
3
3
|
|
|
4
4
|
type TLanguage = 'English' | 'Korean';
|
|
5
5
|
declare const THEME_KEYS: readonly ["default", "protanopia", "deuteranopia", "tritanopia"];
|
|
6
|
+
declare const SIMULATION_KEYS: readonly ["none", "deuteranopia", "protanopia", "tritanopia"];
|
|
6
7
|
type ThemeKey = (typeof THEME_KEYS)[number];
|
|
8
|
+
type SimulationKey = (typeof SIMULATION_KEYS)[number];
|
|
7
9
|
type ThemeContextType = {
|
|
8
10
|
theme: ThemeKey;
|
|
9
11
|
language: TLanguage;
|
|
10
12
|
updateTheme: (k: ThemeKey) => void;
|
|
11
13
|
updateLanguage: (t: TLanguage) => void;
|
|
14
|
+
simulationFilter: SimulationKey;
|
|
15
|
+
setSimulationFilter: (value: SimulationKey) => void;
|
|
12
16
|
};
|
|
13
17
|
declare const useTheme: () => ThemeContextType;
|
|
14
18
|
type ThemeProviderProps = {
|
|
@@ -16,7 +20,7 @@ type ThemeProviderProps = {
|
|
|
16
20
|
};
|
|
17
21
|
declare function ThemeProvider({ children }: ThemeProviderProps): react_jsx_runtime.JSX.Element;
|
|
18
22
|
type ThemeType = ThemeKey;
|
|
19
|
-
declare const THEMES: Record<TLanguage, Record<"
|
|
23
|
+
declare const THEMES: Record<TLanguage, Record<"deuteranopia" | "protanopia" | "tritanopia" | "default", string>>;
|
|
20
24
|
|
|
21
25
|
type Props = {
|
|
22
26
|
options?: {
|
package/dist/client.d.ts
CHANGED
|
@@ -3,12 +3,16 @@ import { ReactNode } from 'react';
|
|
|
3
3
|
|
|
4
4
|
type TLanguage = 'English' | 'Korean';
|
|
5
5
|
declare const THEME_KEYS: readonly ["default", "protanopia", "deuteranopia", "tritanopia"];
|
|
6
|
+
declare const SIMULATION_KEYS: readonly ["none", "deuteranopia", "protanopia", "tritanopia"];
|
|
6
7
|
type ThemeKey = (typeof THEME_KEYS)[number];
|
|
8
|
+
type SimulationKey = (typeof SIMULATION_KEYS)[number];
|
|
7
9
|
type ThemeContextType = {
|
|
8
10
|
theme: ThemeKey;
|
|
9
11
|
language: TLanguage;
|
|
10
12
|
updateTheme: (k: ThemeKey) => void;
|
|
11
13
|
updateLanguage: (t: TLanguage) => void;
|
|
14
|
+
simulationFilter: SimulationKey;
|
|
15
|
+
setSimulationFilter: (value: SimulationKey) => void;
|
|
12
16
|
};
|
|
13
17
|
declare const useTheme: () => ThemeContextType;
|
|
14
18
|
type ThemeProviderProps = {
|
|
@@ -16,7 +20,7 @@ type ThemeProviderProps = {
|
|
|
16
20
|
};
|
|
17
21
|
declare function ThemeProvider({ children }: ThemeProviderProps): react_jsx_runtime.JSX.Element;
|
|
18
22
|
type ThemeType = ThemeKey;
|
|
19
|
-
declare const THEMES: Record<TLanguage, Record<"
|
|
23
|
+
declare const THEMES: Record<TLanguage, Record<"deuteranopia" | "protanopia" | "tritanopia" | "default", string>>;
|
|
20
24
|
|
|
21
25
|
type Props = {
|
|
22
26
|
options?: {
|