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,296 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/devtools/index.ts
|
|
21
|
+
var devtools_exports = {};
|
|
22
|
+
__export(devtools_exports, {
|
|
23
|
+
SimulationFilter: () => SimulationFilter
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(devtools_exports);
|
|
26
|
+
|
|
27
|
+
// src/core/constants/simulation.ts
|
|
28
|
+
var FILTER_ID = "cb-vision-filter";
|
|
29
|
+
var FILTER_WRAPPER_ID = "cb-vision-filter-root";
|
|
30
|
+
var DEFAULT_OPTIONS = {
|
|
31
|
+
defaultMode: "none",
|
|
32
|
+
paramKey: "vision",
|
|
33
|
+
storageKey: "colbrush:vision",
|
|
34
|
+
toolbarPosition: "left-bottom",
|
|
35
|
+
hotkey: true,
|
|
36
|
+
allowInProd: false
|
|
37
|
+
};
|
|
38
|
+
var MATRICES = {
|
|
39
|
+
deuteranopia: `
|
|
40
|
+
0.367 0.861 -0.228 0 0
|
|
41
|
+
0.280 0.673 0.047 0 0
|
|
42
|
+
-0.012 0.043 0.969 0 0
|
|
43
|
+
0.000 0.000 0.000 1 0
|
|
44
|
+
`,
|
|
45
|
+
protanopia: `
|
|
46
|
+
0.152 1.052 -0.204 0 0
|
|
47
|
+
0.115 0.786 0.099 0 0
|
|
48
|
+
-0.004 -0.048 1.052 0 0
|
|
49
|
+
0.000 0.000 0.000 1 0
|
|
50
|
+
`,
|
|
51
|
+
tritanopia: `
|
|
52
|
+
1.256 -0.077 -0.179 0 0
|
|
53
|
+
-0.078 0.931 0.148 0 0
|
|
54
|
+
0.005 0.691 0.304 0 0
|
|
55
|
+
0.000 0.000 0.000 1 0
|
|
56
|
+
`
|
|
57
|
+
};
|
|
58
|
+
var IDENTITY_MATRIX = `
|
|
59
|
+
1 0 0 0 0
|
|
60
|
+
0 1 0 0 0
|
|
61
|
+
0 0 1 0 0
|
|
62
|
+
0 0 0 1 0
|
|
63
|
+
`;
|
|
64
|
+
function getMatrixForMode(mode) {
|
|
65
|
+
if (mode === "none") return IDENTITY_MATRIX;
|
|
66
|
+
return MATRICES[mode];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/devtools/vision/modes.ts
|
|
70
|
+
var SIMULATION_MODES = [
|
|
71
|
+
"deuteranopia",
|
|
72
|
+
"protanopia",
|
|
73
|
+
"tritanopia"
|
|
74
|
+
];
|
|
75
|
+
var MODE_LABELS = {
|
|
76
|
+
English: {
|
|
77
|
+
none: "default",
|
|
78
|
+
protanopia: "protanopia",
|
|
79
|
+
deuteranopia: "deuteranopia",
|
|
80
|
+
tritanopia: "tritanopia"
|
|
81
|
+
},
|
|
82
|
+
Korean: {
|
|
83
|
+
none: "\uAEBC\uC9D0",
|
|
84
|
+
protanopia: "\uC801\uC0C9\uB9F9",
|
|
85
|
+
deuteranopia: "\uB179\uC0C9\uB9F9",
|
|
86
|
+
tritanopia: "\uCCAD\uC0C9\uB9F9"
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// src/react/SimulationFilter.tsx
|
|
91
|
+
var import_react3 = require("react");
|
|
92
|
+
|
|
93
|
+
// src/react/ThemeProvider.tsx
|
|
94
|
+
var import_react = require("react");
|
|
95
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
96
|
+
var ThemeContext = (0, import_react.createContext)({
|
|
97
|
+
theme: "default",
|
|
98
|
+
language: "English",
|
|
99
|
+
updateTheme: () => {
|
|
100
|
+
},
|
|
101
|
+
updateLanguage: () => {
|
|
102
|
+
},
|
|
103
|
+
simulationFilter: "none",
|
|
104
|
+
setSimulationFilter: () => {
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
var useTheme = () => (0, import_react.useContext)(ThemeContext);
|
|
108
|
+
|
|
109
|
+
// src/react/VisionPortal.tsx
|
|
110
|
+
var import_react2 = require("react");
|
|
111
|
+
var import_react_dom = require("react-dom");
|
|
112
|
+
var PORTAL_ID = "cb-vision-portal";
|
|
113
|
+
var portalEl = null;
|
|
114
|
+
var activeOwner = null;
|
|
115
|
+
function ensurePortal() {
|
|
116
|
+
if (typeof document === "undefined")
|
|
117
|
+
throw new Error("No document available");
|
|
118
|
+
if (portalEl && document.body.contains(portalEl)) return portalEl;
|
|
119
|
+
const existing = document.getElementById(PORTAL_ID);
|
|
120
|
+
portalEl = existing ?? Object.assign(document.createElement("div"), { id: PORTAL_ID });
|
|
121
|
+
if (!existing) document.body.appendChild(portalEl);
|
|
122
|
+
return portalEl;
|
|
123
|
+
}
|
|
124
|
+
function VisionFilterPortal({
|
|
125
|
+
visible = true,
|
|
126
|
+
children
|
|
127
|
+
}) {
|
|
128
|
+
const [container, setContainer] = (0, import_react2.useState)(null);
|
|
129
|
+
const ownerId = (0, import_react2.useRef)(Symbol("VisionFilterPortal"));
|
|
130
|
+
(0, import_react2.useEffect)(() => {
|
|
131
|
+
try {
|
|
132
|
+
const el = ensurePortal();
|
|
133
|
+
setContainer(el);
|
|
134
|
+
if (activeOwner === null) activeOwner = ownerId.current;
|
|
135
|
+
} catch {
|
|
136
|
+
setContainer(null);
|
|
137
|
+
}
|
|
138
|
+
return () => {
|
|
139
|
+
if (activeOwner === ownerId.current) {
|
|
140
|
+
activeOwner = null;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}, []);
|
|
144
|
+
const isPrimary = container !== null && activeOwner === ownerId.current;
|
|
145
|
+
const enabled = Boolean(isPrimary && visible);
|
|
146
|
+
(0, import_react2.useEffect)(() => {
|
|
147
|
+
if (!enabled || typeof document === "undefined") return;
|
|
148
|
+
const fallback = document.getElementById(FILTER_WRAPPER_ID);
|
|
149
|
+
if (fallback instanceof SVGSVGElement && fallback.dataset.cbVisionManaged === "fallback") {
|
|
150
|
+
fallback.remove();
|
|
151
|
+
}
|
|
152
|
+
}, [enabled]);
|
|
153
|
+
if (!container || !isPrimary) return null;
|
|
154
|
+
return (0, import_react_dom.createPortal)(isPrimary ? children : null, container);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/react/SimulationFilter.tsx
|
|
158
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
159
|
+
var import_meta = {};
|
|
160
|
+
var originalFilterMap = /* @__PURE__ */ new WeakMap();
|
|
161
|
+
var IS_DEV = (() => {
|
|
162
|
+
if (typeof process !== "undefined" && true) {
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
if (typeof import_meta !== "undefined" && typeof import_meta.env?.MODE === "string") {
|
|
166
|
+
return import_meta.env.MODE !== "production";
|
|
167
|
+
}
|
|
168
|
+
if (typeof window !== "undefined") {
|
|
169
|
+
const host = window.location.hostname;
|
|
170
|
+
return host === "localhost" || host === "127.0.0.1";
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
})();
|
|
174
|
+
function resolveOptions(props) {
|
|
175
|
+
const { visible = true, ...options } = props ?? {};
|
|
176
|
+
const merged = {
|
|
177
|
+
...DEFAULT_OPTIONS,
|
|
178
|
+
...options,
|
|
179
|
+
toolbarPosition: options.toolbarPosition ?? DEFAULT_OPTIONS.toolbarPosition,
|
|
180
|
+
hotkey: options.hotkey ?? DEFAULT_OPTIONS.hotkey,
|
|
181
|
+
allowInProd: options.allowInProd ?? DEFAULT_OPTIONS.allowInProd
|
|
182
|
+
};
|
|
183
|
+
return { config: merged, visible };
|
|
184
|
+
}
|
|
185
|
+
function SimulationFilter(props) {
|
|
186
|
+
const { config, visible } = resolveOptions(props);
|
|
187
|
+
const { toolbarPosition, allowInProd } = config;
|
|
188
|
+
const [open, setOpen] = (0, import_react3.useState)(false);
|
|
189
|
+
const { simulationFilter, setSimulationFilter, language } = useTheme();
|
|
190
|
+
if (!visible) return null;
|
|
191
|
+
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
|
+
const MODES = ["none", ...SIMULATION_MODES];
|
|
199
|
+
const anchorClass = ANCHOR_CLASSES[toolbarPosition] ?? ANCHOR_CLASSES["left-bottom"];
|
|
200
|
+
(0, import_react3.useEffect)(() => {
|
|
201
|
+
if (typeof document === "undefined") return;
|
|
202
|
+
const resolveTarget = () => {
|
|
203
|
+
const preferred = document.querySelector("[data-cb-vision-target]") ?? document.getElementById("root") ?? document.getElementById("__next");
|
|
204
|
+
if (preferred instanceof HTMLElement) return preferred;
|
|
205
|
+
const bodyChildren = Array.from(
|
|
206
|
+
document.body?.children ?? []
|
|
207
|
+
);
|
|
208
|
+
const fallback = bodyChildren.find(
|
|
209
|
+
(child) => child.id !== PORTAL_ID
|
|
210
|
+
);
|
|
211
|
+
return fallback ?? document.body;
|
|
212
|
+
};
|
|
213
|
+
const target = resolveTarget();
|
|
214
|
+
if (!(target instanceof HTMLElement)) return;
|
|
215
|
+
const style = target.style;
|
|
216
|
+
if (!originalFilterMap.has(target)) {
|
|
217
|
+
originalFilterMap.set(target, {
|
|
218
|
+
filter: style.filter ?? "",
|
|
219
|
+
webkitFilter: style.webkitFilter ?? ""
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
const snapshot = originalFilterMap.get(target) ?? {
|
|
223
|
+
filter: "",
|
|
224
|
+
webkitFilter: ""
|
|
225
|
+
};
|
|
226
|
+
const applyFilter = () => {
|
|
227
|
+
const filterValue = `url(#${FILTER_ID})`;
|
|
228
|
+
style.filter = filterValue;
|
|
229
|
+
style.webkitFilter = filterValue;
|
|
230
|
+
};
|
|
231
|
+
const clearFilter = () => {
|
|
232
|
+
style.filter = snapshot.filter;
|
|
233
|
+
style.webkitFilter = snapshot.webkitFilter;
|
|
234
|
+
};
|
|
235
|
+
if (simulationFilter === "none") {
|
|
236
|
+
clearFilter();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
applyFilter();
|
|
240
|
+
return () => {
|
|
241
|
+
clearFilter();
|
|
242
|
+
};
|
|
243
|
+
}, [simulationFilter]);
|
|
244
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(VisionFilterPortal, { children: [
|
|
245
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
246
|
+
"svg",
|
|
247
|
+
{
|
|
248
|
+
id: FILTER_WRAPPER_ID,
|
|
249
|
+
"aria-hidden": "true",
|
|
250
|
+
"data-cb-vision-managed": "react",
|
|
251
|
+
preserveAspectRatio: "none",
|
|
252
|
+
style: {
|
|
253
|
+
position: "fixed",
|
|
254
|
+
inset: 0,
|
|
255
|
+
width: "100vw",
|
|
256
|
+
height: "100vh",
|
|
257
|
+
pointerEvents: "none",
|
|
258
|
+
zIndex: 2147483646
|
|
259
|
+
},
|
|
260
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("filter", { id: FILTER_ID, "data-cb-vision-managed": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
261
|
+
"feColorMatrix",
|
|
262
|
+
{
|
|
263
|
+
type: "matrix",
|
|
264
|
+
values: getMatrixForMode(simulationFilter)
|
|
265
|
+
}
|
|
266
|
+
) }) })
|
|
267
|
+
}
|
|
268
|
+
),
|
|
269
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
270
|
+
"div",
|
|
271
|
+
{
|
|
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] ${anchorClass}`,
|
|
273
|
+
children: [
|
|
274
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
275
|
+
"span",
|
|
276
|
+
{
|
|
277
|
+
className: "font-medium hover:cursor-pointer",
|
|
278
|
+
onClick: () => setOpen(!open),
|
|
279
|
+
children: "Vision"
|
|
280
|
+
}
|
|
281
|
+
),
|
|
282
|
+
open && MODES.map((value) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
283
|
+
"button",
|
|
284
|
+
{
|
|
285
|
+
type: "button",
|
|
286
|
+
className: `rounded-[6px] px-[6px] py-[3px] ${simulationFilter === value ? "bg-white text-black" : "hover:bg-[rgba(255,255,255,0.2)]"}`,
|
|
287
|
+
onClick: () => setSimulationFilter(value),
|
|
288
|
+
children: MODE_LABELS[language][value] ?? value
|
|
289
|
+
},
|
|
290
|
+
value
|
|
291
|
+
))
|
|
292
|
+
]
|
|
293
|
+
}
|
|
294
|
+
)
|
|
295
|
+
] });
|
|
296
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
}
|
|
20
|
+
|
|
21
|
+
type SimulationFilterProps = VisionOptions & {
|
|
22
|
+
visible?: boolean;
|
|
23
|
+
};
|
|
24
|
+
declare function SimulationFilter(props?: SimulationFilterProps): react_jsx_runtime.JSX.Element | null;
|
|
25
|
+
|
|
26
|
+
export { SimulationFilter };
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
}
|
|
20
|
+
|
|
21
|
+
type SimulationFilterProps = VisionOptions & {
|
|
22
|
+
visible?: boolean;
|
|
23
|
+
};
|
|
24
|
+
declare function SimulationFilter(props?: SimulationFilterProps): react_jsx_runtime.JSX.Element | null;
|
|
25
|
+
|
|
26
|
+
export { SimulationFilter };
|
package/dist/devtools.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useTheme
|
|
3
|
+
} from "./chunk-EFIJAMPH.js";
|
|
4
|
+
|
|
5
|
+
// src/core/constants/simulation.ts
|
|
6
|
+
var FILTER_ID = "cb-vision-filter";
|
|
7
|
+
var FILTER_WRAPPER_ID = "cb-vision-filter-root";
|
|
8
|
+
var DEFAULT_OPTIONS = {
|
|
9
|
+
defaultMode: "none",
|
|
10
|
+
paramKey: "vision",
|
|
11
|
+
storageKey: "colbrush:vision",
|
|
12
|
+
toolbarPosition: "left-bottom",
|
|
13
|
+
hotkey: true,
|
|
14
|
+
allowInProd: false
|
|
15
|
+
};
|
|
16
|
+
var MATRICES = {
|
|
17
|
+
deuteranopia: `
|
|
18
|
+
0.367 0.861 -0.228 0 0
|
|
19
|
+
0.280 0.673 0.047 0 0
|
|
20
|
+
-0.012 0.043 0.969 0 0
|
|
21
|
+
0.000 0.000 0.000 1 0
|
|
22
|
+
`,
|
|
23
|
+
protanopia: `
|
|
24
|
+
0.152 1.052 -0.204 0 0
|
|
25
|
+
0.115 0.786 0.099 0 0
|
|
26
|
+
-0.004 -0.048 1.052 0 0
|
|
27
|
+
0.000 0.000 0.000 1 0
|
|
28
|
+
`,
|
|
29
|
+
tritanopia: `
|
|
30
|
+
1.256 -0.077 -0.179 0 0
|
|
31
|
+
-0.078 0.931 0.148 0 0
|
|
32
|
+
0.005 0.691 0.304 0 0
|
|
33
|
+
0.000 0.000 0.000 1 0
|
|
34
|
+
`
|
|
35
|
+
};
|
|
36
|
+
var IDENTITY_MATRIX = `
|
|
37
|
+
1 0 0 0 0
|
|
38
|
+
0 1 0 0 0
|
|
39
|
+
0 0 1 0 0
|
|
40
|
+
0 0 0 1 0
|
|
41
|
+
`;
|
|
42
|
+
function getMatrixForMode(mode) {
|
|
43
|
+
if (mode === "none") return IDENTITY_MATRIX;
|
|
44
|
+
return MATRICES[mode];
|
|
45
|
+
}
|
|
46
|
+
|
|
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
|
+
// src/react/SimulationFilter.tsx
|
|
69
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
70
|
+
|
|
71
|
+
// src/react/VisionPortal.tsx
|
|
72
|
+
import { useEffect, useRef, useState } from "react";
|
|
73
|
+
import { createPortal } from "react-dom";
|
|
74
|
+
var PORTAL_ID = "cb-vision-portal";
|
|
75
|
+
var portalEl = null;
|
|
76
|
+
var activeOwner = null;
|
|
77
|
+
function ensurePortal() {
|
|
78
|
+
if (typeof document === "undefined")
|
|
79
|
+
throw new Error("No document available");
|
|
80
|
+
if (portalEl && document.body.contains(portalEl)) return portalEl;
|
|
81
|
+
const existing = document.getElementById(PORTAL_ID);
|
|
82
|
+
portalEl = existing ?? Object.assign(document.createElement("div"), { id: PORTAL_ID });
|
|
83
|
+
if (!existing) document.body.appendChild(portalEl);
|
|
84
|
+
return portalEl;
|
|
85
|
+
}
|
|
86
|
+
function VisionFilterPortal({
|
|
87
|
+
visible = true,
|
|
88
|
+
children
|
|
89
|
+
}) {
|
|
90
|
+
const [container, setContainer] = useState(null);
|
|
91
|
+
const ownerId = useRef(Symbol("VisionFilterPortal"));
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
try {
|
|
94
|
+
const el = ensurePortal();
|
|
95
|
+
setContainer(el);
|
|
96
|
+
if (activeOwner === null) activeOwner = ownerId.current;
|
|
97
|
+
} catch {
|
|
98
|
+
setContainer(null);
|
|
99
|
+
}
|
|
100
|
+
return () => {
|
|
101
|
+
if (activeOwner === ownerId.current) {
|
|
102
|
+
activeOwner = null;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}, []);
|
|
106
|
+
const isPrimary = container !== null && activeOwner === ownerId.current;
|
|
107
|
+
const enabled = Boolean(isPrimary && visible);
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (!enabled || typeof document === "undefined") return;
|
|
110
|
+
const fallback = document.getElementById(FILTER_WRAPPER_ID);
|
|
111
|
+
if (fallback instanceof SVGSVGElement && fallback.dataset.cbVisionManaged === "fallback") {
|
|
112
|
+
fallback.remove();
|
|
113
|
+
}
|
|
114
|
+
}, [enabled]);
|
|
115
|
+
if (!container || !isPrimary) return null;
|
|
116
|
+
return createPortal(isPrimary ? children : null, container);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/react/SimulationFilter.tsx
|
|
120
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
121
|
+
var originalFilterMap = /* @__PURE__ */ new WeakMap();
|
|
122
|
+
var IS_DEV = (() => {
|
|
123
|
+
if (typeof process !== "undefined" && true) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
if (typeof import.meta !== "undefined" && typeof import.meta.env?.MODE === "string") {
|
|
127
|
+
return import.meta.env.MODE !== "production";
|
|
128
|
+
}
|
|
129
|
+
if (typeof window !== "undefined") {
|
|
130
|
+
const host = window.location.hostname;
|
|
131
|
+
return host === "localhost" || host === "127.0.0.1";
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
})();
|
|
135
|
+
function resolveOptions(props) {
|
|
136
|
+
const { visible = true, ...options } = props ?? {};
|
|
137
|
+
const merged = {
|
|
138
|
+
...DEFAULT_OPTIONS,
|
|
139
|
+
...options,
|
|
140
|
+
toolbarPosition: options.toolbarPosition ?? DEFAULT_OPTIONS.toolbarPosition,
|
|
141
|
+
hotkey: options.hotkey ?? DEFAULT_OPTIONS.hotkey,
|
|
142
|
+
allowInProd: options.allowInProd ?? DEFAULT_OPTIONS.allowInProd
|
|
143
|
+
};
|
|
144
|
+
return { config: merged, visible };
|
|
145
|
+
}
|
|
146
|
+
function SimulationFilter(props) {
|
|
147
|
+
const { config, visible } = resolveOptions(props);
|
|
148
|
+
const { toolbarPosition, allowInProd } = config;
|
|
149
|
+
const [open, setOpen] = useState2(false);
|
|
150
|
+
const { simulationFilter, setSimulationFilter, language } = useTheme();
|
|
151
|
+
if (!visible) return null;
|
|
152
|
+
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
|
+
const MODES = ["none", ...SIMULATION_MODES];
|
|
160
|
+
const anchorClass = ANCHOR_CLASSES[toolbarPosition] ?? ANCHOR_CLASSES["left-bottom"];
|
|
161
|
+
useEffect2(() => {
|
|
162
|
+
if (typeof document === "undefined") return;
|
|
163
|
+
const resolveTarget = () => {
|
|
164
|
+
const preferred = document.querySelector("[data-cb-vision-target]") ?? document.getElementById("root") ?? document.getElementById("__next");
|
|
165
|
+
if (preferred instanceof HTMLElement) return preferred;
|
|
166
|
+
const bodyChildren = Array.from(
|
|
167
|
+
document.body?.children ?? []
|
|
168
|
+
);
|
|
169
|
+
const fallback = bodyChildren.find(
|
|
170
|
+
(child) => child.id !== PORTAL_ID
|
|
171
|
+
);
|
|
172
|
+
return fallback ?? document.body;
|
|
173
|
+
};
|
|
174
|
+
const target = resolveTarget();
|
|
175
|
+
if (!(target instanceof HTMLElement)) return;
|
|
176
|
+
const style = target.style;
|
|
177
|
+
if (!originalFilterMap.has(target)) {
|
|
178
|
+
originalFilterMap.set(target, {
|
|
179
|
+
filter: style.filter ?? "",
|
|
180
|
+
webkitFilter: style.webkitFilter ?? ""
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
const snapshot = originalFilterMap.get(target) ?? {
|
|
184
|
+
filter: "",
|
|
185
|
+
webkitFilter: ""
|
|
186
|
+
};
|
|
187
|
+
const applyFilter = () => {
|
|
188
|
+
const filterValue = `url(#${FILTER_ID})`;
|
|
189
|
+
style.filter = filterValue;
|
|
190
|
+
style.webkitFilter = filterValue;
|
|
191
|
+
};
|
|
192
|
+
const clearFilter = () => {
|
|
193
|
+
style.filter = snapshot.filter;
|
|
194
|
+
style.webkitFilter = snapshot.webkitFilter;
|
|
195
|
+
};
|
|
196
|
+
if (simulationFilter === "none") {
|
|
197
|
+
clearFilter();
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
applyFilter();
|
|
201
|
+
return () => {
|
|
202
|
+
clearFilter();
|
|
203
|
+
};
|
|
204
|
+
}, [simulationFilter]);
|
|
205
|
+
return /* @__PURE__ */ jsxs(VisionFilterPortal, { children: [
|
|
206
|
+
/* @__PURE__ */ jsx(
|
|
207
|
+
"svg",
|
|
208
|
+
{
|
|
209
|
+
id: FILTER_WRAPPER_ID,
|
|
210
|
+
"aria-hidden": "true",
|
|
211
|
+
"data-cb-vision-managed": "react",
|
|
212
|
+
preserveAspectRatio: "none",
|
|
213
|
+
style: {
|
|
214
|
+
position: "fixed",
|
|
215
|
+
inset: 0,
|
|
216
|
+
width: "100vw",
|
|
217
|
+
height: "100vh",
|
|
218
|
+
pointerEvents: "none",
|
|
219
|
+
zIndex: 2147483646
|
|
220
|
+
},
|
|
221
|
+
children: /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("filter", { id: FILTER_ID, "data-cb-vision-managed": "true", children: /* @__PURE__ */ jsx(
|
|
222
|
+
"feColorMatrix",
|
|
223
|
+
{
|
|
224
|
+
type: "matrix",
|
|
225
|
+
values: getMatrixForMode(simulationFilter)
|
|
226
|
+
}
|
|
227
|
+
) }) })
|
|
228
|
+
}
|
|
229
|
+
),
|
|
230
|
+
/* @__PURE__ */ jsxs(
|
|
231
|
+
"div",
|
|
232
|
+
{
|
|
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] ${anchorClass}`,
|
|
234
|
+
children: [
|
|
235
|
+
/* @__PURE__ */ jsx(
|
|
236
|
+
"span",
|
|
237
|
+
{
|
|
238
|
+
className: "font-medium hover:cursor-pointer",
|
|
239
|
+
onClick: () => setOpen(!open),
|
|
240
|
+
children: "Vision"
|
|
241
|
+
}
|
|
242
|
+
),
|
|
243
|
+
open && MODES.map((value) => /* @__PURE__ */ jsx(
|
|
244
|
+
"button",
|
|
245
|
+
{
|
|
246
|
+
type: "button",
|
|
247
|
+
className: `rounded-[6px] px-[6px] py-[3px] ${simulationFilter === value ? "bg-white text-black" : "hover:bg-[rgba(255,255,255,0.2)]"}`,
|
|
248
|
+
onClick: () => setSimulationFilter(value),
|
|
249
|
+
children: MODE_LABELS[language][value] ?? value
|
|
250
|
+
},
|
|
251
|
+
value
|
|
252
|
+
))
|
|
253
|
+
]
|
|
254
|
+
}
|
|
255
|
+
)
|
|
256
|
+
] });
|
|
257
|
+
}
|
|
258
|
+
export {
|
|
259
|
+
SimulationFilter
|
|
260
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "colbrush",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.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": {
|
|
@@ -18,10 +18,21 @@
|
|
|
18
18
|
"import": "./dist/client.js",
|
|
19
19
|
"require": "./dist/client.cjs"
|
|
20
20
|
},
|
|
21
|
+
"./devtools": {
|
|
22
|
+
"types": "./dist/devtools.d.ts",
|
|
23
|
+
"import": "./dist/devtools.js",
|
|
24
|
+
"require": "./dist/devtools.cjs"
|
|
25
|
+
},
|
|
21
26
|
"./package.json": "./package.json"
|
|
22
27
|
},
|
|
23
28
|
"typesVersions": {
|
|
24
29
|
"*": {
|
|
30
|
+
"devtools": [
|
|
31
|
+
"./dist/devtools.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"client": [
|
|
34
|
+
"./dist/client.d.ts"
|
|
35
|
+
],
|
|
25
36
|
"*": [
|
|
26
37
|
"./dist/client.d.ts"
|
|
27
38
|
]
|