@mdcrty/paint 0.1.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/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # @mdcrty/paint
2
+
3
+ A zero-dependency React canvas paint component with built-in brush, eraser, and bucket fill tools.
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - Brush, eraser, and bucket fill tools
10
+ - Dynamic slider — size in px for brush/eraser, tolerance % for bucket
11
+ - Colour palette with custom colour picker
12
+ - Brush/eraser size preview cursor (visible on light and dark backgrounds)
13
+ - Built-in save (PNG download) and clear actions
14
+ - Fully customisable via `classNames` or a `renderControls` render prop
15
+ - No runtime dependencies beyond React
16
+
17
+ ---
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @mdcrty/paint
23
+ ```
24
+
25
+ Peer dependencies: `react >= 18`, `react-dom >= 18`
26
+
27
+ ---
28
+
29
+ ## Basic Usage
30
+
31
+ ```tsx
32
+ import { Paint } from "@mdcrty/paint";
33
+
34
+ export default function Page() {
35
+ return <Paint controls />;
36
+ }
37
+ ```
38
+
39
+ `controls` enables the built-in toolbar (tools, slider, palette, save/clear buttons).
40
+
41
+ ---
42
+
43
+ ## Props
44
+
45
+ ```ts
46
+ type PaintProps = {
47
+ controls?: boolean;
48
+ colors?: string[];
49
+ fillTolerance?: number;
50
+ renderControls?: (state: PaintState) => ReactNode;
51
+ classNames?: PaintClassNames;
52
+ };
53
+ ```
54
+
55
+ | Prop | Default | Description |
56
+ |---|---|---|
57
+ | `controls` | `false` | Show the built-in toolbar |
58
+ | `colors` | `["#000", "#EF626C", "#FDEC03", "#24D102", "#FFF"]` | Preset colour swatches |
59
+ | `fillTolerance` | `80` | Initial bucket tolerance (0–128, raw per-channel RGBA delta) |
60
+ | `renderControls` | — | Replace the built-in toolbar entirely with your own UI |
61
+ | `classNames` | — | Override class names on individual toolbar slots |
62
+
63
+ ---
64
+
65
+ ## Imperative API
66
+
67
+ Access via `ref` to trigger actions from outside the component:
68
+
69
+ ```tsx
70
+ import { useRef } from "react";
71
+ import { Paint, PaintHandle } from "@mdcrty/paint";
72
+
73
+ export default function Page() {
74
+ const paintRef = useRef<PaintHandle>(null);
75
+
76
+ return (
77
+ <>
78
+ <Paint ref={paintRef} controls />
79
+ <button onClick={() => paintRef.current?.clearCanvas()}>Clear</button>
80
+ <button onClick={() => paintRef.current?.saveImage()}>Save</button>
81
+ </>
82
+ );
83
+ }
84
+ ```
85
+
86
+ ```ts
87
+ type PaintHandle = {
88
+ clearCanvas(): void;
89
+ saveImage(): void;
90
+ };
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Custom Controls
96
+
97
+ Use `renderControls` to replace the built-in toolbar with your own UI. All canvas state and actions are passed in:
98
+
99
+ ```tsx
100
+ <Paint
101
+ renderControls={({ marker, setMarker, toolSelection, setToolSelection, clearCanvas }) => (
102
+ <div>
103
+ <button onClick={() => setToolSelection("brush")}>Brush</button>
104
+ <button onClick={() => setToolSelection("eraser")}>Eraser</button>
105
+ <button onClick={() => setMarker("#ff0000")}>Red</button>
106
+ <button onClick={clearCanvas}>Clear</button>
107
+ </div>
108
+ )}
109
+ />
110
+ ```
111
+
112
+ ```ts
113
+ type PaintState = {
114
+ marker: string;
115
+ setMarker: (color: string) => void;
116
+ markerWidth: number;
117
+ setMarkerWidth: (width: number) => void;
118
+ toolSelection: string;
119
+ setToolSelection: (tool: string) => void;
120
+ customColor: string;
121
+ setCustomColor: (color: string) => void;
122
+ fillTolerance: number;
123
+ setFillTolerance: (v: number) => void;
124
+ colors: string[];
125
+ clearCanvas: () => void;
126
+ saveImage: () => void;
127
+ };
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Styling Slots
133
+
134
+ Pass `classNames` to override individual elements in the built-in toolbar:
135
+
136
+ ```tsx
137
+ <Paint
138
+ controls
139
+ classNames={{
140
+ tools: styles.tools,
141
+ colors: styles.colors,
142
+ }}
143
+ />
144
+ ```
145
+
146
+ ```ts
147
+ type PaintClassNames = {
148
+ control?: string;
149
+ tools?: string;
150
+ brushSize?: string;
151
+ colors?: string;
152
+ clr?: string;
153
+ customClr?: string;
154
+ brush?: string;
155
+ bucket?: string;
156
+ eraser?: string;
157
+ bottomControl?: string;
158
+ btn?: string;
159
+ btnClear?: string;
160
+ btnSave?: string;
161
+ };
162
+ ```
163
+
164
+ ---
165
+
166
+ ## License
167
+
168
+ MIT © mdcrty
169
+
170
+ ## Links
171
+
172
+ - GitHub: https://github.com/mdcrty/mdcrty-packages
173
+ - npm: https://www.npmjs.com/package/@mdcrty/paint
@@ -0,0 +1,216 @@
1
+ .control {
2
+ position: absolute;
3
+ width: 100%;
4
+ top: 10px;
5
+ z-index: 2;
6
+ display: flex;
7
+ justify-content: center;
8
+ align-items: center;
9
+ flex-direction: row;
10
+ flex-wrap: nowrap;
11
+ gap: 24px;
12
+ }
13
+
14
+ .tools {
15
+ display: flex;
16
+ align-items: center;
17
+ gap: 8px;
18
+ }
19
+
20
+ .tool {
21
+ width: 40px;
22
+ height: 40px;
23
+ border-radius: 50%;
24
+ border: 3px solid #ccc;
25
+ cursor: pointer;
26
+ padding: 0;
27
+ background: transparent;
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ color: #000;
32
+ transition: border-color 120ms ease;
33
+ }
34
+
35
+ .tool:hover {
36
+ border-color: #888;
37
+ }
38
+
39
+ .brushSize {
40
+ display: flex;
41
+ justify-content: center;
42
+ align-items: center;
43
+ flex-direction: row;
44
+ flex-wrap: nowrap;
45
+ gap: 24px;
46
+ }
47
+
48
+ .brushSize label {
49
+ min-width: 5ch;
50
+ text-align: right;
51
+ font-variant-numeric: tabular-nums;
52
+ }
53
+
54
+ @media (max-width: 1050px) {
55
+ .brushSize {
56
+ text-align: center;
57
+ position: absolute;
58
+ top: 8px;
59
+ right: 24px;
60
+ z-index: 1;
61
+ }
62
+ }
63
+
64
+ @media (max-width: 768px) {
65
+ .brushSize {
66
+ top: 1px;
67
+ right: 10px;
68
+ }
69
+ }
70
+
71
+ .colors {
72
+ display: flex;
73
+ align-items: center;
74
+ flex-wrap: nowrap;
75
+ gap: 8px;
76
+ flex-direction: row;
77
+ }
78
+
79
+ @media (max-width: 920px) {
80
+ .tools {
81
+ flex-direction: column;
82
+ position: absolute;
83
+ top: 1px;
84
+ left: 8px;
85
+ z-index: 1;
86
+ }
87
+ .colors {
88
+ flex-direction: column;
89
+ position: absolute;
90
+ top: calc(9.5 * 16px);
91
+ left: 8px;
92
+ z-index: 1;
93
+ }
94
+ }
95
+
96
+ .clr {
97
+ width: 40px;
98
+ height: 40px;
99
+ border-radius: 50%;
100
+ border: 3px solid #ccc;
101
+ cursor: pointer;
102
+ padding: 0;
103
+ }
104
+
105
+ .customClr {
106
+ position: relative;
107
+ }
108
+
109
+ .customClr input {
110
+ -webkit-appearance: none;
111
+ width: 40px;
112
+ height: 40px;
113
+ padding: 0;
114
+ border-radius: 50%;
115
+ border: 3px solid #ccc;
116
+ overflow: hidden;
117
+ cursor: pointer;
118
+ display: block;
119
+ }
120
+
121
+ .customClr svg {
122
+ position: absolute;
123
+ top: 9px;
124
+ left: 9px;
125
+ cursor: pointer;
126
+ pointer-events: none;
127
+ }
128
+
129
+ .customClr input::-webkit-color-swatch-wrapper {
130
+ padding: 0;
131
+ }
132
+
133
+ .customClr input::-webkit-color-swatch {
134
+ border: none;
135
+ }
136
+
137
+ .canvas {
138
+ position: absolute;
139
+ top: 0;
140
+ left: 0;
141
+ }
142
+
143
+ /* Brush/eraser size preview — white inner ring + black outer ring, visible on any background */
144
+ .cursorCircle {
145
+ position: fixed;
146
+ border-radius: 50%;
147
+ border: 1px solid rgba(255, 255, 255, 0.9);
148
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.9);
149
+ pointer-events: none;
150
+ transform: translate(-50%, -50%);
151
+ z-index: 9999;
152
+ opacity: 0;
153
+ }
154
+
155
+ .bottomControl {
156
+ position: absolute;
157
+ right: 24px;
158
+ height: 70px;
159
+ width: auto;
160
+ bottom: 0;
161
+ z-index: 1;
162
+ display: flex;
163
+ align-items: center;
164
+ gap: 8px;
165
+ }
166
+
167
+ @media (max-width: 768px) {
168
+ .bottomControl {
169
+ right: 12px;
170
+ height: 50px;
171
+ }
172
+ }
173
+
174
+ .btn {
175
+ padding: 6px 16px;
176
+ border-radius: 4px;
177
+ border: 1px solid transparent;
178
+ cursor: pointer;
179
+ font-size: 14px;
180
+ font-weight: 500;
181
+ color: #fff;
182
+ transition:
183
+ background 120ms ease,
184
+ border-color 120ms ease;
185
+ line-height: 1.4;
186
+ }
187
+
188
+ .btnClear {
189
+ background: #228be6;
190
+ border-color: #228be6;
191
+ }
192
+
193
+ .btnClear:hover {
194
+ background: #1c7ed6;
195
+ border-color: #1c7ed6;
196
+ }
197
+
198
+ .btnClear:active {
199
+ background: #1971c2;
200
+ border-color: #1971c2;
201
+ }
202
+
203
+ .btnSave {
204
+ background: #40c057;
205
+ border-color: #40c057;
206
+ }
207
+
208
+ .btnSave:hover {
209
+ background: #37b24d;
210
+ border-color: #37b24d;
211
+ }
212
+
213
+ .btnSave:active {
214
+ background: #2f9e44;
215
+ border-color: #2f9e44;
216
+ }
@@ -0,0 +1,72 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ declare const PAINT_MOCKDATA: string[];
5
+ /** Imperative handle exposed via ref. */
6
+ type PaintHandle = {
7
+ clearCanvas(): void;
8
+ saveImage(): void;
9
+ };
10
+ /**
11
+ * State and actions passed to the `renderControls` render prop.
12
+ * Use this to build fully custom control UIs while the canvas logic stays in Paint.
13
+ */
14
+ type PaintState = {
15
+ marker: string;
16
+ setMarker: (color: string) => void;
17
+ markerWidth: number;
18
+ setMarkerWidth: (width: number) => void;
19
+ toolSelection: string;
20
+ setToolSelection: (tool: string) => void;
21
+ customColor: string;
22
+ setCustomColor: (color: string) => void;
23
+ fillTolerance: number;
24
+ setFillTolerance: (v: number) => void;
25
+ colors: string[];
26
+ clearCanvas: () => void;
27
+ saveImage: () => void;
28
+ };
29
+ /** Override class names for individual slots in the built-in control UI. */
30
+ type PaintClassNames = {
31
+ control?: string;
32
+ /** The tools container (brush / eraser / bucket buttons). */
33
+ tools?: string;
34
+ brushSize?: string;
35
+ colors?: string;
36
+ clr?: string;
37
+ customClr?: string;
38
+ /** Applied to the brush tool button. */
39
+ brush?: string;
40
+ bucket?: string;
41
+ eraser?: string;
42
+ bottomControl?: string;
43
+ /** Applied to both action buttons. */
44
+ btn?: string;
45
+ /** Applied to the clear button (in addition to `btn`). */
46
+ btnClear?: string;
47
+ /** Applied to the save button (in addition to `btn`). */
48
+ btnSave?: string;
49
+ };
50
+ type PaintProps = {
51
+ /** Show the built-in controls bar. Ignored when `renderControls` is provided. */
52
+ controls?: boolean;
53
+ /** Preset colour swatches shown in the built-in palette. */
54
+ colors?: string[];
55
+ /**
56
+ * Initial fill tolerance for the bucket tool. Raw per-channel delta on the
57
+ * 0–255 RGBA scale (0 = exact match, 128 = ~50% — the built-in slider ceiling).
58
+ * Displayed as a percentage of 255 in the built-in UI.
59
+ * @default 80
60
+ */
61
+ fillTolerance?: number;
62
+ /**
63
+ * Replace the built-in controls entirely with your own UI.
64
+ * Receives all canvas state and action callbacks.
65
+ */
66
+ renderControls?: (state: PaintState) => ReactNode;
67
+ /** Override individual class names on the built-in control slots. */
68
+ classNames?: PaintClassNames;
69
+ };
70
+ declare const Paint: react.ForwardRefExoticComponent<PaintProps & react.RefAttributes<PaintHandle>>;
71
+
72
+ export { PAINT_MOCKDATA, Paint, type PaintClassNames, type PaintHandle, type PaintProps, type PaintState };
package/dist/index.js ADDED
@@ -0,0 +1,633 @@
1
+ "use client";
2
+
3
+ // src/Paint.tsx
4
+ import {
5
+ forwardRef,
6
+ useEffect,
7
+ useImperativeHandle,
8
+ useRef,
9
+ useState
10
+ } from "react";
11
+ import classes from "./Paint.module-FY7BC2OM.module.css";
12
+
13
+ // src/colorBrightness.ts
14
+ function colorBrightness(color = "") {
15
+ let r = 0, g = 0, b = 0;
16
+ if (color.match(/^rgb/)) {
17
+ const rgb = color.match(/rgba?\(([^)]+)\)/)?.[1].split(/ *, */).map((v) => Number(v)) || [0, 0, 0];
18
+ r = rgb[0];
19
+ g = rgb[1];
20
+ b = rgb[2];
21
+ } else if (color[0] === "#" && color.length === 7) {
22
+ r = parseInt(color.slice(1, 3), 16);
23
+ g = parseInt(color.slice(3, 5), 16);
24
+ b = parseInt(color.slice(5, 7), 16);
25
+ } else if (color[0] === "#" && color.length === 4) {
26
+ r = parseInt(color[1] + color[1], 16);
27
+ g = parseInt(color[2] + color[2], 16);
28
+ b = parseInt(color[3] + color[3], 16);
29
+ }
30
+ return (r * 299 + g * 587 + b * 114) / 1e3 < 125 ? "dark" : "light";
31
+ }
32
+
33
+ // src/Paint.tsx
34
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
35
+ var cx = (...args) => args.filter(Boolean).join(" ");
36
+ var PAINT_MOCKDATA = ["#000", "#EF626C", "#FDEC03", "#24D102", "#FFF"];
37
+ function IconEraser({ size = 22 }) {
38
+ return /* @__PURE__ */ jsxs(
39
+ "svg",
40
+ {
41
+ xmlns: "http://www.w3.org/2000/svg",
42
+ width: size,
43
+ height: size,
44
+ viewBox: "0 0 24 24",
45
+ fill: "none",
46
+ stroke: "currentColor",
47
+ strokeWidth: "2",
48
+ strokeLinecap: "round",
49
+ strokeLinejoin: "round",
50
+ children: [
51
+ /* @__PURE__ */ jsx("path", { d: "M19 20H7l-4-4 9.5-9.5a2 2 0 0 1 2.8 0l3.2 3.2a2 2 0 0 1 0 2.8L10 20" }),
52
+ /* @__PURE__ */ jsx("path", { d: "M6.5 12.5l5 5" })
53
+ ]
54
+ }
55
+ );
56
+ }
57
+ function IconPalette({
58
+ size = 22,
59
+ color = "currentColor"
60
+ }) {
61
+ return /* @__PURE__ */ jsxs(
62
+ "svg",
63
+ {
64
+ xmlns: "http://www.w3.org/2000/svg",
65
+ width: size,
66
+ height: size,
67
+ viewBox: "0 0 24 24",
68
+ fill: "none",
69
+ stroke: color,
70
+ strokeWidth: "2",
71
+ strokeLinecap: "round",
72
+ strokeLinejoin: "round",
73
+ children: [
74
+ /* @__PURE__ */ jsx("path", { d: "M12 21a9 9 0 1 1 0-18c4.97 0 9 3.582 9 8 0 1.06-.474 2.078-1.318 2.828-.844.75-1.989 1.172-3.182 1.172H15a2 2 0 0 0-1 3.75A1.3 1.3 0 0 1 12 21" }),
75
+ /* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "10.5", r: "1", fill: color }),
76
+ /* @__PURE__ */ jsx("circle", { cx: "12.5", cy: "7.5", r: "1", fill: color }),
77
+ /* @__PURE__ */ jsx("circle", { cx: "16.5", cy: "10.5", r: "1", fill: color })
78
+ ]
79
+ }
80
+ );
81
+ }
82
+ function IconBrush({ size = 22 }) {
83
+ return /* @__PURE__ */ jsxs(
84
+ "svg",
85
+ {
86
+ xmlns: "http://www.w3.org/2000/svg",
87
+ width: size,
88
+ height: size,
89
+ viewBox: "0 0 24 24",
90
+ fill: "none",
91
+ stroke: "currentColor",
92
+ strokeWidth: "2",
93
+ strokeLinecap: "round",
94
+ strokeLinejoin: "round",
95
+ children: [
96
+ /* @__PURE__ */ jsx("path", { stroke: "none", d: "M0 0h24v24H0z", fill: "none" }),
97
+ /* @__PURE__ */ jsx("path", { d: "M3 21v-4a4 4 0 1 1 4 4h-4" }),
98
+ /* @__PURE__ */ jsx("path", { d: "M21 3a16 16 0 0 0 -12.8 10.2" }),
99
+ /* @__PURE__ */ jsx("path", { d: "M21 3a16 16 0 0 1 -10.2 12.8" }),
100
+ /* @__PURE__ */ jsx("path", { d: "M10.6 9a9 9 0 0 1 4.4 4.4" })
101
+ ]
102
+ }
103
+ );
104
+ }
105
+ function IconBucket({ size = 22 }) {
106
+ return /* @__PURE__ */ jsxs(
107
+ "svg",
108
+ {
109
+ xmlns: "http://www.w3.org/2000/svg",
110
+ width: size,
111
+ height: size,
112
+ viewBox: "0 0 24 24",
113
+ fill: "none",
114
+ stroke: "currentColor",
115
+ strokeWidth: "2",
116
+ strokeLinecap: "round",
117
+ strokeLinejoin: "round",
118
+ children: [
119
+ /* @__PURE__ */ jsx("path", { stroke: "none", d: "M0 0h24v24H0z", fill: "none" }),
120
+ /* @__PURE__ */ jsx("path", { d: "M5 16l1.465 1.638a2 2 0 1 1 -3.015 .099l1.55 -1.737" }),
121
+ /* @__PURE__ */ jsx("path", { d: "M13.737 9.737c2.299 -2.3 3.23 -5.095 2.081 -6.245c-1.15 -1.15 -3.945 -.217 -6.244 2.082c-2.3 2.299 -3.231 5.095 -2.082 6.244c1.15 1.15 3.946 .218 6.245 -2.081" }),
122
+ /* @__PURE__ */ jsx("path", { d: "M7.492 11.818c.362 .362 .768 .676 1.208 .934l6.895 4.047c1.078 .557 2.255 -.075 3.692 -1.512c1.437 -1.437 2.07 -2.614 1.512 -3.692c-.372 -.718 -1.72 -3.017 -4.047 -6.895a6.015 6.015 0 0 0 -.934 -1.208" })
123
+ ]
124
+ }
125
+ );
126
+ }
127
+ function drawBezierPath(ctx, pts) {
128
+ if (pts.length < 3) {
129
+ const b = pts[0];
130
+ ctx.beginPath();
131
+ ctx.arc(b.x, b.y, ctx.lineWidth / 2, 0, Math.PI * 2, true);
132
+ ctx.fill();
133
+ ctx.closePath();
134
+ return;
135
+ }
136
+ ctx.beginPath();
137
+ ctx.moveTo(pts[0].x, pts[0].y);
138
+ let i;
139
+ for (i = 1; i < pts.length - 2; i++) {
140
+ const c = (pts[i].x + pts[i + 1].x) / 2;
141
+ const d = (pts[i].y + pts[i + 1].y) / 2;
142
+ ctx.quadraticCurveTo(pts[i].x, pts[i].y, c, d);
143
+ }
144
+ ctx.quadraticCurveTo(pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y);
145
+ ctx.stroke();
146
+ }
147
+ function floodFill(ctx, startX, startY, fillColor, tolerance) {
148
+ const canvas = ctx.canvas;
149
+ const w = canvas.width;
150
+ const h = canvas.height;
151
+ const x0 = Math.round(startX);
152
+ const y0 = Math.round(startY);
153
+ if (x0 < 0 || x0 >= w || y0 < 0 || y0 >= h) return;
154
+ const imageData = ctx.getImageData(0, 0, w, h);
155
+ const data = imageData.data;
156
+ const tiny = document.createElement("canvas");
157
+ tiny.width = tiny.height = 1;
158
+ const tc = tiny.getContext("2d");
159
+ tc.fillStyle = fillColor;
160
+ tc.fillRect(0, 0, 1, 1);
161
+ const fill = tc.getImageData(0, 0, 1, 1).data;
162
+ const [fr, fg, fb, fa] = [fill[0], fill[1], fill[2], fill[3]];
163
+ const si = y0 * w + x0;
164
+ const bi = si * 4;
165
+ const [tr, tg, tb, ta] = [data[bi], data[bi + 1], data[bi + 2], data[bi + 3]];
166
+ if (tr === fr && tg === fg && tb === fb && ta === fa) return;
167
+ const matches = (pi) => {
168
+ const i = pi * 4;
169
+ return Math.abs(data[i] - tr) <= tolerance && Math.abs(data[i + 1] - tg) <= tolerance && Math.abs(data[i + 2] - tb) <= tolerance && Math.abs(data[i + 3] - ta) <= tolerance;
170
+ };
171
+ const filled = new Uint8Array(w * h);
172
+ const setPixel = (pi) => {
173
+ const i = pi * 4;
174
+ data[i] = fr;
175
+ data[i + 1] = fg;
176
+ data[i + 2] = fb;
177
+ data[i + 3] = fa;
178
+ filled[pi] = 1;
179
+ };
180
+ const visited = new Uint8Array(w * h);
181
+ const stack = [[x0, y0]];
182
+ visited[si] = 1;
183
+ while (stack.length > 0) {
184
+ const [sx, sy] = stack.pop();
185
+ if (!matches(sy * w + sx)) continue;
186
+ let lx = sx;
187
+ while (lx > 0 && matches(sy * w + lx - 1)) lx--;
188
+ let spanAbove = false;
189
+ let spanBelow = false;
190
+ let rx = lx;
191
+ while (rx < w && matches(sy * w + rx)) {
192
+ setPixel(sy * w + rx);
193
+ if (sy > 0) {
194
+ const above = (sy - 1) * w + rx;
195
+ if (!spanAbove && matches(above) && !visited[above]) {
196
+ visited[above] = 1;
197
+ stack.push([rx, sy - 1]);
198
+ spanAbove = true;
199
+ } else if (spanAbove && !matches(above)) {
200
+ spanAbove = false;
201
+ }
202
+ }
203
+ if (sy < h - 1) {
204
+ const below = (sy + 1) * w + rx;
205
+ if (!spanBelow && matches(below) && !visited[below]) {
206
+ visited[below] = 1;
207
+ stack.push([rx, sy + 1]);
208
+ spanBelow = true;
209
+ } else if (spanBelow && !matches(below)) {
210
+ spanBelow = false;
211
+ }
212
+ }
213
+ rx++;
214
+ }
215
+ }
216
+ for (let py = 0; py < h; py++) {
217
+ for (let px = 0; px < w; px++) {
218
+ const pi = py * w + px;
219
+ if (!filled[pi]) continue;
220
+ if (px > 0 && !filled[pi - 1] && data[(pi - 1) * 4 + 3] < 200)
221
+ setPixel(pi - 1);
222
+ if (px < w - 1 && !filled[pi + 1] && data[(pi + 1) * 4 + 3] < 200)
223
+ setPixel(pi + 1);
224
+ if (py > 0 && !filled[pi - w] && data[(pi - w) * 4 + 3] < 200)
225
+ setPixel(pi - w);
226
+ if (py < h - 1 && !filled[pi + w] && data[(pi + w) * 4 + 3] < 200)
227
+ setPixel(pi + w);
228
+ }
229
+ }
230
+ ctx.putImageData(imageData, 0, 0);
231
+ }
232
+ var Paint = forwardRef(function Paint2({
233
+ controls,
234
+ colors = PAINT_MOCKDATA,
235
+ fillTolerance: fillToleranceProp = 80,
236
+ renderControls,
237
+ classNames = {}
238
+ }, ref) {
239
+ const [marker, setMarker] = useState(colors[0]);
240
+ const [markerWidth, setMarkerWidth] = useState(5);
241
+ const [toolSelection, setToolSelection] = useState("brush");
242
+ const [customColor, setCustomColor] = useState("#A020F0");
243
+ const [fillTolerance, setFillTolerance] = useState(fillToleranceProp);
244
+ const [context, setContext] = useState(null);
245
+ const [tmp_context, setTmpContext] = useState(null);
246
+ const cursorRef = useRef({ x: 0, y: 0 });
247
+ const canvas_ref = useRef(null);
248
+ const tmp_canvas_ref = useRef(null);
249
+ const customColorInputRef = useRef(null);
250
+ const pptsRef = useRef([]);
251
+ const eraserSnapshotRef = useRef(null);
252
+ const cursorCircleRef = useRef(null);
253
+ function setupCanvas(canvas) {
254
+ const dpr = window.devicePixelRatio || 1;
255
+ const rect = { width: window.innerWidth, height: window.innerHeight };
256
+ canvas.width = rect.width * dpr;
257
+ canvas.height = rect.height * dpr;
258
+ const ctx = canvas.getContext("2d");
259
+ ctx.scale(dpr, dpr);
260
+ canvas.width = rect.width;
261
+ canvas.height = rect.height;
262
+ return ctx;
263
+ }
264
+ function clearCanvas() {
265
+ if (!context || !canvas_ref.current) return;
266
+ context.clearRect(
267
+ 0,
268
+ 0,
269
+ canvas_ref.current.width,
270
+ canvas_ref.current.height
271
+ );
272
+ }
273
+ function saveImage() {
274
+ if (!canvas_ref.current) return;
275
+ const data = canvas_ref.current.toDataURL("image/png");
276
+ const a = document.createElement("a");
277
+ a.href = data;
278
+ a.download = "sketch.png";
279
+ a.click();
280
+ a.remove();
281
+ }
282
+ useImperativeHandle(ref, () => ({ clearCanvas, saveImage }));
283
+ useEffect(() => {
284
+ if (!canvas_ref.current || !tmp_canvas_ref.current) return;
285
+ const canvas = canvas_ref.current;
286
+ const tmp_canvas = tmp_canvas_ref.current;
287
+ let ctx = null;
288
+ let tmp_ctx = null;
289
+ if (tmp_context !== null) {
290
+ ctx = context;
291
+ tmp_ctx = tmp_context;
292
+ } else {
293
+ ctx = setupCanvas(canvas);
294
+ setContext(ctx);
295
+ tmp_ctx = setupCanvas(tmp_canvas);
296
+ setTmpContext(tmp_ctx);
297
+ tmp_ctx.lineJoin = "round";
298
+ tmp_ctx.lineCap = "round";
299
+ }
300
+ if (tmp_ctx) {
301
+ tmp_ctx.lineWidth = markerWidth;
302
+ tmp_ctx.strokeStyle = marker;
303
+ tmp_ctx.fillStyle = marker;
304
+ }
305
+ const cursorStart = (ev) => {
306
+ const rect = ev.target?.getBoundingClientRect();
307
+ const isTouch = ev.type === "touchstart";
308
+ let x, y;
309
+ if (isTouch) {
310
+ const tev = ev;
311
+ x = tev.targetTouches[0].pageX - rect.left;
312
+ y = tev.targetTouches[0].pageY - rect.top;
313
+ } else {
314
+ const mev = ev;
315
+ x = typeof mev.offsetX !== "undefined" ? mev.offsetX : mev.layerX;
316
+ y = typeof mev.offsetY !== "undefined" ? mev.offsetY : mev.layerY;
317
+ }
318
+ if (toolSelection === "bucket" && ctx) {
319
+ floodFill(ctx, x, y, marker, fillTolerance);
320
+ return;
321
+ }
322
+ if (isTouch) {
323
+ tmp_canvas.addEventListener("touchmove", onPaint, false);
324
+ } else {
325
+ tmp_canvas.addEventListener("mousemove", onPaint, false);
326
+ }
327
+ cursorRef.current.x = x;
328
+ cursorRef.current.y = y;
329
+ if (toolSelection === "eraser" && ctx) {
330
+ eraserSnapshotRef.current = ctx.getImageData(
331
+ 0,
332
+ 0,
333
+ canvas.width,
334
+ canvas.height
335
+ );
336
+ }
337
+ pptsRef.current.push({ x, y });
338
+ onPaint();
339
+ };
340
+ const cursorMove = (ev) => {
341
+ const rect = ev.target?.getBoundingClientRect();
342
+ let clientX, clientY;
343
+ if (ev.type === "touchmove") {
344
+ const tev = ev;
345
+ cursorRef.current.x = tev.targetTouches[0].pageX - rect.left;
346
+ cursorRef.current.y = tev.targetTouches[0].pageY - rect.top;
347
+ clientX = tev.targetTouches[0].clientX;
348
+ clientY = tev.targetTouches[0].clientY;
349
+ } else {
350
+ const mev = ev;
351
+ cursorRef.current.x = typeof mev.offsetX !== "undefined" ? mev.offsetX : mev.layerX;
352
+ cursorRef.current.y = typeof mev.offsetY !== "undefined" ? mev.offsetY : mev.layerY;
353
+ clientX = mev.clientX;
354
+ clientY = mev.clientY;
355
+ }
356
+ if (cursorCircleRef.current) {
357
+ cursorCircleRef.current.style.left = `${clientX}px`;
358
+ cursorCircleRef.current.style.top = `${clientY}px`;
359
+ }
360
+ };
361
+ const showCursorCircle = () => {
362
+ if (cursorCircleRef.current && toolSelection !== "bucket") {
363
+ cursorCircleRef.current.style.opacity = "1";
364
+ }
365
+ };
366
+ const hideCursorCircle = () => {
367
+ if (cursorCircleRef.current) cursorCircleRef.current.style.opacity = "0";
368
+ };
369
+ const cursorEnd = (ev) => {
370
+ if (ev.type === "touchend") {
371
+ tmp_canvas.removeEventListener("touchmove", onPaint, false);
372
+ } else {
373
+ tmp_canvas.removeEventListener("mousemove", onPaint, false);
374
+ }
375
+ if (!tmp_ctx || !ctx) return;
376
+ if (toolSelection === "eraser") {
377
+ eraserSnapshotRef.current = null;
378
+ } else {
379
+ ctx.globalCompositeOperation = "source-over";
380
+ ctx.drawImage(tmp_canvas, 0, 0);
381
+ tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height);
382
+ }
383
+ pptsRef.current = [];
384
+ cursorRef.current = { x: 0, y: 0 };
385
+ };
386
+ const onPaint = () => {
387
+ if (!tmp_ctx || !ctx) return;
388
+ pptsRef.current.push({ x: cursorRef.current.x, y: cursorRef.current.y });
389
+ if (toolSelection === "eraser") {
390
+ if (eraserSnapshotRef.current) {
391
+ ctx.putImageData(eraserSnapshotRef.current, 0, 0);
392
+ }
393
+ ctx.save();
394
+ ctx.globalCompositeOperation = "destination-out";
395
+ ctx.lineWidth = markerWidth;
396
+ ctx.strokeStyle = "rgba(0,0,0,1)";
397
+ ctx.fillStyle = "rgba(0,0,0,1)";
398
+ ctx.lineJoin = "round";
399
+ ctx.lineCap = "round";
400
+ drawBezierPath(ctx, pptsRef.current);
401
+ ctx.restore();
402
+ return;
403
+ }
404
+ if (pptsRef.current.length < 3) {
405
+ const b = pptsRef.current[0];
406
+ tmp_ctx.beginPath();
407
+ tmp_ctx.arc(b.x, b.y, tmp_ctx.lineWidth / 2, 0, Math.PI * 2, true);
408
+ tmp_ctx.fill();
409
+ tmp_ctx.closePath();
410
+ return;
411
+ }
412
+ tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height);
413
+ drawBezierPath(tmp_ctx, pptsRef.current);
414
+ };
415
+ const handleResize = () => {
416
+ if (!tmp_ctx || !ctx) return;
417
+ tmp_ctx.drawImage(canvas, 0, 0);
418
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
419
+ canvas.width = window.innerWidth;
420
+ canvas.height = window.innerHeight;
421
+ ctx.drawImage(tmp_canvas, 0, 0);
422
+ tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height);
423
+ tmp_canvas.width = canvas.width;
424
+ tmp_canvas.height = canvas.height;
425
+ tmp_ctx.lineWidth = markerWidth;
426
+ tmp_ctx.strokeStyle = marker;
427
+ tmp_ctx.fillStyle = marker;
428
+ tmp_ctx.lineJoin = "round";
429
+ tmp_ctx.lineCap = "round";
430
+ };
431
+ window.addEventListener("resize", handleResize);
432
+ tmp_canvas.addEventListener("mousedown", cursorStart, false);
433
+ tmp_canvas.addEventListener("mousemove", cursorMove, false);
434
+ tmp_canvas.addEventListener("mouseup", cursorEnd, false);
435
+ tmp_canvas.addEventListener("mouseenter", showCursorCircle, false);
436
+ tmp_canvas.addEventListener("mouseleave", hideCursorCircle, false);
437
+ tmp_canvas.addEventListener("touchstart", cursorStart, { passive: true });
438
+ tmp_canvas.addEventListener("touchmove", cursorMove, { passive: true });
439
+ tmp_canvas.addEventListener("touchend", cursorEnd, { passive: true });
440
+ return () => {
441
+ window.removeEventListener("resize", handleResize, false);
442
+ tmp_canvas.removeEventListener("mousedown", cursorStart, false);
443
+ tmp_canvas.removeEventListener("mousemove", cursorMove, false);
444
+ tmp_canvas.removeEventListener("mouseup", cursorEnd, false);
445
+ tmp_canvas.removeEventListener("mouseenter", showCursorCircle, false);
446
+ tmp_canvas.removeEventListener("mouseleave", hideCursorCircle, false);
447
+ tmp_canvas.removeEventListener("touchstart", cursorStart, false);
448
+ tmp_canvas.removeEventListener("touchmove", cursorMove, false);
449
+ tmp_canvas.removeEventListener("touchend", cursorEnd, false);
450
+ };
451
+ }, [marker, markerWidth, fillTolerance, toolSelection, context, tmp_context]);
452
+ const paintState = {
453
+ marker,
454
+ setMarker,
455
+ markerWidth,
456
+ setMarkerWidth,
457
+ toolSelection,
458
+ setToolSelection,
459
+ customColor,
460
+ setCustomColor,
461
+ fillTolerance,
462
+ setFillTolerance,
463
+ colors,
464
+ clearCanvas,
465
+ saveImage
466
+ };
467
+ const colorActive = true;
468
+ const isCustomActive = colorActive && !colors.includes(marker);
469
+ const builtInTopControls = /* @__PURE__ */ jsxs("div", { className: cx(classes.control, classNames.control), children: [
470
+ /* @__PURE__ */ jsxs("div", { className: cx(classes.tools, classNames.tools), children: [
471
+ /* @__PURE__ */ jsx(
472
+ "button",
473
+ {
474
+ className: cx(classes.tool, classNames.brush),
475
+ style: { borderColor: toolSelection === "brush" ? "#000" : "#CCC" },
476
+ onClick: () => setToolSelection("brush"),
477
+ title: "Brush",
478
+ children: /* @__PURE__ */ jsx(IconBrush, { size: 20 })
479
+ }
480
+ ),
481
+ /* @__PURE__ */ jsx(
482
+ "button",
483
+ {
484
+ className: cx(classes.tool, classNames.bucket),
485
+ style: { borderColor: toolSelection === "bucket" ? "#000" : "#CCC" },
486
+ onClick: () => setToolSelection("bucket"),
487
+ title: "Fill",
488
+ children: /* @__PURE__ */ jsx(IconBucket, { size: 20 })
489
+ }
490
+ ),
491
+ /* @__PURE__ */ jsx(
492
+ "button",
493
+ {
494
+ className: cx(classes.tool, classNames.eraser),
495
+ style: { borderColor: toolSelection === "eraser" ? "#000" : "#CCC" },
496
+ onClick: () => setToolSelection("eraser"),
497
+ title: "Eraser",
498
+ children: /* @__PURE__ */ jsx(IconEraser, { size: 20 })
499
+ }
500
+ )
501
+ ] }),
502
+ /* @__PURE__ */ jsx("div", { className: cx(classes.brushSize, classNames.brushSize), children: toolSelection === "bucket" ? /* @__PURE__ */ jsxs(Fragment, { children: [
503
+ /* @__PURE__ */ jsx(
504
+ "input",
505
+ {
506
+ type: "range",
507
+ id: "fillTolerance",
508
+ name: "fillTolerance",
509
+ min: "0",
510
+ max: "128",
511
+ value: fillTolerance,
512
+ step: "1",
513
+ onChange: (e) => setFillTolerance(Number(e.target.value))
514
+ }
515
+ ),
516
+ /* @__PURE__ */ jsxs("label", { htmlFor: "fillTolerance", children: [
517
+ Math.round(fillTolerance / 255 * 100),
518
+ "%"
519
+ ] })
520
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
521
+ /* @__PURE__ */ jsx(
522
+ "input",
523
+ {
524
+ type: "range",
525
+ id: "brushSize",
526
+ name: "brushSize",
527
+ min: "4",
528
+ max: "100",
529
+ value: markerWidth,
530
+ step: "2",
531
+ onChange: (e) => setMarkerWidth(Number(e.target.value))
532
+ }
533
+ ),
534
+ /* @__PURE__ */ jsxs("label", { htmlFor: "brushSize", children: [
535
+ markerWidth,
536
+ "px"
537
+ ] })
538
+ ] }) }),
539
+ /* @__PURE__ */ jsxs("div", { className: cx(classes.colors, classNames.colors), children: [
540
+ colors.map((color, i) => /* @__PURE__ */ jsx(
541
+ "button",
542
+ {
543
+ className: cx(classes.clr, classNames.clr),
544
+ style: {
545
+ borderColor: colorActive && marker === color ? "#000" : "#CCC",
546
+ backgroundColor: color
547
+ },
548
+ onClick: () => setMarker(color)
549
+ },
550
+ i
551
+ )),
552
+ /* @__PURE__ */ jsxs("div", { className: cx(classes.customClr, classNames.customClr), children: [
553
+ /* @__PURE__ */ jsx(
554
+ "input",
555
+ {
556
+ ref: customColorInputRef,
557
+ type: "color",
558
+ name: "custClr",
559
+ defaultValue: customColor,
560
+ style: { borderColor: isCustomActive ? "#000" : "#CCC" },
561
+ onChange: (e) => {
562
+ setMarker(e.target.value);
563
+ setCustomColor(e.target.value);
564
+ }
565
+ }
566
+ ),
567
+ /* @__PURE__ */ jsx(
568
+ IconPalette,
569
+ {
570
+ size: 22,
571
+ color: colorBrightness(customColor) === "dark" ? "white" : "black"
572
+ }
573
+ )
574
+ ] })
575
+ ] })
576
+ ] });
577
+ const builtInBottomControls = /* @__PURE__ */ jsxs("div", { className: cx(classes.bottomControl, classNames.bottomControl), children: [
578
+ /* @__PURE__ */ jsx(
579
+ "button",
580
+ {
581
+ className: cx(
582
+ classes.btn,
583
+ classes.btnClear,
584
+ classNames.btn,
585
+ classNames.btnClear
586
+ ),
587
+ onClick: clearCanvas,
588
+ children: "clear"
589
+ }
590
+ ),
591
+ /* @__PURE__ */ jsx(
592
+ "button",
593
+ {
594
+ className: cx(
595
+ classes.btn,
596
+ classes.btnSave,
597
+ classNames.btn,
598
+ classNames.btnSave
599
+ ),
600
+ onClick: saveImage,
601
+ children: "save"
602
+ }
603
+ )
604
+ ] });
605
+ const showBuiltInControls = controls && !renderControls;
606
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
607
+ renderControls ? renderControls(paintState) : showBuiltInControls && builtInTopControls,
608
+ /* @__PURE__ */ jsx("canvas", { className: classes.canvas, ref: canvas_ref }),
609
+ /* @__PURE__ */ jsx(
610
+ "canvas",
611
+ {
612
+ className: classes.canvas,
613
+ ref: tmp_canvas_ref,
614
+ style: { cursor: toolSelection === "bucket" ? "crosshair" : "none" }
615
+ }
616
+ ),
617
+ /* @__PURE__ */ jsx(
618
+ "div",
619
+ {
620
+ ref: cursorCircleRef,
621
+ className: classes.cursorCircle,
622
+ style: { width: markerWidth, height: markerWidth }
623
+ }
624
+ ),
625
+ showBuiltInControls && builtInBottomControls
626
+ ] });
627
+ });
628
+ var Paint_default = Paint;
629
+ export {
630
+ PAINT_MOCKDATA,
631
+ Paint_default as Paint
632
+ };
633
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/Paint.tsx","../src/colorBrightness.ts"],"sourcesContent":["\"use client\";\n\nimport {\n forwardRef,\n ReactNode,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\nimport classes from \"./Paint.module.css\";\nimport colorBrightness from \"./colorBrightness\";\n\nconst cx = (...args: (string | undefined)[]) => args.filter(Boolean).join(\" \");\n\nexport const PAINT_MOCKDATA = [\"#000\", \"#EF626C\", \"#FDEC03\", \"#24D102\", \"#FFF\"];\n\n// ─── Public types ────────────────────────────────────────────────────────────\n\n/** Imperative handle exposed via ref. */\nexport type PaintHandle = {\n clearCanvas(): void;\n saveImage(): void;\n};\n\n/**\n * State and actions passed to the `renderControls` render prop.\n * Use this to build fully custom control UIs while the canvas logic stays in Paint.\n */\nexport type PaintState = {\n marker: string;\n setMarker: (color: string) => void;\n markerWidth: number;\n setMarkerWidth: (width: number) => void;\n toolSelection: string;\n setToolSelection: (tool: string) => void;\n customColor: string;\n setCustomColor: (color: string) => void;\n fillTolerance: number;\n setFillTolerance: (v: number) => void;\n colors: string[];\n clearCanvas: () => void;\n saveImage: () => void;\n};\n\n/** Override class names for individual slots in the built-in control UI. */\nexport type PaintClassNames = {\n control?: string;\n /** The tools container (brush / eraser / bucket buttons). */\n tools?: string;\n brushSize?: string;\n colors?: string;\n clr?: string;\n customClr?: string;\n /** Applied to the brush tool button. */\n brush?: string;\n bucket?: string;\n eraser?: string;\n bottomControl?: string;\n /** Applied to both action buttons. */\n btn?: string;\n /** Applied to the clear button (in addition to `btn`). */\n btnClear?: string;\n /** Applied to the save button (in addition to `btn`). */\n btnSave?: string;\n};\n\nexport type PaintProps = {\n /** Show the built-in controls bar. Ignored when `renderControls` is provided. */\n controls?: boolean;\n /** Preset colour swatches shown in the built-in palette. */\n colors?: string[];\n /**\n * Initial fill tolerance for the bucket tool. Raw per-channel delta on the\n * 0–255 RGBA scale (0 = exact match, 128 = ~50% — the built-in slider ceiling).\n * Displayed as a percentage of 255 in the built-in UI.\n * @default 80\n */\n fillTolerance?: number;\n /**\n * Replace the built-in controls entirely with your own UI.\n * Receives all canvas state and action callbacks.\n */\n renderControls?: (state: PaintState) => ReactNode;\n /** Override individual class names on the built-in control slots. */\n classNames?: PaintClassNames;\n};\n\n// ─── Inline SVG icons (no external dependency) ───────────────────────────────\n\nfunction IconEraser({ size = 22 }: { size?: number }) {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M19 20H7l-4-4 9.5-9.5a2 2 0 0 1 2.8 0l3.2 3.2a2 2 0 0 1 0 2.8L10 20\" />\n <path d=\"M6.5 12.5l5 5\" />\n </svg>\n );\n}\n\nfunction IconPalette({\n size = 22,\n color = \"currentColor\",\n}: {\n size?: number;\n color?: string;\n}) {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke={color}\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M12 21a9 9 0 1 1 0-18c4.97 0 9 3.582 9 8 0 1.06-.474 2.078-1.318 2.828-.844.75-1.989 1.172-3.182 1.172H15a2 2 0 0 0-1 3.75A1.3 1.3 0 0 1 12 21\" />\n <circle cx=\"8.5\" cy=\"10.5\" r=\"1\" fill={color} />\n <circle cx=\"12.5\" cy=\"7.5\" r=\"1\" fill={color} />\n <circle cx=\"16.5\" cy=\"10.5\" r=\"1\" fill={color} />\n </svg>\n );\n}\n\nfunction IconBrush({ size = 22 }: { size?: number }) {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path d=\"M3 21v-4a4 4 0 1 1 4 4h-4\" />\n <path d=\"M21 3a16 16 0 0 0 -12.8 10.2\" />\n <path d=\"M21 3a16 16 0 0 1 -10.2 12.8\" />\n <path d=\"M10.6 9a9 9 0 0 1 4.4 4.4\" />\n </svg>\n );\n}\n\nfunction IconBucket({ size = 22 }: { size?: number }) {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path d=\"M5 16l1.465 1.638a2 2 0 1 1 -3.015 .099l1.55 -1.737\" />\n <path d=\"M13.737 9.737c2.299 -2.3 3.23 -5.095 2.081 -6.245c-1.15 -1.15 -3.945 -.217 -6.244 2.082c-2.3 2.299 -3.231 5.095 -2.082 6.244c1.15 1.15 3.946 .218 6.245 -2.081\" />\n <path d=\"M7.492 11.818c.362 .362 .768 .676 1.208 .934l6.895 4.047c1.078 .557 2.255 -.075 3.692 -1.512c1.437 -1.437 2.07 -2.614 1.512 -3.692c-.372 -.718 -1.72 -3.017 -4.047 -6.895a6.015 6.015 0 0 0 -.934 -1.208\" />\n </svg>\n );\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\n/**\n * Draws a smooth bezier path through accumulated points onto the given context.\n * Falls back to a filled dot for fewer than 3 points.\n */\nfunction drawBezierPath(\n ctx: CanvasRenderingContext2D,\n pts: Array<{ x: number; y: number }>,\n) {\n if (pts.length < 3) {\n const b = pts[0];\n ctx.beginPath();\n ctx.arc(b.x, b.y, ctx.lineWidth / 2, 0, Math.PI * 2, true);\n ctx.fill();\n ctx.closePath();\n return;\n }\n\n ctx.beginPath();\n ctx.moveTo(pts[0].x, pts[0].y);\n\n let i;\n for (i = 1; i < pts.length - 2; i++) {\n const c = (pts[i].x + pts[i + 1].x) / 2;\n const d = (pts[i].y + pts[i + 1].y) / 2;\n ctx.quadraticCurveTo(pts[i].x, pts[i].y, c, d);\n }\n ctx.quadraticCurveTo(pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y);\n ctx.stroke();\n}\n\n/**\n * Scanline flood fill on a canvas context.\n *\n * Snapshots the pixel data, fills all connected pixels whose colour is within\n * `tolerance` of the target (the pixel at startX/startY), then writes back.\n * The scanline approach processes full horizontal runs at once, making it\n * significantly faster than naive BFS for large fills.\n *\n * A `visited` bitfield prevents infinite loops when `tolerance > 0` causes\n * newly-filled pixels to still match the target colour.\n */\nfunction floodFill(\n ctx: CanvasRenderingContext2D,\n startX: number,\n startY: number,\n fillColor: string,\n tolerance: number,\n) {\n const canvas = ctx.canvas;\n const w = canvas.width;\n const h = canvas.height;\n\n const x0 = Math.round(startX);\n const y0 = Math.round(startY);\n if (x0 < 0 || x0 >= w || y0 < 0 || y0 >= h) return;\n\n const imageData = ctx.getImageData(0, 0, w, h);\n const data = imageData.data;\n\n // Parse fill colour to RGBA via a tiny offscreen canvas — works with any\n // valid CSS colour string (hex, rgb, named, etc.)\n const tiny = document.createElement(\"canvas\");\n tiny.width = tiny.height = 1;\n const tc = tiny.getContext(\"2d\")!;\n tc.fillStyle = fillColor;\n tc.fillRect(0, 0, 1, 1);\n const fill = tc.getImageData(0, 0, 1, 1).data;\n const [fr, fg, fb, fa] = [fill[0], fill[1], fill[2], fill[3]];\n\n // Target colour at the clicked pixel (using pixel index throughout)\n const si = y0 * w + x0;\n const bi = si * 4;\n const [tr, tg, tb, ta] = [data[bi], data[bi + 1], data[bi + 2], data[bi + 3]];\n\n // Already the fill colour — nothing to do\n if (tr === fr && tg === fg && tb === fb && ta === fa) return;\n\n // matches / setPixel both take a pixel index (not byte offset) for clarity\n const matches = (pi: number) => {\n const i = pi * 4;\n return (\n Math.abs(data[i] - tr) <= tolerance &&\n Math.abs(data[i + 1] - tg) <= tolerance &&\n Math.abs(data[i + 2] - tb) <= tolerance &&\n Math.abs(data[i + 3] - ta) <= tolerance\n );\n };\n\n // Track filled pixels so the post-fill boundary pass can find them\n const filled = new Uint8Array(w * h);\n\n const setPixel = (pi: number) => {\n const i = pi * 4;\n data[i] = fr;\n data[i + 1] = fg;\n data[i + 2] = fb;\n data[i + 3] = fa;\n filled[pi] = 1;\n };\n\n // One byte per pixel — prevents duplicate stack pushes (needed when\n // tolerance > 0 means filled pixels can still satisfy `matches`)\n const visited = new Uint8Array(w * h);\n\n const stack: Array<[number, number]> = [[x0, y0]];\n visited[si] = 1;\n\n while (stack.length > 0) {\n const [sx, sy] = stack.pop()!;\n\n if (!matches(sy * w + sx)) continue;\n\n // Scan left to the boundary of the matching region\n let lx = sx;\n while (lx > 0 && matches(sy * w + lx - 1)) lx--;\n\n // Scan right: fill the span and push unvisited neighbours above/below\n let spanAbove = false;\n let spanBelow = false;\n let rx = lx;\n\n while (rx < w && matches(sy * w + rx)) {\n setPixel(sy * w + rx);\n\n if (sy > 0) {\n const above = (sy - 1) * w + rx;\n if (!spanAbove && matches(above) && !visited[above]) {\n visited[above] = 1;\n stack.push([rx, sy - 1]);\n spanAbove = true;\n } else if (spanAbove && !matches(above)) {\n spanAbove = false;\n }\n }\n\n if (sy < h - 1) {\n const below = (sy + 1) * w + rx;\n if (!spanBelow && matches(below) && !visited[below]) {\n visited[below] = 1;\n stack.push([rx, sy + 1]);\n spanBelow = true;\n } else if (spanBelow && !matches(below)) {\n spanBelow = false;\n }\n }\n\n rx++;\n }\n }\n\n // Post-fill pass: eliminate the 1-pixel anti-aliased fringe that scanline\n // fill leaves at stroke edges. Any semi-transparent pixel (alpha < 200)\n // directly adjacent to a filled pixel is also filled.\n for (let py = 0; py < h; py++) {\n for (let px = 0; px < w; px++) {\n const pi = py * w + px;\n if (!filled[pi]) continue;\n if (px > 0 && !filled[pi - 1] && data[(pi - 1) * 4 + 3] < 200)\n setPixel(pi - 1);\n if (px < w - 1 && !filled[pi + 1] && data[(pi + 1) * 4 + 3] < 200)\n setPixel(pi + 1);\n if (py > 0 && !filled[pi - w] && data[(pi - w) * 4 + 3] < 200)\n setPixel(pi - w);\n if (py < h - 1 && !filled[pi + w] && data[(pi + w) * 4 + 3] < 200)\n setPixel(pi + w);\n }\n }\n\n ctx.putImageData(imageData, 0, 0);\n}\n\n// ─── Component ───────────────────────────────────────────────────────────────\n\nconst Paint = forwardRef<PaintHandle, PaintProps>(function Paint(\n {\n controls,\n colors = PAINT_MOCKDATA,\n fillTolerance: fillToleranceProp = 80,\n renderControls,\n classNames = {},\n },\n ref,\n) {\n const [marker, setMarker] = useState(colors[0]);\n const [markerWidth, setMarkerWidth] = useState(5);\n // \"brush\" | \"bucket\" | \"eraser\" — tool is independent of colour choice\n const [toolSelection, setToolSelection] = useState(\"brush\");\n const [customColor, setCustomColor] = useState(\"#A020F0\");\n const [fillTolerance, setFillTolerance] = useState(fillToleranceProp);\n const [context, setContext] = useState<CanvasRenderingContext2D | null>(null);\n const [tmp_context, setTmpContext] =\n useState<CanvasRenderingContext2D | null>(null);\n\n const cursorRef = useRef({ x: 0, y: 0 });\n const canvas_ref = useRef<HTMLCanvasElement>(null);\n const tmp_canvas_ref = useRef<HTMLCanvasElement>(null);\n const customColorInputRef = useRef<HTMLInputElement>(null);\n const pptsRef = useRef<Array<{ x: number; y: number }>>([]);\n // Captured once on eraser mousedown; restored before each paint frame so\n // the eraser stroke is drawn directly on ctx with destination-out live.\n const eraserSnapshotRef = useRef<ImageData | null>(null);\n // Sized circle that follows the cursor for brush/eraser — updated via direct\n // DOM manipulation so mousemove doesn't trigger React re-renders.\n const cursorCircleRef = useRef<HTMLDivElement>(null);\n\n function setupCanvas(canvas: HTMLCanvasElement) {\n const dpr = window.devicePixelRatio || 1;\n const rect = { width: window.innerWidth, height: window.innerHeight };\n canvas.width = rect.width * dpr;\n canvas.height = rect.height * dpr;\n const ctx = canvas.getContext(\"2d\")!;\n ctx.scale(dpr, dpr);\n canvas.width = rect.width;\n canvas.height = rect.height;\n return ctx;\n }\n\n function clearCanvas() {\n if (!context || !canvas_ref.current) return;\n context.clearRect(\n 0,\n 0,\n canvas_ref.current.width,\n canvas_ref.current.height,\n );\n }\n\n function saveImage() {\n if (!canvas_ref.current) return;\n const data = canvas_ref.current.toDataURL(\"image/png\");\n const a = document.createElement(\"a\");\n a.href = data;\n a.download = \"sketch.png\";\n a.click();\n a.remove();\n }\n\n useImperativeHandle(ref, () => ({ clearCanvas, saveImage }));\n\n useEffect(() => {\n if (!canvas_ref.current || !tmp_canvas_ref.current) return;\n\n const canvas = canvas_ref.current;\n const tmp_canvas = tmp_canvas_ref.current;\n\n let ctx: CanvasRenderingContext2D | null = null;\n let tmp_ctx: CanvasRenderingContext2D | null = null;\n\n if (tmp_context !== null) {\n ctx = context;\n tmp_ctx = tmp_context;\n } else {\n ctx = setupCanvas(canvas);\n setContext(ctx);\n tmp_ctx = setupCanvas(tmp_canvas);\n setTmpContext(tmp_ctx);\n tmp_ctx.lineJoin = \"round\";\n tmp_ctx.lineCap = \"round\";\n }\n\n if (tmp_ctx) {\n tmp_ctx.lineWidth = markerWidth;\n tmp_ctx.strokeStyle = marker;\n tmp_ctx.fillStyle = marker;\n }\n\n const cursorStart = (ev: MouseEvent | TouchEvent) => {\n const rect = (ev.target as HTMLElement)?.getBoundingClientRect();\n const isTouch = ev.type === \"touchstart\";\n\n let x: number, y: number;\n if (isTouch) {\n const tev = ev as TouchEvent;\n x = tev.targetTouches[0].pageX - rect.left;\n y = tev.targetTouches[0].pageY - rect.top;\n } else {\n const mev = ev as MouseEvent;\n x = typeof mev.offsetX !== \"undefined\" ? mev.offsetX : mev.layerX;\n y = typeof mev.offsetY !== \"undefined\" ? mev.offsetY : mev.layerY;\n }\n\n // Bucket fill is a single click — don't start a stroke\n if (toolSelection === \"bucket\" && ctx) {\n floodFill(ctx, x, y, marker, fillTolerance);\n return;\n }\n\n // Register the move listener for stroke drawing\n if (isTouch) {\n tmp_canvas.addEventListener(\"touchmove\", onPaint, false);\n } else {\n tmp_canvas.addEventListener(\"mousemove\", onPaint, false);\n }\n\n cursorRef.current.x = x;\n cursorRef.current.y = y;\n\n // Snapshot once per eraser stroke so onPaint can restore + redraw live\n if (toolSelection === \"eraser\" && ctx) {\n eraserSnapshotRef.current = ctx.getImageData(\n 0,\n 0,\n canvas.width,\n canvas.height,\n );\n }\n\n pptsRef.current.push({ x, y });\n onPaint();\n };\n\n const cursorMove = (ev: MouseEvent | TouchEvent) => {\n const rect = (ev.target as HTMLElement)?.getBoundingClientRect();\n let clientX: number, clientY: number;\n if (ev.type === \"touchmove\") {\n const tev = ev as TouchEvent;\n cursorRef.current.x = tev.targetTouches[0].pageX - rect.left;\n cursorRef.current.y = tev.targetTouches[0].pageY - rect.top;\n clientX = tev.targetTouches[0].clientX;\n clientY = tev.targetTouches[0].clientY;\n } else {\n const mev = ev as MouseEvent;\n cursorRef.current.x =\n typeof mev.offsetX !== \"undefined\" ? mev.offsetX : mev.layerX;\n cursorRef.current.y =\n typeof mev.offsetY !== \"undefined\" ? mev.offsetY : mev.layerY;\n clientX = mev.clientX;\n clientY = mev.clientY;\n }\n if (cursorCircleRef.current) {\n cursorCircleRef.current.style.left = `${clientX}px`;\n cursorCircleRef.current.style.top = `${clientY}px`;\n }\n };\n\n const showCursorCircle = () => {\n if (cursorCircleRef.current && toolSelection !== \"bucket\") {\n cursorCircleRef.current.style.opacity = \"1\";\n }\n };\n const hideCursorCircle = () => {\n if (cursorCircleRef.current) cursorCircleRef.current.style.opacity = \"0\";\n };\n\n const cursorEnd = (ev: MouseEvent | TouchEvent) => {\n if (ev.type === \"touchend\") {\n tmp_canvas.removeEventListener(\"touchmove\", onPaint, false);\n } else {\n tmp_canvas.removeEventListener(\"mousemove\", onPaint, false);\n }\n\n if (!tmp_ctx || !ctx) return;\n\n if (toolSelection === \"eraser\") {\n // Erasure was already committed live in onPaint; just clean up\n eraserSnapshotRef.current = null;\n } else {\n ctx.globalCompositeOperation = \"source-over\";\n ctx.drawImage(tmp_canvas, 0, 0);\n tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height);\n }\n\n pptsRef.current = [];\n cursorRef.current = { x: 0, y: 0 };\n };\n\n const onPaint = () => {\n if (!tmp_ctx || !ctx) return;\n pptsRef.current.push({ x: cursorRef.current.x, y: cursorRef.current.y });\n\n if (toolSelection === \"eraser\") {\n // Restore the pre-stroke snapshot then redraw the full accumulated path\n // with destination-out directly on ctx — live transparent erasure.\n if (eraserSnapshotRef.current) {\n ctx.putImageData(eraserSnapshotRef.current, 0, 0);\n }\n ctx.save();\n ctx.globalCompositeOperation = \"destination-out\";\n ctx.lineWidth = markerWidth;\n ctx.strokeStyle = \"rgba(0,0,0,1)\";\n ctx.fillStyle = \"rgba(0,0,0,1)\";\n ctx.lineJoin = \"round\";\n ctx.lineCap = \"round\";\n drawBezierPath(ctx, pptsRef.current);\n ctx.restore();\n return;\n }\n\n if (pptsRef.current.length < 3) {\n const b = pptsRef.current[0];\n tmp_ctx.beginPath();\n tmp_ctx.arc(b.x, b.y, tmp_ctx.lineWidth / 2, 0, Math.PI * 2, true);\n tmp_ctx.fill();\n tmp_ctx.closePath();\n return;\n }\n\n tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height);\n drawBezierPath(tmp_ctx, pptsRef.current);\n };\n\n const handleResize = () => {\n if (!tmp_ctx || !ctx) return;\n tmp_ctx.drawImage(canvas, 0, 0);\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n canvas.width = window.innerWidth;\n canvas.height = window.innerHeight;\n ctx.drawImage(tmp_canvas, 0, 0);\n tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height);\n tmp_canvas.width = canvas.width;\n tmp_canvas.height = canvas.height;\n tmp_ctx.lineWidth = markerWidth;\n tmp_ctx.strokeStyle = marker;\n tmp_ctx.fillStyle = marker;\n tmp_ctx.lineJoin = \"round\";\n tmp_ctx.lineCap = \"round\";\n };\n\n window.addEventListener(\"resize\", handleResize);\n tmp_canvas.addEventListener(\"mousedown\", cursorStart, false);\n tmp_canvas.addEventListener(\"mousemove\", cursorMove, false);\n tmp_canvas.addEventListener(\"mouseup\", cursorEnd, false);\n tmp_canvas.addEventListener(\"mouseenter\", showCursorCircle, false);\n tmp_canvas.addEventListener(\"mouseleave\", hideCursorCircle, false);\n tmp_canvas.addEventListener(\"touchstart\", cursorStart, { passive: true });\n tmp_canvas.addEventListener(\"touchmove\", cursorMove, { passive: true });\n tmp_canvas.addEventListener(\"touchend\", cursorEnd, { passive: true });\n\n return () => {\n window.removeEventListener(\"resize\", handleResize, false);\n tmp_canvas.removeEventListener(\"mousedown\", cursorStart, false);\n tmp_canvas.removeEventListener(\"mousemove\", cursorMove, false);\n tmp_canvas.removeEventListener(\"mouseup\", cursorEnd, false);\n tmp_canvas.removeEventListener(\"mouseenter\", showCursorCircle, false);\n tmp_canvas.removeEventListener(\"mouseleave\", hideCursorCircle, false);\n tmp_canvas.removeEventListener(\"touchstart\", cursorStart, false);\n tmp_canvas.removeEventListener(\"touchmove\", cursorMove, false);\n tmp_canvas.removeEventListener(\"touchend\", cursorEnd, false);\n };\n }, [marker, markerWidth, fillTolerance, toolSelection, context, tmp_context]);\n\n // ─── Shared state object for renderControls ─────────────────────────────────\n\n const paintState: PaintState = {\n marker,\n setMarker,\n markerWidth,\n setMarkerWidth,\n toolSelection,\n setToolSelection,\n customColor,\n setCustomColor,\n fillTolerance,\n setFillTolerance,\n colors,\n clearCanvas,\n saveImage,\n };\n\n // ─── Built-in controls ───────────────────────────────────────────────────────\n\n // Active-colour outline disappears when eraser is selected (colour is irrelevant then)\n // const colorActive = toolSelection !== \"eraser\";\n const colorActive = true;\n // Custom picker is active when the current marker isn't one of the preset swatches\n const isCustomActive = colorActive && !colors.includes(marker);\n\n const builtInTopControls = (\n <div className={cx(classes.control, classNames.control)}>\n {/* Tool selector — independent of colour choice */}\n <div className={cx(classes.tools, classNames.tools)}>\n <button\n className={cx(classes.tool, classNames.brush)}\n style={{ borderColor: toolSelection === \"brush\" ? \"#000\" : \"#CCC\" }}\n onClick={() => setToolSelection(\"brush\")}\n title=\"Brush\"\n >\n <IconBrush size={20} />\n </button>\n <button\n className={cx(classes.tool, classNames.bucket)}\n style={{ borderColor: toolSelection === \"bucket\" ? \"#000\" : \"#CCC\" }}\n onClick={() => setToolSelection(\"bucket\")}\n title=\"Fill\"\n >\n <IconBucket size={20} />\n </button>\n <button\n className={cx(classes.tool, classNames.eraser)}\n style={{ borderColor: toolSelection === \"eraser\" ? \"#000\" : \"#CCC\" }}\n onClick={() => setToolSelection(\"eraser\")}\n title=\"Eraser\"\n >\n <IconEraser size={20} />\n </button>\n </div>\n\n {/* Dynamic slider: size for brush/eraser, tolerance for bucket */}\n <div className={cx(classes.brushSize, classNames.brushSize)}>\n {toolSelection === \"bucket\" ? (\n <>\n <input\n type=\"range\"\n id=\"fillTolerance\"\n name=\"fillTolerance\"\n min=\"0\"\n max=\"128\"\n value={fillTolerance}\n step=\"1\"\n onChange={(e) => setFillTolerance(Number(e.target.value))}\n />\n <label htmlFor=\"fillTolerance\">\n {Math.round((fillTolerance / 255) * 100)}%\n </label>\n </>\n ) : (\n <>\n <input\n type=\"range\"\n id=\"brushSize\"\n name=\"brushSize\"\n min=\"4\"\n max=\"100\"\n value={markerWidth}\n step=\"2\"\n onChange={(e) => setMarkerWidth(Number(e.target.value))}\n />\n <label htmlFor=\"brushSize\">{markerWidth}px</label>\n </>\n )}\n </div>\n\n {/* Colour palette */}\n <div className={cx(classes.colors, classNames.colors)}>\n {colors.map((color, i) => (\n <button\n key={i}\n className={cx(classes.clr, classNames.clr)}\n style={{\n borderColor: colorActive && marker === color ? \"#000\" : \"#CCC\",\n backgroundColor: color,\n }}\n onClick={() => setMarker(color)}\n />\n ))}\n\n <div className={cx(classes.customClr, classNames.customClr)}>\n <input\n ref={customColorInputRef}\n type=\"color\"\n name=\"custClr\"\n defaultValue={customColor}\n style={{ borderColor: isCustomActive ? \"#000\" : \"#CCC\" }}\n onChange={(e) => {\n setMarker(e.target.value);\n setCustomColor(e.target.value);\n }}\n />\n <IconPalette\n size={22}\n color={colorBrightness(customColor) === \"dark\" ? \"white\" : \"black\"}\n />\n </div>\n </div>\n </div>\n );\n\n const builtInBottomControls = (\n <div className={cx(classes.bottomControl, classNames.bottomControl)}>\n <button\n className={cx(\n classes.btn,\n classes.btnClear,\n classNames.btn,\n classNames.btnClear,\n )}\n onClick={clearCanvas}\n >\n clear\n </button>\n <button\n className={cx(\n classes.btn,\n classes.btnSave,\n classNames.btn,\n classNames.btnSave,\n )}\n onClick={saveImage}\n >\n save\n </button>\n </div>\n );\n\n // ─── Render ──────────────────────────────────────────────────────────────────\n\n const showBuiltInControls = controls && !renderControls;\n\n return (\n <>\n {renderControls\n ? renderControls(paintState)\n : showBuiltInControls && builtInTopControls}\n\n <canvas className={classes.canvas} ref={canvas_ref} />\n <canvas\n className={classes.canvas}\n ref={tmp_canvas_ref}\n style={{ cursor: toolSelection === \"bucket\" ? \"crosshair\" : \"none\" }}\n />\n {/* Brush/eraser size preview — position updated via DOM, no re-renders */}\n <div\n ref={cursorCircleRef}\n className={classes.cursorCircle}\n style={{ width: markerWidth, height: markerWidth }}\n />\n\n {showBuiltInControls && builtInBottomControls}\n </>\n );\n});\n\nexport default Paint;\n","export default function colorBrightness(color: string = \"\"): \"dark\" | \"light\" {\n let r = 0, g = 0, b = 0;\n\n if (color.match(/^rgb/)) {\n const rgb = color\n .match(/rgba?\\(([^)]+)\\)/)?.[1]\n .split(/ *, */)\n .map((v) => Number(v)) || [0, 0, 0];\n r = rgb[0]; g = rgb[1]; b = rgb[2];\n } else if (color[0] === \"#\" && color.length === 7) {\n r = parseInt(color.slice(1, 3), 16);\n g = parseInt(color.slice(3, 5), 16);\n b = parseInt(color.slice(5, 7), 16);\n } else if (color[0] === \"#\" && color.length === 4) {\n r = parseInt(color[1] + color[1], 16);\n g = parseInt(color[2] + color[2], 16);\n b = parseInt(color[3] + color[3], 16);\n }\n\n return (r * 299 + g * 587 + b * 114) / 1000 < 125 ? \"dark\" : \"light\";\n}\n"],"mappings":"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","names":["Paint"]}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@mdcrty/paint",
3
+ "version": "0.1.0",
4
+ "description": "A zero-dependency React canvas paint component with customisable controls.",
5
+ "keywords": [
6
+ "react",
7
+ "canvas",
8
+ "paint",
9
+ "drawing",
10
+ "sketch"
11
+ ],
12
+ "license": "MIT",
13
+ "type": "module",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "peerDependencies": {
26
+ "react": ">=18.0.0 <20.0.0",
27
+ "react-dom": ">=18.0.0 <20.0.0"
28
+ },
29
+ "scripts": {
30
+ "build": "tsup"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/mdcrty/mdcrty-packages.git",
35
+ "directory": "packages/paint"
36
+ },
37
+ "bugs": {
38
+ "url": "https://github.com/mdcrty/mdcrty-packages/issues"
39
+ },
40
+ "homepage": "https://github.com/mdcrty/mdcrty-packages/tree/main/packages/paint#readme",
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "sideEffects": [
45
+ "**/*.css"
46
+ ],
47
+ "devDependencies": {}
48
+ }