colbrush 1.7.0 → 1.9.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/LICENSE +21 -0
- package/README.md +45 -5
- package/dist/chunk-WQEJ7FU2.js +168 -0
- package/dist/client.cjs +58 -18
- package/dist/client.d.cts +7 -7
- package/dist/client.d.ts +7 -7
- package/dist/client.js +20 -7
- package/dist/devtools.cjs +46 -27
- package/dist/devtools.d.cts +1 -18
- package/dist/devtools.d.ts +1 -18
- package/dist/devtools.js +31 -49
- package/dist/simulationTypes-CenFTH7t.d.cts +21 -0
- package/dist/simulationTypes-CenFTH7t.d.ts +21 -0
- package/dist/styles.css +1 -1
- package/package.json +1 -2
- package/dist/chunk-EFIJAMPH.js +0 -104
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 colbrush
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ npm install colbrush
|
|
|
29
29
|
---
|
|
30
30
|
## Usage
|
|
31
31
|
### 1. Define CSS variables (index.css or global CSS)
|
|
32
|
-
```
|
|
32
|
+
```css
|
|
33
33
|
@theme {
|
|
34
34
|
--color-primary-500: #7fe4c1;
|
|
35
35
|
--color-secondary-yellow: #fdfa91;
|
|
@@ -102,6 +102,31 @@ export default function TestPage() {
|
|
|
102
102
|
);
|
|
103
103
|
}
|
|
104
104
|
```
|
|
105
|
+
### 6. Apply SimulationFilter for vision simulation
|
|
106
|
+
```
|
|
107
|
+
import { SimulationFilter } from 'colbrush/devtools';
|
|
108
|
+
|
|
109
|
+
function App() {
|
|
110
|
+
return (
|
|
111
|
+
<ThemeProvider>
|
|
112
|
+
<SimulationFilter
|
|
113
|
+
initialMode="normal"
|
|
114
|
+
toolbarPosition="left-bottom"
|
|
115
|
+
...>
|
|
116
|
+
<YourApp />
|
|
117
|
+
</SimulationFilter>
|
|
118
|
+
</ThemeProvider>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
| **SimulationFilterProp** | **Type** | **Default** | **Description** |
|
|
123
|
+
| ----------------- | ----------------------------------------------------------------- | ------------- | ------------------------- |
|
|
124
|
+
| `initialMode?` | `"normal"` / `"protanopia"` / `"deuteranopia"` / `"tritanopia"` | `"normal"` | initial simulation mode |
|
|
125
|
+
| `toolbarPosition?` | `"top-left"` / `"top-right"` / `"bottom-left"` / `"bottom-right"` | `"top-right"` | toolbar position |
|
|
126
|
+
| `shortcut?` | `boolean` | `true` | enable keyboard shortcuts (⌘/Ctrl + Alt + D) |
|
|
127
|
+
| `productionGuard?` | `boolean` | `false` | block usage in production |
|
|
128
|
+
|
|
129
|
+
|
|
105
130
|
## Supported Vision Types
|
|
106
131
|
| **Vision Type** | **설명** |
|
|
107
132
|
| --------------- | ------ |
|
|
@@ -119,12 +144,27 @@ export default function TestPage() {
|
|
|
119
144
|
|
|
120
145
|
# 📜 License
|
|
121
146
|
|
|
122
|
-
|
|
147
|
+
MIT License
|
|
148
|
+
|
|
149
|
+
Copyright (c) 2025 colbrush
|
|
150
|
+
|
|
151
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
152
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
153
|
+
in the Software without restriction, including without limitation the rights
|
|
154
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
155
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
156
|
+
furnished to do so, subject to the following conditions:
|
|
123
157
|
|
|
124
|
-
|
|
158
|
+
The above copyright notice and this permission notice shall be included in all
|
|
159
|
+
copies or substantial portions of the Software.
|
|
125
160
|
|
|
126
|
-
|
|
161
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
162
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
163
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
164
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
165
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
166
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
167
|
+
SOFTWARE.
|
|
127
168
|
|
|
128
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
129
169
|
|
|
130
170
|
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// src/react/ThemeProvider.tsx
|
|
2
|
+
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
|
3
|
+
|
|
4
|
+
// src/core/constants/modes.ts
|
|
5
|
+
var SIMULATION_MODES = [
|
|
6
|
+
"deuteranopia",
|
|
7
|
+
"protanopia",
|
|
8
|
+
"tritanopia"
|
|
9
|
+
];
|
|
10
|
+
var THEME_MODES = [
|
|
11
|
+
"default",
|
|
12
|
+
...SIMULATION_MODES
|
|
13
|
+
];
|
|
14
|
+
var VISION_MODES = ["none", ...SIMULATION_MODES];
|
|
15
|
+
var MODE_LABELS = {
|
|
16
|
+
English: {
|
|
17
|
+
none: "default",
|
|
18
|
+
protanopia: "protanopia",
|
|
19
|
+
deuteranopia: "deuteranopia",
|
|
20
|
+
tritanopia: "tritanopia"
|
|
21
|
+
},
|
|
22
|
+
Korean: {
|
|
23
|
+
none: "\uAEBC\uC9D0",
|
|
24
|
+
protanopia: "\uC801\uC0C9\uB9F9",
|
|
25
|
+
deuteranopia: "\uB179\uC0C9\uB9F9",
|
|
26
|
+
tritanopia: "\uCCAD\uC0C9\uB9F9"
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var THEME_LABEL = {
|
|
30
|
+
English: {
|
|
31
|
+
default: "default",
|
|
32
|
+
protanopia: "protanopia",
|
|
33
|
+
deuteranopia: "deuteranopia",
|
|
34
|
+
tritanopia: "tritanopia"
|
|
35
|
+
},
|
|
36
|
+
Korean: {
|
|
37
|
+
default: "\uAE30\uBCF8",
|
|
38
|
+
protanopia: "\uC801\uC0C9\uB9F9",
|
|
39
|
+
deuteranopia: "\uB179\uC0C9\uB9F9",
|
|
40
|
+
tritanopia: "\uCCAD\uC0C9\uB9F9"
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// src/core/constants/key.ts
|
|
45
|
+
var ThemeStorageKey = "colbrush-theme";
|
|
46
|
+
var LanguageStorageKey = "colbrush-language";
|
|
47
|
+
var SimulationStorageKey = "colbrush-filter";
|
|
48
|
+
var VISION_PORTAL_ID = "cb-vision-portal";
|
|
49
|
+
var FILTER_ID = "cb-vision-filter";
|
|
50
|
+
var FILTER_WRAPPER_ID = "cb-vision-filter-root";
|
|
51
|
+
var THEME_SWITCHER_PORTAL_ID = "theme-switcher-portal";
|
|
52
|
+
|
|
53
|
+
// src/react/ThemeProvider.tsx
|
|
54
|
+
import { jsx } from "react/jsx-runtime";
|
|
55
|
+
var getThemeOptions = (lang) => THEME_MODES.map((key) => ({ key, label: THEME_LABEL[lang][key] }));
|
|
56
|
+
var ThemeContext = createContext({
|
|
57
|
+
theme: "default",
|
|
58
|
+
language: "English",
|
|
59
|
+
updateTheme: () => {
|
|
60
|
+
},
|
|
61
|
+
updateLanguage: () => {
|
|
62
|
+
},
|
|
63
|
+
simulationFilter: "none",
|
|
64
|
+
setSimulationFilter: () => {
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
var useTheme = () => useContext(ThemeContext);
|
|
68
|
+
function normalizeToKey(value) {
|
|
69
|
+
if (!value) return "default";
|
|
70
|
+
if (THEME_MODES.includes(value))
|
|
71
|
+
return value;
|
|
72
|
+
const reverse = {};
|
|
73
|
+
["English", "Korean"].forEach((lang) => {
|
|
74
|
+
Object.entries(THEME_LABEL[lang]).forEach(
|
|
75
|
+
([k, label]) => {
|
|
76
|
+
reverse[label] = k;
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
return reverse[value] ?? "default";
|
|
81
|
+
}
|
|
82
|
+
function ThemeProvider({ children }) {
|
|
83
|
+
const [theme, setTheme] = useState("default");
|
|
84
|
+
const [simulationFilter, setSimulationFilter] = useState("none");
|
|
85
|
+
const [language, setLanguage] = useState("English");
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (typeof window === "undefined") return;
|
|
88
|
+
const storedTheme = normalizeToKey(
|
|
89
|
+
localStorage.getItem(ThemeStorageKey)
|
|
90
|
+
);
|
|
91
|
+
const storedLang = localStorage.getItem(LanguageStorageKey) || "English";
|
|
92
|
+
const storedFilter = localStorage.getItem(SimulationStorageKey) || "none";
|
|
93
|
+
setSimulationFilter(storedFilter);
|
|
94
|
+
setTheme(storedTheme);
|
|
95
|
+
setLanguage(storedLang);
|
|
96
|
+
document.documentElement.setAttribute("data-theme", storedTheme);
|
|
97
|
+
}, []);
|
|
98
|
+
const updateTheme = (k) => {
|
|
99
|
+
setTheme(k);
|
|
100
|
+
if (typeof window !== "undefined") {
|
|
101
|
+
localStorage.setItem(ThemeStorageKey, k);
|
|
102
|
+
document.documentElement.setAttribute("data-theme", k);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const updateLanguage = (t) => {
|
|
106
|
+
setLanguage(t);
|
|
107
|
+
if (typeof window !== "undefined") {
|
|
108
|
+
localStorage.setItem(LanguageStorageKey, t);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const value = useMemo(
|
|
112
|
+
() => ({
|
|
113
|
+
theme,
|
|
114
|
+
language,
|
|
115
|
+
updateTheme,
|
|
116
|
+
updateLanguage,
|
|
117
|
+
simulationFilter,
|
|
118
|
+
setSimulationFilter
|
|
119
|
+
}),
|
|
120
|
+
[theme, language, simulationFilter]
|
|
121
|
+
);
|
|
122
|
+
return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
|
|
123
|
+
}
|
|
124
|
+
var THEMES = THEME_LABEL;
|
|
125
|
+
|
|
126
|
+
// src/core/constants/position.ts
|
|
127
|
+
var Position = [
|
|
128
|
+
"left-bottom",
|
|
129
|
+
"right-bottom",
|
|
130
|
+
"left-top",
|
|
131
|
+
"right-top"
|
|
132
|
+
];
|
|
133
|
+
var SWITCHER_POSITION = {
|
|
134
|
+
"left-bottom": "left-[16px] bottom-[16px]",
|
|
135
|
+
"right-bottom": "right-[16px] bottom-[16px]",
|
|
136
|
+
"left-top": "left-[16px] top-[16px]",
|
|
137
|
+
"right-top": "right-[16px] top-[16px]"
|
|
138
|
+
};
|
|
139
|
+
var SWITCHER_MENU_POSITION = {
|
|
140
|
+
"left-bottom": "left-[25px] bottom-[100px]",
|
|
141
|
+
"right-bottom": "right-[25px] bottom-[100px]",
|
|
142
|
+
"left-top": "left-[25px] top-[100px]",
|
|
143
|
+
"right-top": "right-[25px] top-[100px]"
|
|
144
|
+
};
|
|
145
|
+
var TOOLBAR_POSITION = {
|
|
146
|
+
"left-bottom": "left-[16px] bottom-[16px]",
|
|
147
|
+
"right-bottom": "right-[16px] bottom-[16px]",
|
|
148
|
+
"left-top": "left-[16px] top-[16px]",
|
|
149
|
+
"right-top": "right-[16px] top-[16px]"
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export {
|
|
153
|
+
SIMULATION_MODES,
|
|
154
|
+
MODE_LABELS,
|
|
155
|
+
SimulationStorageKey,
|
|
156
|
+
VISION_PORTAL_ID,
|
|
157
|
+
FILTER_ID,
|
|
158
|
+
FILTER_WRAPPER_ID,
|
|
159
|
+
THEME_SWITCHER_PORTAL_ID,
|
|
160
|
+
getThemeOptions,
|
|
161
|
+
useTheme,
|
|
162
|
+
ThemeProvider,
|
|
163
|
+
THEMES,
|
|
164
|
+
Position,
|
|
165
|
+
SWITCHER_POSITION,
|
|
166
|
+
SWITCHER_MENU_POSITION,
|
|
167
|
+
TOOLBAR_POSITION
|
|
168
|
+
};
|
package/dist/client.cjs
CHANGED
|
@@ -30,13 +30,18 @@ module.exports = __toCommonJS(client_exports);
|
|
|
30
30
|
|
|
31
31
|
// src/react/ThemeProvider.tsx
|
|
32
32
|
var import_react = require("react");
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"protanopia",
|
|
33
|
+
|
|
34
|
+
// src/core/constants/modes.ts
|
|
35
|
+
var SIMULATION_MODES = [
|
|
37
36
|
"deuteranopia",
|
|
37
|
+
"protanopia",
|
|
38
38
|
"tritanopia"
|
|
39
39
|
];
|
|
40
|
+
var THEME_MODES = [
|
|
41
|
+
"default",
|
|
42
|
+
...SIMULATION_MODES
|
|
43
|
+
];
|
|
44
|
+
var VISION_MODES = ["none", ...SIMULATION_MODES];
|
|
40
45
|
var THEME_LABEL = {
|
|
41
46
|
English: {
|
|
42
47
|
default: "default",
|
|
@@ -51,9 +56,16 @@ var THEME_LABEL = {
|
|
|
51
56
|
tritanopia: "\uCCAD\uC0C9\uB9F9"
|
|
52
57
|
}
|
|
53
58
|
};
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
var
|
|
59
|
+
|
|
60
|
+
// src/core/constants/key.ts
|
|
61
|
+
var ThemeStorageKey = "colbrush-theme";
|
|
62
|
+
var LanguageStorageKey = "colbrush-language";
|
|
63
|
+
var SimulationStorageKey = "colbrush-filter";
|
|
64
|
+
var THEME_SWITCHER_PORTAL_ID = "theme-switcher-portal";
|
|
65
|
+
|
|
66
|
+
// src/react/ThemeProvider.tsx
|
|
67
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
68
|
+
var getThemeOptions = (lang) => THEME_MODES.map((key) => ({ key, label: THEME_LABEL[lang][key] }));
|
|
57
69
|
var ThemeContext = (0, import_react.createContext)({
|
|
58
70
|
theme: "default",
|
|
59
71
|
language: "English",
|
|
@@ -68,7 +80,7 @@ var ThemeContext = (0, import_react.createContext)({
|
|
|
68
80
|
var useTheme = () => (0, import_react.useContext)(ThemeContext);
|
|
69
81
|
function normalizeToKey(value) {
|
|
70
82
|
if (!value) return "default";
|
|
71
|
-
if (
|
|
83
|
+
if (THEME_MODES.includes(value))
|
|
72
84
|
return value;
|
|
73
85
|
const reverse = {};
|
|
74
86
|
["English", "Korean"].forEach((lang) => {
|
|
@@ -86,8 +98,12 @@ function ThemeProvider({ children }) {
|
|
|
86
98
|
const [language, setLanguage] = (0, import_react.useState)("English");
|
|
87
99
|
(0, import_react.useEffect)(() => {
|
|
88
100
|
if (typeof window === "undefined") return;
|
|
89
|
-
const storedTheme = normalizeToKey(
|
|
90
|
-
|
|
101
|
+
const storedTheme = normalizeToKey(
|
|
102
|
+
localStorage.getItem(ThemeStorageKey)
|
|
103
|
+
);
|
|
104
|
+
const storedLang = localStorage.getItem(LanguageStorageKey) || "English";
|
|
105
|
+
const storedFilter = localStorage.getItem(SimulationStorageKey) || "none";
|
|
106
|
+
setSimulationFilter(storedFilter);
|
|
91
107
|
setTheme(storedTheme);
|
|
92
108
|
setLanguage(storedLang);
|
|
93
109
|
document.documentElement.setAttribute("data-theme", storedTheme);
|
|
@@ -95,14 +111,14 @@ function ThemeProvider({ children }) {
|
|
|
95
111
|
const updateTheme = (k) => {
|
|
96
112
|
setTheme(k);
|
|
97
113
|
if (typeof window !== "undefined") {
|
|
98
|
-
localStorage.setItem(
|
|
114
|
+
localStorage.setItem(ThemeStorageKey, k);
|
|
99
115
|
document.documentElement.setAttribute("data-theme", k);
|
|
100
116
|
}
|
|
101
117
|
};
|
|
102
118
|
const updateLanguage = (t) => {
|
|
103
119
|
setLanguage(t);
|
|
104
120
|
if (typeof window !== "undefined") {
|
|
105
|
-
localStorage.setItem(
|
|
121
|
+
localStorage.setItem(LanguageStorageKey, t);
|
|
106
122
|
}
|
|
107
123
|
};
|
|
108
124
|
const value = (0, import_react.useMemo)(
|
|
@@ -577,14 +593,17 @@ var Deuteranopia_default = SvgDeuteranopia;
|
|
|
577
593
|
// src/react/ThemeSwitcherPortal.tsx
|
|
578
594
|
var import_react2 = require("react");
|
|
579
595
|
var import_react_dom = require("react-dom");
|
|
580
|
-
var PORTAL_ID = "theme-switcher-portal";
|
|
581
596
|
var portalEl = null;
|
|
582
597
|
var activeOwner = null;
|
|
583
598
|
function ensurePortal() {
|
|
584
599
|
if (typeof document === "undefined") throw new Error("No document");
|
|
585
600
|
if (portalEl && document.body.contains(portalEl)) return portalEl;
|
|
586
|
-
const existing = document.getElementById(
|
|
587
|
-
|
|
601
|
+
const existing = document.getElementById(
|
|
602
|
+
THEME_SWITCHER_PORTAL_ID
|
|
603
|
+
);
|
|
604
|
+
portalEl = existing ?? Object.assign(document.createElement("div"), {
|
|
605
|
+
id: THEME_SWITCHER_PORTAL_ID
|
|
606
|
+
});
|
|
588
607
|
if (!existing) document.body.appendChild(portalEl);
|
|
589
608
|
return portalEl;
|
|
590
609
|
}
|
|
@@ -608,6 +627,20 @@ function ThemeSwitcherPortal({
|
|
|
608
627
|
return (0, import_react_dom.createPortal)(isPrimary ? children : null, container);
|
|
609
628
|
}
|
|
610
629
|
|
|
630
|
+
// src/core/constants/position.ts
|
|
631
|
+
var SWITCHER_POSITION = {
|
|
632
|
+
"left-bottom": "left-[16px] bottom-[16px]",
|
|
633
|
+
"right-bottom": "right-[16px] bottom-[16px]",
|
|
634
|
+
"left-top": "left-[16px] top-[16px]",
|
|
635
|
+
"right-top": "right-[16px] top-[16px]"
|
|
636
|
+
};
|
|
637
|
+
var SWITCHER_MENU_POSITION = {
|
|
638
|
+
"left-bottom": "left-[25px] bottom-[100px]",
|
|
639
|
+
"right-bottom": "right-[25px] bottom-[100px]",
|
|
640
|
+
"left-top": "left-[25px] top-[100px]",
|
|
641
|
+
"right-top": "right-[25px] top-[100px]"
|
|
642
|
+
};
|
|
643
|
+
|
|
611
644
|
// src/react/ThemeSwitcher.tsx
|
|
612
645
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
613
646
|
var THEME_ICON = {
|
|
@@ -616,7 +649,7 @@ var THEME_ICON = {
|
|
|
616
649
|
deuteranopia: Deuteranopia_default,
|
|
617
650
|
tritanopia: Tritanopia_default
|
|
618
651
|
};
|
|
619
|
-
function ThemeSwitcher({ options }) {
|
|
652
|
+
function ThemeSwitcher({ options, position }) {
|
|
620
653
|
const { theme, updateTheme, language, updateLanguage } = useTheme();
|
|
621
654
|
const [hovered, setHovered] = (0, import_react3.useState)(null);
|
|
622
655
|
const list = (0, import_react3.useMemo)(
|
|
@@ -625,6 +658,8 @@ function ThemeSwitcher({ options }) {
|
|
|
625
658
|
);
|
|
626
659
|
const [isOpen, setIsOpen] = (0, import_react3.useState)(false);
|
|
627
660
|
const wrapperRef = (0, import_react3.useRef)(null);
|
|
661
|
+
const switcherClass = SWITCHER_POSITION[position ?? "right-bottom"];
|
|
662
|
+
const switcherMenuClass = SWITCHER_MENU_POSITION[position ?? "right-bottom"];
|
|
628
663
|
(0, import_react3.useEffect)(() => {
|
|
629
664
|
const handleClickOutside = (event) => {
|
|
630
665
|
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
|
|
@@ -646,7 +681,9 @@ function ThemeSwitcher({ options }) {
|
|
|
646
681
|
"aria-haspopup": "menu",
|
|
647
682
|
"aria-expanded": isOpen,
|
|
648
683
|
onClick: toggle,
|
|
649
|
-
className:
|
|
684
|
+
className: `fixed w-[60px] h-[60px] p-[10px] bg-[#ffffff] rounded-full flex justify-center items-center shadow-[0_0_3px_0_rgba(0,0,0,0.17)]
|
|
685
|
+
${switcherClass}
|
|
686
|
+
`,
|
|
650
687
|
children: isOpen ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(X_default, { className: "self-center" }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Logo_default, { className: "self-center" })
|
|
651
688
|
}
|
|
652
689
|
),
|
|
@@ -655,7 +692,10 @@ function ThemeSwitcher({ options }) {
|
|
|
655
692
|
{
|
|
656
693
|
role: "menu",
|
|
657
694
|
"aria-label": "Select theme",
|
|
658
|
-
className:
|
|
695
|
+
className: `
|
|
696
|
+
fixed flex-col bg-[#ffffff] rounded-[18px] w-[220px] gap-[11px] filter drop-shadow-[0_0_1.3px_rgba(0,0,0,0.25)]
|
|
697
|
+
${switcherMenuClass}
|
|
698
|
+
`,
|
|
659
699
|
children: [
|
|
660
700
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { children: list.map((opt) => {
|
|
661
701
|
const Icon = THEME_ICON[opt.key];
|
package/dist/client.d.cts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
+
import { T as TThemeKey, V as VisionMode, P as Position } from './simulationTypes-CenFTH7t.cjs';
|
|
3
4
|
|
|
4
5
|
type TLanguage = 'English' | 'Korean';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
type ThemeKey = (typeof THEME_KEYS)[number];
|
|
8
|
-
type SimulationKey = (typeof SIMULATION_KEYS)[number];
|
|
6
|
+
type ThemeKey = TThemeKey[number];
|
|
7
|
+
type SimulationKey = VisionMode[number];
|
|
9
8
|
type ThemeContextType = {
|
|
10
9
|
theme: ThemeKey;
|
|
11
10
|
language: TLanguage;
|
|
@@ -20,14 +19,15 @@ type ThemeProviderProps = {
|
|
|
20
19
|
};
|
|
21
20
|
declare function ThemeProvider({ children }: ThemeProviderProps): react_jsx_runtime.JSX.Element;
|
|
22
21
|
type ThemeType = ThemeKey;
|
|
23
|
-
declare const THEMES: Record<TLanguage, Record<
|
|
22
|
+
declare const THEMES: Record<TLanguage, Record<TThemeKey, string>>;
|
|
24
23
|
|
|
25
|
-
type
|
|
24
|
+
type TThemeSwitcherProps = {
|
|
26
25
|
options?: {
|
|
27
26
|
key: ThemeKey;
|
|
28
27
|
label: string;
|
|
29
28
|
}[];
|
|
29
|
+
position?: Position;
|
|
30
30
|
};
|
|
31
|
-
declare function ThemeSwitcher({ options }:
|
|
31
|
+
declare function ThemeSwitcher({ options, position }: TThemeSwitcherProps): react_jsx_runtime.JSX.Element;
|
|
32
32
|
|
|
33
33
|
export { THEMES, type TLanguage, ThemeProvider, ThemeSwitcher, type ThemeType, useTheme };
|
package/dist/client.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
+
import { T as TThemeKey, V as VisionMode, P as Position } from './simulationTypes-CenFTH7t.js';
|
|
3
4
|
|
|
4
5
|
type TLanguage = 'English' | 'Korean';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
type ThemeKey = (typeof THEME_KEYS)[number];
|
|
8
|
-
type SimulationKey = (typeof SIMULATION_KEYS)[number];
|
|
6
|
+
type ThemeKey = TThemeKey[number];
|
|
7
|
+
type SimulationKey = VisionMode[number];
|
|
9
8
|
type ThemeContextType = {
|
|
10
9
|
theme: ThemeKey;
|
|
11
10
|
language: TLanguage;
|
|
@@ -20,14 +19,15 @@ type ThemeProviderProps = {
|
|
|
20
19
|
};
|
|
21
20
|
declare function ThemeProvider({ children }: ThemeProviderProps): react_jsx_runtime.JSX.Element;
|
|
22
21
|
type ThemeType = ThemeKey;
|
|
23
|
-
declare const THEMES: Record<TLanguage, Record<
|
|
22
|
+
declare const THEMES: Record<TLanguage, Record<TThemeKey, string>>;
|
|
24
23
|
|
|
25
|
-
type
|
|
24
|
+
type TThemeSwitcherProps = {
|
|
26
25
|
options?: {
|
|
27
26
|
key: ThemeKey;
|
|
28
27
|
label: string;
|
|
29
28
|
}[];
|
|
29
|
+
position?: Position;
|
|
30
30
|
};
|
|
31
|
-
declare function ThemeSwitcher({ options }:
|
|
31
|
+
declare function ThemeSwitcher({ options, position }: TThemeSwitcherProps): react_jsx_runtime.JSX.Element;
|
|
32
32
|
|
|
33
33
|
export { THEMES, type TLanguage, ThemeProvider, ThemeSwitcher, type ThemeType, useTheme };
|
package/dist/client.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
|
+
SWITCHER_MENU_POSITION,
|
|
4
|
+
SWITCHER_POSITION,
|
|
3
5
|
THEMES,
|
|
6
|
+
THEME_SWITCHER_PORTAL_ID,
|
|
4
7
|
ThemeProvider,
|
|
5
8
|
getThemeOptions,
|
|
6
9
|
useTheme
|
|
7
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-WQEJ7FU2.js";
|
|
8
11
|
|
|
9
12
|
// src/react/ThemeSwitcher.tsx
|
|
10
13
|
import { useEffect as useEffect2, useRef as useRef2, useState as useState2, useMemo } from "react";
|
|
@@ -463,14 +466,17 @@ var Deuteranopia_default = SvgDeuteranopia;
|
|
|
463
466
|
// src/react/ThemeSwitcherPortal.tsx
|
|
464
467
|
import { useEffect, useRef, useState } from "react";
|
|
465
468
|
import { createPortal } from "react-dom";
|
|
466
|
-
var PORTAL_ID = "theme-switcher-portal";
|
|
467
469
|
var portalEl = null;
|
|
468
470
|
var activeOwner = null;
|
|
469
471
|
function ensurePortal() {
|
|
470
472
|
if (typeof document === "undefined") throw new Error("No document");
|
|
471
473
|
if (portalEl && document.body.contains(portalEl)) return portalEl;
|
|
472
|
-
const existing = document.getElementById(
|
|
473
|
-
|
|
474
|
+
const existing = document.getElementById(
|
|
475
|
+
THEME_SWITCHER_PORTAL_ID
|
|
476
|
+
);
|
|
477
|
+
portalEl = existing ?? Object.assign(document.createElement("div"), {
|
|
478
|
+
id: THEME_SWITCHER_PORTAL_ID
|
|
479
|
+
});
|
|
474
480
|
if (!existing) document.body.appendChild(portalEl);
|
|
475
481
|
return portalEl;
|
|
476
482
|
}
|
|
@@ -502,7 +508,7 @@ var THEME_ICON = {
|
|
|
502
508
|
deuteranopia: Deuteranopia_default,
|
|
503
509
|
tritanopia: Tritanopia_default
|
|
504
510
|
};
|
|
505
|
-
function ThemeSwitcher({ options }) {
|
|
511
|
+
function ThemeSwitcher({ options, position }) {
|
|
506
512
|
const { theme, updateTheme, language, updateLanguage } = useTheme();
|
|
507
513
|
const [hovered, setHovered] = useState2(null);
|
|
508
514
|
const list = useMemo(
|
|
@@ -511,6 +517,8 @@ function ThemeSwitcher({ options }) {
|
|
|
511
517
|
);
|
|
512
518
|
const [isOpen, setIsOpen] = useState2(false);
|
|
513
519
|
const wrapperRef = useRef2(null);
|
|
520
|
+
const switcherClass = SWITCHER_POSITION[position ?? "right-bottom"];
|
|
521
|
+
const switcherMenuClass = SWITCHER_MENU_POSITION[position ?? "right-bottom"];
|
|
514
522
|
useEffect2(() => {
|
|
515
523
|
const handleClickOutside = (event) => {
|
|
516
524
|
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
|
|
@@ -532,7 +540,9 @@ function ThemeSwitcher({ options }) {
|
|
|
532
540
|
"aria-haspopup": "menu",
|
|
533
541
|
"aria-expanded": isOpen,
|
|
534
542
|
onClick: toggle,
|
|
535
|
-
className:
|
|
543
|
+
className: `fixed w-[60px] h-[60px] p-[10px] bg-[#ffffff] rounded-full flex justify-center items-center shadow-[0_0_3px_0_rgba(0,0,0,0.17)]
|
|
544
|
+
${switcherClass}
|
|
545
|
+
`,
|
|
536
546
|
children: isOpen ? /* @__PURE__ */ jsx9(X_default, { className: "self-center" }) : /* @__PURE__ */ jsx9(Logo_default, { className: "self-center" })
|
|
537
547
|
}
|
|
538
548
|
),
|
|
@@ -541,7 +551,10 @@ function ThemeSwitcher({ options }) {
|
|
|
541
551
|
{
|
|
542
552
|
role: "menu",
|
|
543
553
|
"aria-label": "Select theme",
|
|
544
|
-
className:
|
|
554
|
+
className: `
|
|
555
|
+
fixed flex-col bg-[#ffffff] rounded-[18px] w-[220px] gap-[11px] filter drop-shadow-[0_0_1.3px_rgba(0,0,0,0.25)]
|
|
556
|
+
${switcherMenuClass}
|
|
557
|
+
`,
|
|
545
558
|
children: [
|
|
546
559
|
/* @__PURE__ */ jsx9("div", { children: list.map((opt) => {
|
|
547
560
|
const Icon = THEME_ICON[opt.key];
|
package/dist/devtools.cjs
CHANGED
|
@@ -24,15 +24,31 @@ __export(devtools_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(devtools_exports);
|
|
26
26
|
|
|
27
|
-
// src/core/constants/
|
|
27
|
+
// src/core/constants/key.ts
|
|
28
|
+
var SimulationStorageKey = "colbrush-filter";
|
|
29
|
+
var VISION_PORTAL_ID = "cb-vision-portal";
|
|
28
30
|
var FILTER_ID = "cb-vision-filter";
|
|
29
31
|
var FILTER_WRAPPER_ID = "cb-vision-filter-root";
|
|
32
|
+
|
|
33
|
+
// src/core/constants/position.ts
|
|
34
|
+
var Position = [
|
|
35
|
+
"left-bottom",
|
|
36
|
+
"right-bottom",
|
|
37
|
+
"left-top",
|
|
38
|
+
"right-top"
|
|
39
|
+
];
|
|
40
|
+
var TOOLBAR_POSITION = {
|
|
41
|
+
"left-bottom": "left-[16px] bottom-[16px]",
|
|
42
|
+
"right-bottom": "right-[16px] bottom-[16px]",
|
|
43
|
+
"left-top": "left-[16px] top-[16px]",
|
|
44
|
+
"right-top": "right-[16px] top-[16px]"
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// src/core/constants/simulation.ts
|
|
30
48
|
var DEFAULT_OPTIONS = {
|
|
31
49
|
defaultMode: "none",
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
toolbarPosition: "left-bottom",
|
|
35
|
-
hotkey: true,
|
|
50
|
+
storageKey: SimulationStorageKey,
|
|
51
|
+
position: Position[0],
|
|
36
52
|
allowInProd: false
|
|
37
53
|
};
|
|
38
54
|
var MATRICES = {
|
|
@@ -66,12 +82,17 @@ function getMatrixForMode(mode) {
|
|
|
66
82
|
return MATRICES[mode];
|
|
67
83
|
}
|
|
68
84
|
|
|
69
|
-
// src/
|
|
85
|
+
// src/core/constants/modes.ts
|
|
70
86
|
var SIMULATION_MODES = [
|
|
71
87
|
"deuteranopia",
|
|
72
88
|
"protanopia",
|
|
73
89
|
"tritanopia"
|
|
74
90
|
];
|
|
91
|
+
var THEME_MODES = [
|
|
92
|
+
"default",
|
|
93
|
+
...SIMULATION_MODES
|
|
94
|
+
];
|
|
95
|
+
var VISION_MODES = ["none", ...SIMULATION_MODES];
|
|
75
96
|
var MODE_LABELS = {
|
|
76
97
|
English: {
|
|
77
98
|
none: "default",
|
|
@@ -109,15 +130,16 @@ var useTheme = () => (0, import_react.useContext)(ThemeContext);
|
|
|
109
130
|
// src/react/VisionPortal.tsx
|
|
110
131
|
var import_react2 = require("react");
|
|
111
132
|
var import_react_dom = require("react-dom");
|
|
112
|
-
var PORTAL_ID = "cb-vision-portal";
|
|
113
133
|
var portalEl = null;
|
|
114
134
|
var activeOwner = null;
|
|
115
135
|
function ensurePortal() {
|
|
116
136
|
if (typeof document === "undefined")
|
|
117
137
|
throw new Error("No document available");
|
|
118
138
|
if (portalEl && document.body.contains(portalEl)) return portalEl;
|
|
119
|
-
const existing = document.getElementById(
|
|
120
|
-
|
|
139
|
+
const existing = document.getElementById(
|
|
140
|
+
VISION_PORTAL_ID
|
|
141
|
+
);
|
|
142
|
+
portalEl = existing ?? Object.assign(document.createElement("div"), { id: VISION_PORTAL_ID });
|
|
121
143
|
if (!existing) document.body.appendChild(portalEl);
|
|
122
144
|
return portalEl;
|
|
123
145
|
}
|
|
@@ -156,15 +178,11 @@ function VisionFilterPortal({
|
|
|
156
178
|
|
|
157
179
|
// src/react/SimulationFilter.tsx
|
|
158
180
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
159
|
-
var import_meta = {};
|
|
160
181
|
var originalFilterMap = /* @__PURE__ */ new WeakMap();
|
|
161
182
|
var IS_DEV = (() => {
|
|
162
183
|
if (typeof process !== "undefined" && true) {
|
|
163
184
|
return true;
|
|
164
185
|
}
|
|
165
|
-
if (typeof import_meta !== "undefined" && typeof import_meta.env?.MODE === "string") {
|
|
166
|
-
return import_meta.env.MODE !== "production";
|
|
167
|
-
}
|
|
168
186
|
if (typeof window !== "undefined") {
|
|
169
187
|
const host = window.location.hostname;
|
|
170
188
|
return host === "localhost" || host === "127.0.0.1";
|
|
@@ -176,27 +194,26 @@ function resolveOptions(props) {
|
|
|
176
194
|
const merged = {
|
|
177
195
|
...DEFAULT_OPTIONS,
|
|
178
196
|
...options,
|
|
179
|
-
|
|
180
|
-
|
|
197
|
+
storageKey: options.storageKey ?? DEFAULT_OPTIONS.storageKey,
|
|
198
|
+
position: options.position ?? DEFAULT_OPTIONS.position,
|
|
181
199
|
allowInProd: options.allowInProd ?? DEFAULT_OPTIONS.allowInProd
|
|
182
200
|
};
|
|
183
201
|
return { config: merged, visible };
|
|
184
202
|
}
|
|
185
203
|
function SimulationFilter(props) {
|
|
186
204
|
const { config, visible } = resolveOptions(props);
|
|
187
|
-
const {
|
|
205
|
+
const { position, allowInProd, defaultMode, storageKey } = config;
|
|
188
206
|
const [open, setOpen] = (0, import_react3.useState)(false);
|
|
189
207
|
const { simulationFilter, setSimulationFilter, language } = useTheme();
|
|
208
|
+
const initialized = (0, import_react3.useRef)(false);
|
|
190
209
|
if (!visible) return null;
|
|
191
210
|
if (!allowInProd && !IS_DEV) return null;
|
|
192
|
-
const ANCHOR_CLASSES = {
|
|
193
|
-
"left-bottom": "left-[16px] bottom-[16px]",
|
|
194
|
-
"right-bottom": "right-[16px] bottom-[16px]",
|
|
195
|
-
"left-top": "left-[16px] top-[16px]",
|
|
196
|
-
"right-top": "right-[16px] top-[16px]"
|
|
197
|
-
};
|
|
198
211
|
const MODES = ["none", ...SIMULATION_MODES];
|
|
199
|
-
const
|
|
212
|
+
const toolBarClass = TOOLBAR_POSITION[position] ?? TOOLBAR_POSITION["left-bottom"];
|
|
213
|
+
const updateSimulationFilter = (value) => {
|
|
214
|
+
setSimulationFilter(value);
|
|
215
|
+
localStorage.setItem(SimulationStorageKey, value);
|
|
216
|
+
};
|
|
200
217
|
(0, import_react3.useEffect)(() => {
|
|
201
218
|
if (typeof document === "undefined") return;
|
|
202
219
|
const resolveTarget = () => {
|
|
@@ -206,7 +223,7 @@ function SimulationFilter(props) {
|
|
|
206
223
|
document.body?.children ?? []
|
|
207
224
|
);
|
|
208
225
|
const fallback = bodyChildren.find(
|
|
209
|
-
(child) => child.id !==
|
|
226
|
+
(child) => child.id !== VISION_PORTAL_ID
|
|
210
227
|
);
|
|
211
228
|
return fallback ?? document.body;
|
|
212
229
|
};
|
|
@@ -261,7 +278,9 @@ function SimulationFilter(props) {
|
|
|
261
278
|
"feColorMatrix",
|
|
262
279
|
{
|
|
263
280
|
type: "matrix",
|
|
264
|
-
values: getMatrixForMode(
|
|
281
|
+
values: getMatrixForMode(
|
|
282
|
+
simulationFilter
|
|
283
|
+
)
|
|
265
284
|
}
|
|
266
285
|
) }) })
|
|
267
286
|
}
|
|
@@ -269,7 +288,7 @@ function SimulationFilter(props) {
|
|
|
269
288
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
270
289
|
"div",
|
|
271
290
|
{
|
|
272
|
-
className: `cb-vision-toolbar h-[36px] fixed z-[100] inline-flex items-center gap-[6px] rounded-[10px] bg-[rgba(17,17,17,0.85)] p-[6px_8px] text-[12px] text-white opacity-90 shadow-[0_6px_18px_rgba(0,0,0,0.25)] backdrop-blur-[6px] ${
|
|
291
|
+
className: `cb-vision-toolbar h-[36px] fixed z-[100] inline-flex items-center gap-[6px] rounded-[10px] bg-[rgba(17,17,17,0.85)] p-[6px_8px] text-[12px] text-white opacity-90 shadow-[0_6px_18px_rgba(0,0,0,0.25)] backdrop-blur-[6px] ${toolBarClass}`,
|
|
273
292
|
children: [
|
|
274
293
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
275
294
|
"span",
|
|
@@ -284,7 +303,7 @@ function SimulationFilter(props) {
|
|
|
284
303
|
{
|
|
285
304
|
type: "button",
|
|
286
305
|
className: `rounded-[6px] px-[6px] py-[3px] ${simulationFilter === value ? "bg-white text-black" : "hover:bg-[rgba(255,255,255,0.2)]"}`,
|
|
287
|
-
onClick: () =>
|
|
306
|
+
onClick: () => updateSimulationFilter(value),
|
|
288
307
|
children: MODE_LABELS[language][value] ?? value
|
|
289
308
|
},
|
|
290
309
|
value
|
package/dist/devtools.d.cts
CHANGED
|
@@ -1,22 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
|
|
3
|
-
type VisionMode = 'deuteranopia' | 'protanopia' | 'tritanopia' | 'none';
|
|
4
|
-
interface VisionOptions {
|
|
5
|
-
/** 기본 모드. 기본값 'none' */
|
|
6
|
-
defaultMode?: VisionMode;
|
|
7
|
-
/** URL 토글 파라미터 키 (예: ?vision=deut), 기본값 'vision' */
|
|
8
|
-
paramKey?: string;
|
|
9
|
-
/** localStorage 키, 기본값 'colbrush:vision' */
|
|
10
|
-
storageKey?: string;
|
|
11
|
-
/** 개발 호스트 허용(정규식) — 기본: localhost/127/192.168.x */
|
|
12
|
-
devHostPattern?: RegExp;
|
|
13
|
-
/** 툴바 위치, 기본 'left-bottom' */
|
|
14
|
-
toolbarPosition?: 'left-bottom' | 'right-bottom' | 'left-top' | 'right-top';
|
|
15
|
-
/** 단축키 활성 여부, 기본 true (⌘/Ctrl + Alt + D) */
|
|
16
|
-
hotkey?: boolean;
|
|
17
|
-
/** 프로덕션에서도 강제로 허용(디버깅용). 기본 false */
|
|
18
|
-
allowInProd?: boolean;
|
|
19
|
-
}
|
|
2
|
+
import { a as VisionOptions } from './simulationTypes-CenFTH7t.cjs';
|
|
20
3
|
|
|
21
4
|
type SimulationFilterProps = VisionOptions & {
|
|
22
5
|
visible?: boolean;
|
package/dist/devtools.d.ts
CHANGED
|
@@ -1,22 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
|
|
3
|
-
type VisionMode = 'deuteranopia' | 'protanopia' | 'tritanopia' | 'none';
|
|
4
|
-
interface VisionOptions {
|
|
5
|
-
/** 기본 모드. 기본값 'none' */
|
|
6
|
-
defaultMode?: VisionMode;
|
|
7
|
-
/** URL 토글 파라미터 키 (예: ?vision=deut), 기본값 'vision' */
|
|
8
|
-
paramKey?: string;
|
|
9
|
-
/** localStorage 키, 기본값 'colbrush:vision' */
|
|
10
|
-
storageKey?: string;
|
|
11
|
-
/** 개발 호스트 허용(정규식) — 기본: localhost/127/192.168.x */
|
|
12
|
-
devHostPattern?: RegExp;
|
|
13
|
-
/** 툴바 위치, 기본 'left-bottom' */
|
|
14
|
-
toolbarPosition?: 'left-bottom' | 'right-bottom' | 'left-top' | 'right-top';
|
|
15
|
-
/** 단축키 활성 여부, 기본 true (⌘/Ctrl + Alt + D) */
|
|
16
|
-
hotkey?: boolean;
|
|
17
|
-
/** 프로덕션에서도 강제로 허용(디버깅용). 기본 false */
|
|
18
|
-
allowInProd?: boolean;
|
|
19
|
-
}
|
|
2
|
+
import { a as VisionOptions } from './simulationTypes-CenFTH7t.js';
|
|
20
3
|
|
|
21
4
|
type SimulationFilterProps = VisionOptions & {
|
|
22
5
|
visible?: boolean;
|
package/dist/devtools.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
|
+
FILTER_ID,
|
|
3
|
+
FILTER_WRAPPER_ID,
|
|
4
|
+
MODE_LABELS,
|
|
5
|
+
Position,
|
|
6
|
+
SIMULATION_MODES,
|
|
7
|
+
SimulationStorageKey,
|
|
8
|
+
TOOLBAR_POSITION,
|
|
9
|
+
VISION_PORTAL_ID,
|
|
2
10
|
useTheme
|
|
3
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-WQEJ7FU2.js";
|
|
4
12
|
|
|
5
13
|
// src/core/constants/simulation.ts
|
|
6
|
-
var FILTER_ID = "cb-vision-filter";
|
|
7
|
-
var FILTER_WRAPPER_ID = "cb-vision-filter-root";
|
|
8
14
|
var DEFAULT_OPTIONS = {
|
|
9
15
|
defaultMode: "none",
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
toolbarPosition: "left-bottom",
|
|
13
|
-
hotkey: true,
|
|
16
|
+
storageKey: SimulationStorageKey,
|
|
17
|
+
position: Position[0],
|
|
14
18
|
allowInProd: false
|
|
15
19
|
};
|
|
16
20
|
var MATRICES = {
|
|
@@ -44,42 +48,22 @@ function getMatrixForMode(mode) {
|
|
|
44
48
|
return MATRICES[mode];
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
// src/devtools/vision/modes.ts
|
|
48
|
-
var SIMULATION_MODES = [
|
|
49
|
-
"deuteranopia",
|
|
50
|
-
"protanopia",
|
|
51
|
-
"tritanopia"
|
|
52
|
-
];
|
|
53
|
-
var MODE_LABELS = {
|
|
54
|
-
English: {
|
|
55
|
-
none: "default",
|
|
56
|
-
protanopia: "protanopia",
|
|
57
|
-
deuteranopia: "deuteranopia",
|
|
58
|
-
tritanopia: "tritanopia"
|
|
59
|
-
},
|
|
60
|
-
Korean: {
|
|
61
|
-
none: "\uAEBC\uC9D0",
|
|
62
|
-
protanopia: "\uC801\uC0C9\uB9F9",
|
|
63
|
-
deuteranopia: "\uB179\uC0C9\uB9F9",
|
|
64
|
-
tritanopia: "\uCCAD\uC0C9\uB9F9"
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
51
|
// src/react/SimulationFilter.tsx
|
|
69
|
-
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
52
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
70
53
|
|
|
71
54
|
// src/react/VisionPortal.tsx
|
|
72
55
|
import { useEffect, useRef, useState } from "react";
|
|
73
56
|
import { createPortal } from "react-dom";
|
|
74
|
-
var PORTAL_ID = "cb-vision-portal";
|
|
75
57
|
var portalEl = null;
|
|
76
58
|
var activeOwner = null;
|
|
77
59
|
function ensurePortal() {
|
|
78
60
|
if (typeof document === "undefined")
|
|
79
61
|
throw new Error("No document available");
|
|
80
62
|
if (portalEl && document.body.contains(portalEl)) return portalEl;
|
|
81
|
-
const existing = document.getElementById(
|
|
82
|
-
|
|
63
|
+
const existing = document.getElementById(
|
|
64
|
+
VISION_PORTAL_ID
|
|
65
|
+
);
|
|
66
|
+
portalEl = existing ?? Object.assign(document.createElement("div"), { id: VISION_PORTAL_ID });
|
|
83
67
|
if (!existing) document.body.appendChild(portalEl);
|
|
84
68
|
return portalEl;
|
|
85
69
|
}
|
|
@@ -123,9 +107,6 @@ var IS_DEV = (() => {
|
|
|
123
107
|
if (typeof process !== "undefined" && true) {
|
|
124
108
|
return true;
|
|
125
109
|
}
|
|
126
|
-
if (typeof import.meta !== "undefined" && typeof import.meta.env?.MODE === "string") {
|
|
127
|
-
return import.meta.env.MODE !== "production";
|
|
128
|
-
}
|
|
129
110
|
if (typeof window !== "undefined") {
|
|
130
111
|
const host = window.location.hostname;
|
|
131
112
|
return host === "localhost" || host === "127.0.0.1";
|
|
@@ -137,27 +118,26 @@ function resolveOptions(props) {
|
|
|
137
118
|
const merged = {
|
|
138
119
|
...DEFAULT_OPTIONS,
|
|
139
120
|
...options,
|
|
140
|
-
|
|
141
|
-
|
|
121
|
+
storageKey: options.storageKey ?? DEFAULT_OPTIONS.storageKey,
|
|
122
|
+
position: options.position ?? DEFAULT_OPTIONS.position,
|
|
142
123
|
allowInProd: options.allowInProd ?? DEFAULT_OPTIONS.allowInProd
|
|
143
124
|
};
|
|
144
125
|
return { config: merged, visible };
|
|
145
126
|
}
|
|
146
127
|
function SimulationFilter(props) {
|
|
147
128
|
const { config, visible } = resolveOptions(props);
|
|
148
|
-
const {
|
|
129
|
+
const { position, allowInProd, defaultMode, storageKey } = config;
|
|
149
130
|
const [open, setOpen] = useState2(false);
|
|
150
131
|
const { simulationFilter, setSimulationFilter, language } = useTheme();
|
|
132
|
+
const initialized = useRef2(false);
|
|
151
133
|
if (!visible) return null;
|
|
152
134
|
if (!allowInProd && !IS_DEV) return null;
|
|
153
|
-
const ANCHOR_CLASSES = {
|
|
154
|
-
"left-bottom": "left-[16px] bottom-[16px]",
|
|
155
|
-
"right-bottom": "right-[16px] bottom-[16px]",
|
|
156
|
-
"left-top": "left-[16px] top-[16px]",
|
|
157
|
-
"right-top": "right-[16px] top-[16px]"
|
|
158
|
-
};
|
|
159
135
|
const MODES = ["none", ...SIMULATION_MODES];
|
|
160
|
-
const
|
|
136
|
+
const toolBarClass = TOOLBAR_POSITION[position] ?? TOOLBAR_POSITION["left-bottom"];
|
|
137
|
+
const updateSimulationFilter = (value) => {
|
|
138
|
+
setSimulationFilter(value);
|
|
139
|
+
localStorage.setItem(SimulationStorageKey, value);
|
|
140
|
+
};
|
|
161
141
|
useEffect2(() => {
|
|
162
142
|
if (typeof document === "undefined") return;
|
|
163
143
|
const resolveTarget = () => {
|
|
@@ -167,7 +147,7 @@ function SimulationFilter(props) {
|
|
|
167
147
|
document.body?.children ?? []
|
|
168
148
|
);
|
|
169
149
|
const fallback = bodyChildren.find(
|
|
170
|
-
(child) => child.id !==
|
|
150
|
+
(child) => child.id !== VISION_PORTAL_ID
|
|
171
151
|
);
|
|
172
152
|
return fallback ?? document.body;
|
|
173
153
|
};
|
|
@@ -222,7 +202,9 @@ function SimulationFilter(props) {
|
|
|
222
202
|
"feColorMatrix",
|
|
223
203
|
{
|
|
224
204
|
type: "matrix",
|
|
225
|
-
values: getMatrixForMode(
|
|
205
|
+
values: getMatrixForMode(
|
|
206
|
+
simulationFilter
|
|
207
|
+
)
|
|
226
208
|
}
|
|
227
209
|
) }) })
|
|
228
210
|
}
|
|
@@ -230,7 +212,7 @@ function SimulationFilter(props) {
|
|
|
230
212
|
/* @__PURE__ */ jsxs(
|
|
231
213
|
"div",
|
|
232
214
|
{
|
|
233
|
-
className: `cb-vision-toolbar h-[36px] fixed z-[100] inline-flex items-center gap-[6px] rounded-[10px] bg-[rgba(17,17,17,0.85)] p-[6px_8px] text-[12px] text-white opacity-90 shadow-[0_6px_18px_rgba(0,0,0,0.25)] backdrop-blur-[6px] ${
|
|
215
|
+
className: `cb-vision-toolbar h-[36px] fixed z-[100] inline-flex items-center gap-[6px] rounded-[10px] bg-[rgba(17,17,17,0.85)] p-[6px_8px] text-[12px] text-white opacity-90 shadow-[0_6px_18px_rgba(0,0,0,0.25)] backdrop-blur-[6px] ${toolBarClass}`,
|
|
234
216
|
children: [
|
|
235
217
|
/* @__PURE__ */ jsx(
|
|
236
218
|
"span",
|
|
@@ -245,7 +227,7 @@ function SimulationFilter(props) {
|
|
|
245
227
|
{
|
|
246
228
|
type: "button",
|
|
247
229
|
className: `rounded-[6px] px-[6px] py-[3px] ${simulationFilter === value ? "bg-white text-black" : "hover:bg-[rgba(255,255,255,0.2)]"}`,
|
|
248
|
-
onClick: () =>
|
|
230
|
+
onClick: () => updateSimulationFilter(value),
|
|
249
231
|
children: MODE_LABELS[language][value] ?? value
|
|
250
232
|
},
|
|
251
233
|
value
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type TPosition = 'left-bottom' | 'right-bottom' | 'left-top' | 'right-top';
|
|
2
|
+
|
|
3
|
+
type Vision = 'protanopia' | 'deuteranopia' | 'tritanopia';
|
|
4
|
+
type VisionMode = Vision | 'none';
|
|
5
|
+
type TThemeKey = Vision | 'default';
|
|
6
|
+
|
|
7
|
+
interface VisionOptions {
|
|
8
|
+
/** 기본 모드. 기본값 'none' */
|
|
9
|
+
defaultMode?: VisionMode;
|
|
10
|
+
/** localStorage 키, 기본값 'colbrush-filter' */
|
|
11
|
+
storageKey?: string;
|
|
12
|
+
/** 개발 호스트 허용(정규식) — 기본: localhost/127/192.168.x */
|
|
13
|
+
devHostPattern?: RegExp;
|
|
14
|
+
/** 툴바 위치, 기본 'left-bottom' */
|
|
15
|
+
position?: TPosition;
|
|
16
|
+
/** 프로덕션에서도 강제로 허용(디버깅용). 기본 false */
|
|
17
|
+
allowInProd?: boolean;
|
|
18
|
+
}
|
|
19
|
+
type Position = NonNullable<VisionOptions['position']>;
|
|
20
|
+
|
|
21
|
+
export type { Position as P, TThemeKey as T, VisionMode as V, VisionOptions as a };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type TPosition = 'left-bottom' | 'right-bottom' | 'left-top' | 'right-top';
|
|
2
|
+
|
|
3
|
+
type Vision = 'protanopia' | 'deuteranopia' | 'tritanopia';
|
|
4
|
+
type VisionMode = Vision | 'none';
|
|
5
|
+
type TThemeKey = Vision | 'default';
|
|
6
|
+
|
|
7
|
+
interface VisionOptions {
|
|
8
|
+
/** 기본 모드. 기본값 'none' */
|
|
9
|
+
defaultMode?: VisionMode;
|
|
10
|
+
/** localStorage 키, 기본값 'colbrush-filter' */
|
|
11
|
+
storageKey?: string;
|
|
12
|
+
/** 개발 호스트 허용(정규식) — 기본: localhost/127/192.168.x */
|
|
13
|
+
devHostPattern?: RegExp;
|
|
14
|
+
/** 툴바 위치, 기본 'left-bottom' */
|
|
15
|
+
position?: TPosition;
|
|
16
|
+
/** 프로덕션에서도 강제로 허용(디버깅용). 기본 false */
|
|
17
|
+
allowInProd?: boolean;
|
|
18
|
+
}
|
|
19
|
+
type Position = NonNullable<VisionOptions['position']>;
|
|
20
|
+
|
|
21
|
+
export type { Position as P, TThemeKey as T, VisionMode as V, VisionOptions as a };
|
package/dist/styles.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@source "./src/**/*.{js,ts,jsx,tsx}";
|
|
1
|
+
@source "./src/**/*.{js,ts,jsx,tsx}";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "colbrush",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.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": {
|
|
@@ -53,7 +53,6 @@
|
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"chokidar": "^3.6.0",
|
|
55
55
|
"chroma-js": "^3.1.2",
|
|
56
|
-
"color-blind": "^0.1.3",
|
|
57
56
|
"colorjs.io": "^0.5.2",
|
|
58
57
|
"postcss": "^8.5.6",
|
|
59
58
|
"postcss-safe-parser": "^7.0.1"
|
package/dist/chunk-EFIJAMPH.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
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
|
-
};
|