@haklex/rich-plugin-image-editor 0.24.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.
@@ -0,0 +1,20 @@
1
+ import { AnnotationState } from '@markerjs/markerjs3';
2
+ import { FC, RefObject } from 'react';
3
+ import { AnnotationTool } from './annotation';
4
+ export interface AnnotationApi {
5
+ redo: () => void;
6
+ undo: () => void;
7
+ }
8
+ export interface AnnotationSurfaceProps {
9
+ apiRef: RefObject<AnnotationApi | null>;
10
+ counterRef: RefObject<number>;
11
+ onDecodeError: () => void;
12
+ onHistoryChange: (canUndo: boolean, canRedo: boolean) => void;
13
+ sourceUrl: string;
14
+ stateRef: RefObject<AnnotationState | null>;
15
+ strokeColor: string;
16
+ strokeWidth: number;
17
+ tool: AnnotationTool;
18
+ }
19
+ export declare const AnnotationSurface: FC<AnnotationSurfaceProps>;
20
+ //# sourceMappingURL=AnnotationSurface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnnotationSurface.d.ts","sourceRoot":"","sources":["../src/AnnotationSurface.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAoB,MAAM,qBAAqB,CAAC;AAE7E,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAG3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAInD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,SAAS,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IACxC,UAAU,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;IAC9B,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9D,SAAS,EAAE,MAAM,CAAC;IAElB,QAAQ,EAAE,SAAS,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,cAAc,CAAC;CACtB;AAID,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,sBAAsB,CA2LxD,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { FC } from 'react';
2
+ export interface ImageEditModalProps {
3
+ file: File;
4
+ onCancel: () => void;
5
+ onConfirm: (file: File) => void;
6
+ onSkip: () => void;
7
+ }
8
+ export declare const ImageEditModal: FC<ImageEditModalProps>;
9
+ //# sourceMappingURL=ImageEditModal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageEditModal.d.ts","sourceRoot":"","sources":["../src/ImageEditModal.tsx"],"names":[],"mappings":"AAAA,OAAO,qCAAqC,CAAC;AAa7C,OAAO,KAAK,EAAE,EAAE,EAAkB,MAAM,OAAO,CAAC;AA0BhD,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAChC,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,eAAO,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CA2LlD,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { FC } from 'react';
2
+ export declare const ImageEditModalPlugin: FC;
3
+ //# sourceMappingURL=ImageEditModalPlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageEditModalPlugin.d.ts","sourceRoot":"","sources":["../src/ImageEditModalPlugin.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAMhC,eAAO,MAAM,oBAAoB,EAAE,EAkDlC,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { FC } from 'react';
2
+ export interface ToolOptionsBarProps {
3
+ canRedo: boolean;
4
+ canUndo: boolean;
5
+ onRedo: () => void;
6
+ onStrokeColorChange: (color: string) => void;
7
+ onStrokeWidthChange: (width: number) => void;
8
+ onUndo: () => void;
9
+ showStrokeWidth: boolean;
10
+ strokeColor: string;
11
+ strokeWidth: number;
12
+ }
13
+ export declare const ToolOptionsBar: FC<ToolOptionsBarProps>;
14
+ //# sourceMappingURL=ToolOptionsBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToolOptionsBar.d.ts","sourceRoot":"","sources":["../src/ToolOptionsBar.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAKhC,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CAoElD,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { AnnotationState } from '@markerjs/markerjs3';
2
+ import { EditorTool } from './useImageEditorState';
3
+ export type AnnotationTool = Exclude<EditorTool, 'crop'>;
4
+ export declare const MARKER_TYPE_BY_TOOL: Record<AnnotationTool, string>;
5
+ export declare function isAnnotationTool(tool: EditorTool): tool is AnnotationTool;
6
+ export declare const SWATCH_COLORS: readonly ["#ef4444", "#f59e0b", "#22c55e", "#3b82f6", "#171717", "#ffffff"];
7
+ export declare const STROKE_WIDTHS: readonly [2, 4, 6];
8
+ export declare const STROKE_TOOLS: ReadonlySet<AnnotationTool>;
9
+ export declare function offsetMarkerState(state: AnnotationState, dx: number, dy: number): AnnotationState;
10
+ //# sourceMappingURL=annotation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"annotation.d.ts","sourceRoot":"","sources":["../src/annotation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAA2B,MAAM,qBAAqB,CAAC;AAEpF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AAIzD,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAQ9D,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,IAAI,cAAc,CAEzE;AAED,eAAO,MAAM,aAAa,6EAOhB,CAAC;AAEX,eAAO,MAAM,aAAa,oBAAqB,CAAC;AAEhD,eAAO,MAAM,YAAY,EAAE,WAAW,CAAC,cAAc,CAKnD,CAAC;AA0BH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,eAAe,CAKjG"}
@@ -0,0 +1,2 @@
1
+ export declare function buildResult(original: File, canvas?: HTMLCanvasElement | null): Promise<File>;
2
+ //# sourceMappingURL=build-result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-result.d.ts","sourceRoot":"","sources":["../src/build-result.ts"],"names":[],"mappings":"AAIA,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,IAAI,EACd,MAAM,CAAC,EAAE,iBAAiB,GAAG,IAAI,GAChC,OAAO,CAAC,IAAI,CAAC,CAWf"}
package/dist/crop.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ export interface CropRect {
2
+ height: number;
3
+ width: number;
4
+ x: number;
5
+ y: number;
6
+ }
7
+ export declare function clampCropRect(rect: CropRect, imageWidth: number, imageHeight: number): CropRect;
8
+ export declare function isFullImageCrop(rect: CropRect, imageWidth: number, imageHeight: number): boolean;
9
+ export declare function displayedToNatural(rect: CropRect, scaleX: number, scaleY: number): CropRect;
10
+ export declare function pickExportMime(originalType: string): string;
11
+ export declare function loadImage(imageSrc: string): Promise<HTMLImageElement>;
12
+ export declare function extractCrop(imageSrc: string, areaPixels: CropRect): Promise<HTMLCanvasElement>;
13
+ export declare function cropCanvas(source: HTMLCanvasElement, areaPixels: CropRect): HTMLCanvasElement;
14
+ export declare function sourceToCanvas(imageSrc: string): Promise<HTMLCanvasElement>;
15
+ export declare function canvasToObjectUrl(canvas: HTMLCanvasElement): Promise<string>;
16
+ //# sourceMappingURL=crop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crop.d.ts","sourceRoot":"","sources":["../src/crop.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAID,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,QAAQ,CAM/F;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAQhG;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,CAO3F;AAID,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAM3E;AAkBD,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,QAAQ,GACnB,OAAO,CAAC,iBAAiB,CAAC,CAG5B;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,iBAAiB,EAAE,UAAU,EAAE,QAAQ,GAAG,iBAAiB,CAE7F;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAQjF;AAGD,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAQlF"}
@@ -0,0 +1,3 @@
1
+ export { ImageEditModal, type ImageEditModalProps } from './ImageEditModal';
2
+ export { ImageEditModalPlugin } from './ImageEditModalPlugin';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.mjs ADDED
@@ -0,0 +1,740 @@
1
+ import { Circle, Crop, Hash, ImageOff, MoveUpRight, PaintBucket, Pen, Redo2, Square, Type, Undo2 } from "lucide-react";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { ReactCrop } from "react-image-crop";
4
+ import { MarkerArea, Renderer, TextMarkerEditor } from "@markerjs/markerjs3";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ import { useImagePreprocess } from "@haklex/rich-editor/plugins";
7
+ import { presentDialog } from "@haklex/rich-editor-ui";
8
+ import { usePortalTheme } from "@haklex/rich-style-token";
9
+ //#region src/annotation.ts
10
+ var MARKER_TYPE_BY_TOOL = {
11
+ arrow: "ArrowMarker",
12
+ counter: "TextMarker",
13
+ cover: "CoverMarker",
14
+ ellipse: "EllipseFrameMarker",
15
+ pen: "FreehandMarker",
16
+ rect: "FrameMarker",
17
+ text: "TextMarker"
18
+ };
19
+ function isAnnotationTool(tool) {
20
+ return tool !== "crop";
21
+ }
22
+ var SWATCH_COLORS = [
23
+ "#ef4444",
24
+ "#f59e0b",
25
+ "#22c55e",
26
+ "#3b82f6",
27
+ "#171717",
28
+ "#ffffff"
29
+ ];
30
+ var STROKE_WIDTHS = [
31
+ 2,
32
+ 4,
33
+ 6
34
+ ];
35
+ var STROKE_TOOLS = new Set([
36
+ "arrow",
37
+ "ellipse",
38
+ "pen",
39
+ "rect"
40
+ ]);
41
+ function offsetMarker(marker, dx, dy) {
42
+ const next = { ...marker };
43
+ if (typeof next.left === "number") next.left += dx;
44
+ if (typeof next.top === "number") next.top += dy;
45
+ if (typeof next.x1 === "number") next.x1 += dx;
46
+ if (typeof next.y1 === "number") next.y1 += dy;
47
+ if (typeof next.x2 === "number") next.x2 += dx;
48
+ if (typeof next.y2 === "number") next.y2 += dy;
49
+ if (Array.isArray(next.points)) next.points = next.points.map((point) => ({
50
+ x: point.x + dx,
51
+ y: point.y + dy
52
+ }));
53
+ return next;
54
+ }
55
+ function offsetMarkerState(state, dx, dy) {
56
+ return {
57
+ ...state,
58
+ markers: state.markers.map((marker) => offsetMarker(marker, dx, dy))
59
+ };
60
+ }
61
+ //#endregion
62
+ //#region src/styles.css.ts
63
+ var fullscreenPopup = "_1257qq22";
64
+ var root = "_1257qq23";
65
+ var topBar = "_1257qq24";
66
+ var topBarTitle = "_1257qq25";
67
+ var topBarControls = "_1257qq26";
68
+ var toolGroup = "_1257qq27";
69
+ var body = "_1257qq28";
70
+ var toolRail = "_1257qq29";
71
+ var toolButton = "_1257qq2a";
72
+ var toolButtonActive = "_1257qq2b";
73
+ var canvasArea = "_1257qq2d";
74
+ var annotationSurface = "_1257qq2g";
75
+ var optionsBar = "_1257qq2h";
76
+ var swatch = "_1257qq2i";
77
+ var swatchActive = "_1257qq2j";
78
+ var optionButton = "_1257qq2k";
79
+ var optionDivider = "_1257qq2n";
80
+ var errorState = "_1257qq2o";
81
+ var attribution = "_1257qq2p";
82
+ var footer = "_1257qq2q";
83
+ var footerSpacer = "_1257qq2r";
84
+ var ghostButton = "_1257qq2t _1257qq2s";
85
+ var secondaryButton = "_1257qq2v _1257qq2s";
86
+ var primaryButton = "_1257qq2w _1257qq2s";
87
+ //#endregion
88
+ //#region src/AnnotationSurface.tsx
89
+ var TEXT_TOOLS = new Set(["counter", "text"]);
90
+ var AnnotationSurface = ({ apiRef, counterRef, onDecodeError, onHistoryChange, sourceUrl, stateRef, strokeColor, strokeWidth, tool }) => {
91
+ const containerRef = useRef(null);
92
+ const areaRef = useRef(null);
93
+ const syncRef = useRef(() => {});
94
+ const [ready, setReady] = useState(false);
95
+ const settingsRef = useRef({
96
+ strokeColor,
97
+ strokeWidth,
98
+ tool
99
+ });
100
+ settingsRef.current = {
101
+ strokeColor,
102
+ strokeWidth,
103
+ tool
104
+ };
105
+ const callbacksRef = useRef({
106
+ onDecodeError,
107
+ onHistoryChange
108
+ });
109
+ callbacksRef.current = {
110
+ onDecodeError,
111
+ onHistoryChange
112
+ };
113
+ const styleEditorRef = useRef((editor, isNew) => {});
114
+ styleEditorRef.current = (editor, isNew) => {
115
+ const settings = settingsRef.current;
116
+ if (settings.tool === "cover") editor.fillColor = settings.strokeColor;
117
+ else if (editor.is(TextMarkerEditor)) {
118
+ editor.color = settings.strokeColor;
119
+ if (isNew && settings.tool === "counter") editor.marker.text = String(counterRef.current);
120
+ } else {
121
+ editor.strokeColor = settings.strokeColor;
122
+ editor.strokeWidth = settings.strokeWidth;
123
+ }
124
+ };
125
+ const armRef = useRef(() => {});
126
+ armRef.current = () => {
127
+ const area = areaRef.current;
128
+ if (!area) return;
129
+ const editor = area.createMarker(MARKER_TYPE_BY_TOOL[settingsRef.current.tool]);
130
+ if (editor) styleEditorRef.current(editor, true);
131
+ };
132
+ useEffect(() => {
133
+ const container = containerRef.current;
134
+ if (!container) return;
135
+ let disposed = false;
136
+ const area = new MarkerArea();
137
+ const img = document.createElement("img");
138
+ img.crossOrigin = "anonymous";
139
+ img.src = sourceUrl;
140
+ const sync = () => {
141
+ stateRef.current = area.getState();
142
+ callbacksRef.current.onHistoryChange(area.isUndoPossible, area.isRedoPossible);
143
+ };
144
+ const handleCreate = () => {
145
+ if (settingsRef.current.tool === "counter") counterRef.current += 1;
146
+ sync();
147
+ if (!TEXT_TOOLS.has(settingsRef.current.tool)) armRef.current();
148
+ };
149
+ let pointerActive = false;
150
+ let armPending = false;
151
+ const tryArm = () => {
152
+ if (disposed) return;
153
+ if (area.currentMarkerEditor || area.selectedMarkerEditors.length > 0) return;
154
+ if (pointerActive) {
155
+ armPending = true;
156
+ return;
157
+ }
158
+ armRef.current();
159
+ };
160
+ const handlePointerDown = () => {
161
+ pointerActive = true;
162
+ };
163
+ const handlePointerRelease = () => {
164
+ pointerActive = false;
165
+ if (!armPending) return;
166
+ armPending = false;
167
+ setTimeout(tryArm, 0);
168
+ };
169
+ container.addEventListener("pointerdown", handlePointerDown, true);
170
+ window.addEventListener("pointerup", handlePointerRelease);
171
+ window.addEventListener("pointercancel", handlePointerRelease);
172
+ const handleDeselect = () => {
173
+ if (!TEXT_TOOLS.has(settingsRef.current.tool)) return;
174
+ setTimeout(tryArm, 0);
175
+ };
176
+ img.decode().then(() => {
177
+ if (disposed) return;
178
+ area.targetImage = img;
179
+ area.autoZoomOut = true;
180
+ area.addEventListener("markercreate", handleCreate);
181
+ area.addEventListener("markerchange", sync);
182
+ area.addEventListener("markerdelete", sync);
183
+ area.addEventListener("areastatechange", sync);
184
+ area.addEventListener("markerdeselect", handleDeselect);
185
+ container.append(area);
186
+ areaRef.current = area;
187
+ if (stateRef.current) area.restoreState(stateRef.current, false);
188
+ syncRef.current = sync;
189
+ setReady(true);
190
+ }).catch(() => {
191
+ if (!disposed) callbacksRef.current.onDecodeError();
192
+ });
193
+ return () => {
194
+ disposed = true;
195
+ container.removeEventListener("pointerdown", handlePointerDown, true);
196
+ window.removeEventListener("pointerup", handlePointerRelease);
197
+ window.removeEventListener("pointercancel", handlePointerRelease);
198
+ if (areaRef.current === area) {
199
+ stateRef.current = area.getState();
200
+ areaRef.current = null;
201
+ }
202
+ syncRef.current = () => {};
203
+ callbacksRef.current.onHistoryChange(false, false);
204
+ area.remove();
205
+ setReady(false);
206
+ };
207
+ }, [
208
+ sourceUrl,
209
+ stateRef,
210
+ counterRef
211
+ ]);
212
+ useEffect(() => {
213
+ if (!ready) return;
214
+ armRef.current();
215
+ }, [ready, tool]);
216
+ useEffect(() => {
217
+ if (!ready) return;
218
+ const area = areaRef.current;
219
+ if (!area) return;
220
+ for (const editor of area.selectedMarkerEditors) styleEditorRef.current(editor, false);
221
+ syncRef.current();
222
+ armRef.current();
223
+ }, [
224
+ ready,
225
+ strokeColor,
226
+ strokeWidth
227
+ ]);
228
+ useEffect(() => {
229
+ apiRef.current = {
230
+ redo: () => {
231
+ const area = areaRef.current;
232
+ if (!area) return;
233
+ area.redo();
234
+ syncRef.current();
235
+ armRef.current();
236
+ },
237
+ undo: () => {
238
+ const area = areaRef.current;
239
+ if (!area) return;
240
+ area.undo();
241
+ syncRef.current();
242
+ armRef.current();
243
+ }
244
+ };
245
+ return () => {
246
+ apiRef.current = null;
247
+ };
248
+ }, [apiRef]);
249
+ return /* @__PURE__ */ jsx("div", {
250
+ className: annotationSurface,
251
+ ref: containerRef
252
+ });
253
+ };
254
+ //#endregion
255
+ //#region src/crop.ts
256
+ var clamp = (value, min, max) => Math.min(Math.max(value, min), max);
257
+ function clampCropRect(rect, imageWidth, imageHeight) {
258
+ const left = clamp(Math.round(rect.x), 0, imageWidth);
259
+ const top = clamp(Math.round(rect.y), 0, imageHeight);
260
+ const right = clamp(Math.round(rect.x + rect.width), left, imageWidth);
261
+ return {
262
+ height: clamp(Math.round(rect.y + rect.height), top, imageHeight) - top,
263
+ width: right - left,
264
+ x: left,
265
+ y: top
266
+ };
267
+ }
268
+ function isFullImageCrop(rect, imageWidth, imageHeight) {
269
+ const clamped = clampCropRect(rect, imageWidth, imageHeight);
270
+ return clamped.x === 0 && clamped.y === 0 && clamped.width === imageWidth && clamped.height === imageHeight;
271
+ }
272
+ function displayedToNatural(rect, scaleX, scaleY) {
273
+ return {
274
+ height: rect.height * scaleY,
275
+ width: rect.width * scaleX,
276
+ x: rect.x * scaleX,
277
+ y: rect.y * scaleY
278
+ };
279
+ }
280
+ var CANVAS_ENCODABLE_TYPES = new Set([
281
+ "image/jpeg",
282
+ "image/png",
283
+ "image/webp"
284
+ ]);
285
+ function pickExportMime(originalType) {
286
+ return CANVAS_ENCODABLE_TYPES.has(originalType) ? originalType : "image/png";
287
+ }
288
+ async function loadImage(imageSrc) {
289
+ const image = new Image();
290
+ image.crossOrigin = "anonymous";
291
+ image.src = imageSrc;
292
+ await image.decode();
293
+ return image;
294
+ }
295
+ function drawCrop(source, rect) {
296
+ if (rect.width === 0 || rect.height === 0) throw new Error("Crop region is empty");
297
+ const canvas = document.createElement("canvas");
298
+ canvas.width = rect.width;
299
+ canvas.height = rect.height;
300
+ const ctx = canvas.getContext("2d");
301
+ if (!ctx) throw new Error("Canvas 2d context unavailable");
302
+ ctx.drawImage(source, rect.x, rect.y, rect.width, rect.height, 0, 0, rect.width, rect.height);
303
+ return canvas;
304
+ }
305
+ async function extractCrop(imageSrc, areaPixels) {
306
+ const image = await loadImage(imageSrc);
307
+ return drawCrop(image, clampCropRect(areaPixels, image.naturalWidth, image.naturalHeight));
308
+ }
309
+ function cropCanvas(source, areaPixels) {
310
+ return drawCrop(source, clampCropRect(areaPixels, source.width, source.height));
311
+ }
312
+ async function sourceToCanvas(imageSrc) {
313
+ const image = await loadImage(imageSrc);
314
+ return drawCrop(image, {
315
+ height: image.naturalHeight,
316
+ width: image.naturalWidth,
317
+ x: 0,
318
+ y: 0
319
+ });
320
+ }
321
+ async function canvasToObjectUrl(canvas) {
322
+ const blob = await new Promise((resolve, reject) => {
323
+ canvas.toBlob((result) => result ? resolve(result) : reject(/* @__PURE__ */ new Error("canvas.toBlob returned null")), "image/png");
324
+ });
325
+ return URL.createObjectURL(blob);
326
+ }
327
+ //#endregion
328
+ //#region src/build-result.ts
329
+ async function buildResult(original, canvas) {
330
+ if (!canvas) return original;
331
+ const type = pickExportMime(original.type);
332
+ const blob = await new Promise((resolve, reject) => {
333
+ canvas.toBlob((result) => result ? resolve(result) : reject(/* @__PURE__ */ new Error("canvas.toBlob returned null")), type);
334
+ });
335
+ return new File([blob], original.name, { type: blob.type || type });
336
+ }
337
+ //#endregion
338
+ //#region src/rasterize.ts
339
+ async function rasterizeAnnotations(imageSrc, state) {
340
+ const image = await loadImage(imageSrc);
341
+ const renderer = new Renderer();
342
+ renderer.targetImage = image;
343
+ renderer.naturalSize = true;
344
+ const canvas = document.createElement("canvas");
345
+ await renderer.rasterize(state, canvas);
346
+ return canvas;
347
+ }
348
+ //#endregion
349
+ //#region src/pipeline.ts
350
+ async function applyCropRebase(sourceUrl, rect, markerState) {
351
+ return {
352
+ bitmapUrl: await canvasToObjectUrl(await extractCrop(sourceUrl, rect)),
353
+ markerState: markerState ? {
354
+ ...offsetMarkerState(markerState, -rect.x, -rect.y),
355
+ height: rect.height,
356
+ width: rect.width
357
+ } : null
358
+ };
359
+ }
360
+ async function exportResult({ hasRebasedBitmap, markerState, original, pendingCropRect, sourceUrl }) {
361
+ const hasMarkers = markerState !== null && markerState.markers.length > 0;
362
+ let canvas = null;
363
+ if (hasMarkers && markerState) {
364
+ canvas = await rasterizeAnnotations(sourceUrl, markerState);
365
+ if (pendingCropRect) canvas = cropCanvas(canvas, pendingCropRect);
366
+ } else if (pendingCropRect) canvas = await extractCrop(sourceUrl, pendingCropRect);
367
+ else if (hasRebasedBitmap) canvas = await sourceToCanvas(sourceUrl);
368
+ return buildResult(original, canvas);
369
+ }
370
+ //#endregion
371
+ //#region src/ToolOptionsBar.tsx
372
+ var ToolOptionsBar = ({ canRedo, canUndo, onRedo, onStrokeColorChange, onStrokeWidthChange, onUndo, showStrokeWidth, strokeColor, strokeWidth }) => /* @__PURE__ */ jsxs("div", {
373
+ className: optionsBar,
374
+ children: [
375
+ SWATCH_COLORS.map((color) => /* @__PURE__ */ jsx("button", {
376
+ "aria-label": `Color ${color}`,
377
+ "aria-pressed": strokeColor === color,
378
+ className: strokeColor === color ? `${swatch} ${swatchActive}` : swatch,
379
+ style: { backgroundColor: color },
380
+ title: color,
381
+ type: "button",
382
+ onClick: () => onStrokeColorChange(color)
383
+ }, color)),
384
+ showStrokeWidth && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", { className: "_1257qq2n" }), STROKE_WIDTHS.map((width) => /* @__PURE__ */ jsx("button", {
385
+ "aria-label": `Stroke width ${width}`,
386
+ "aria-pressed": strokeWidth === width,
387
+ title: `Stroke width ${width}px`,
388
+ type: "button",
389
+ className: strokeWidth === width ? `_1257qq2k _1257qq2l` : "_1257qq2k",
390
+ onClick: () => onStrokeWidthChange(width),
391
+ children: /* @__PURE__ */ jsx("span", {
392
+ className: "_1257qq2m",
393
+ style: {
394
+ height: width + 4,
395
+ width: width + 4
396
+ }
397
+ })
398
+ }, width))] }),
399
+ /* @__PURE__ */ jsx("div", { className: optionDivider }),
400
+ /* @__PURE__ */ jsx("button", {
401
+ "aria-label": "Undo",
402
+ className: optionButton,
403
+ disabled: !canUndo,
404
+ title: "Undo",
405
+ type: "button",
406
+ onClick: onUndo,
407
+ children: /* @__PURE__ */ jsx(Undo2, { size: 16 })
408
+ }),
409
+ /* @__PURE__ */ jsx("button", {
410
+ "aria-label": "Redo",
411
+ className: optionButton,
412
+ disabled: !canRedo,
413
+ title: "Redo",
414
+ type: "button",
415
+ onClick: onRedo,
416
+ children: /* @__PURE__ */ jsx(Redo2, { size: 16 })
417
+ })
418
+ ]
419
+ });
420
+ //#endregion
421
+ //#region src/useImageEditorState.ts
422
+ var FULL_IMAGE_CROP = {
423
+ height: 100,
424
+ unit: "%",
425
+ width: 100,
426
+ x: 0,
427
+ y: 0
428
+ };
429
+ function useImageEditorState(objectUrl) {
430
+ const [activeTool, setActiveTool] = useState("crop");
431
+ const [rebasedBitmapUrl, setRebasedBitmapUrl] = useState(null);
432
+ const [crop, setCrop] = useState(FULL_IMAGE_CROP);
433
+ const [croppedAreaPixels, setCroppedAreaPixels] = useState(null);
434
+ const [naturalSize, setNaturalSize] = useState(null);
435
+ const markerStateRef = useRef(null);
436
+ const rebasedUrlRef = useRef(null);
437
+ rebasedUrlRef.current = rebasedBitmapUrl;
438
+ useEffect(() => () => {
439
+ if (rebasedUrlRef.current) URL.revokeObjectURL(rebasedUrlRef.current);
440
+ }, []);
441
+ const sourceUrl = rebasedBitmapUrl ?? objectUrl;
442
+ const hasPendingCrop = croppedAreaPixels !== null && naturalSize !== null && croppedAreaPixels.width >= 1 && croppedAreaPixels.height >= 1 && !isFullImageCrop(croppedAreaPixels, naturalSize.width, naturalSize.height);
443
+ const resetCrop = () => {
444
+ setCrop(FULL_IMAGE_CROP);
445
+ setCroppedAreaPixels(null);
446
+ };
447
+ const confirmCropAndSwitch = async (tool) => {
448
+ if (!croppedAreaPixels || !naturalSize) return;
449
+ const rect = clampCropRect(croppedAreaPixels, naturalSize.width, naturalSize.height);
450
+ try {
451
+ const rebase = await applyCropRebase(sourceUrl, rect, markerStateRef.current);
452
+ if (rebasedBitmapUrl) URL.revokeObjectURL(rebasedBitmapUrl);
453
+ setRebasedBitmapUrl(rebase.bitmapUrl);
454
+ markerStateRef.current = rebase.markerState;
455
+ resetCrop();
456
+ setNaturalSize({
457
+ height: rect.height,
458
+ width: rect.width
459
+ });
460
+ setActiveTool(tool);
461
+ } catch {}
462
+ };
463
+ return {
464
+ activeTool,
465
+ confirmCropAndSwitch,
466
+ crop,
467
+ croppedAreaPixels,
468
+ hasPendingCrop,
469
+ markerStateRef,
470
+ naturalSize,
471
+ rebasedBitmapUrl,
472
+ resetCrop,
473
+ setActiveTool,
474
+ setCrop,
475
+ setCroppedAreaPixels,
476
+ setNaturalSize,
477
+ sourceUrl
478
+ };
479
+ }
480
+ //#endregion
481
+ //#region src/ImageEditModal.tsx
482
+ var TOOLS = [
483
+ {
484
+ icon: Crop,
485
+ id: "crop",
486
+ label: "Crop"
487
+ },
488
+ {
489
+ icon: MoveUpRight,
490
+ id: "arrow",
491
+ label: "Arrow"
492
+ },
493
+ {
494
+ icon: Pen,
495
+ id: "pen",
496
+ label: "Pen"
497
+ },
498
+ {
499
+ icon: Square,
500
+ id: "rect",
501
+ label: "Rectangle"
502
+ },
503
+ {
504
+ icon: Circle,
505
+ id: "ellipse",
506
+ label: "Ellipse"
507
+ },
508
+ {
509
+ icon: Type,
510
+ id: "text",
511
+ label: "Text"
512
+ },
513
+ {
514
+ icon: Hash,
515
+ id: "counter",
516
+ label: "Counter"
517
+ },
518
+ {
519
+ icon: PaintBucket,
520
+ id: "cover",
521
+ label: "Cover"
522
+ }
523
+ ];
524
+ var ImageEditModal = ({ file, onCancel, onConfirm, onSkip }) => {
525
+ const [objectUrl, setObjectUrl] = useState("");
526
+ const [decodeError, setDecodeError] = useState(false);
527
+ const [exporting, setExporting] = useState(false);
528
+ const [strokeColor, setStrokeColor] = useState(SWATCH_COLORS[0]);
529
+ const [strokeWidth, setStrokeWidth] = useState(STROKE_WIDTHS[1]);
530
+ const [history, setHistory] = useState({
531
+ canRedo: false,
532
+ canUndo: false
533
+ });
534
+ const imgRef = useRef(null);
535
+ const counterRef = useRef(1);
536
+ const annotationApiRef = useRef(null);
537
+ const editor = useImageEditorState(objectUrl);
538
+ const { hasPendingCrop, markerStateRef, sourceUrl } = editor;
539
+ useEffect(() => {
540
+ const url = URL.createObjectURL(file);
541
+ setObjectUrl(url);
542
+ return () => URL.revokeObjectURL(url);
543
+ }, [file]);
544
+ const handleImageLoad = (event) => {
545
+ const { naturalHeight, naturalWidth } = event.currentTarget;
546
+ editor.setNaturalSize({
547
+ height: naturalHeight,
548
+ width: naturalWidth
549
+ });
550
+ };
551
+ const handleCropComplete = (pixelCrop) => {
552
+ const img = imgRef.current;
553
+ if (!img || img.width === 0 || img.height === 0) return;
554
+ editor.setCroppedAreaPixels(displayedToNatural(pixelCrop, img.naturalWidth / img.width, img.naturalHeight / img.height));
555
+ };
556
+ const handleToolClick = (tool) => {
557
+ if (tool === editor.activeTool) return;
558
+ if (editor.activeTool === "crop" && hasPendingCrop) {
559
+ editor.confirmCropAndSwitch(tool);
560
+ return;
561
+ }
562
+ editor.setActiveTool(tool);
563
+ };
564
+ const handleConfirm = async () => {
565
+ setExporting(true);
566
+ try {
567
+ const pendingCropRect = editor.activeTool === "crop" && hasPendingCrop ? editor.croppedAreaPixels : null;
568
+ onConfirm(await exportResult({
569
+ hasRebasedBitmap: editor.rebasedBitmapUrl !== null,
570
+ markerState: markerStateRef.current,
571
+ original: file,
572
+ pendingCropRect,
573
+ sourceUrl
574
+ }));
575
+ } catch {
576
+ onSkip();
577
+ } finally {
578
+ setExporting(false);
579
+ }
580
+ };
581
+ return /* @__PURE__ */ jsxs("div", {
582
+ className: root,
583
+ children: [
584
+ /* @__PURE__ */ jsxs("div", {
585
+ className: topBar,
586
+ children: [/* @__PURE__ */ jsx("span", {
587
+ className: topBarTitle,
588
+ children: "Edit image"
589
+ }), /* @__PURE__ */ jsx("div", {
590
+ className: topBarControls,
591
+ children: !decodeError && (editor.activeTool === "crop" ? /* @__PURE__ */ jsx("button", {
592
+ className: "_1257qq2u _1257qq2t _1257qq2s",
593
+ disabled: !hasPendingCrop,
594
+ type: "button",
595
+ onClick: editor.resetCrop,
596
+ children: "Reset crop"
597
+ }) : /* @__PURE__ */ jsx(ToolOptionsBar, {
598
+ canRedo: history.canRedo,
599
+ canUndo: history.canUndo,
600
+ showStrokeWidth: STROKE_TOOLS.has(editor.activeTool),
601
+ strokeColor,
602
+ strokeWidth,
603
+ onRedo: () => annotationApiRef.current?.redo(),
604
+ onStrokeColorChange: setStrokeColor,
605
+ onStrokeWidthChange: setStrokeWidth,
606
+ onUndo: () => annotationApiRef.current?.undo()
607
+ }))
608
+ })]
609
+ }),
610
+ /* @__PURE__ */ jsxs("div", {
611
+ className: body,
612
+ children: [/* @__PURE__ */ jsx("div", {
613
+ className: toolRail,
614
+ children: TOOLS.map((tool, index) => /* @__PURE__ */ jsxs("span", {
615
+ className: toolGroup,
616
+ children: [/* @__PURE__ */ jsx("button", {
617
+ "aria-label": tool.label,
618
+ "aria-pressed": editor.activeTool === tool.id,
619
+ disabled: decodeError,
620
+ title: tool.label,
621
+ type: "button",
622
+ className: editor.activeTool === tool.id ? `${toolButton} ${toolButtonActive}` : toolButton,
623
+ onClick: () => handleToolClick(tool.id),
624
+ children: /* @__PURE__ */ jsx(tool.icon, { size: 18 })
625
+ }), index === 0 && /* @__PURE__ */ jsx("div", { className: "_1257qq2c" })]
626
+ }, tool.id))
627
+ }), /* @__PURE__ */ jsxs("div", {
628
+ className: canvasArea,
629
+ children: [decodeError ? /* @__PURE__ */ jsxs("div", {
630
+ className: errorState,
631
+ children: [/* @__PURE__ */ jsx(ImageOff, { size: 24 }), /* @__PURE__ */ jsx("span", { children: "This image could not be displayed for editing." })]
632
+ }) : sourceUrl && (isAnnotationTool(editor.activeTool) ? /* @__PURE__ */ jsx(AnnotationSurface, {
633
+ apiRef: annotationApiRef,
634
+ counterRef,
635
+ sourceUrl,
636
+ stateRef: markerStateRef,
637
+ strokeColor,
638
+ strokeWidth,
639
+ tool: editor.activeTool,
640
+ onDecodeError: () => setDecodeError(true),
641
+ onHistoryChange: (canUndo, canRedo) => setHistory({
642
+ canRedo,
643
+ canUndo
644
+ })
645
+ }) : /* @__PURE__ */ jsx(ReactCrop, {
646
+ keepSelection: true,
647
+ className: "_1257qq2e",
648
+ crop: editor.crop,
649
+ onChange: (_, percentCrop) => editor.setCrop(percentCrop),
650
+ onComplete: handleCropComplete,
651
+ children: /* @__PURE__ */ jsx("img", {
652
+ alt: file.name,
653
+ className: "_1257qq2f",
654
+ ref: imgRef,
655
+ src: sourceUrl,
656
+ onError: () => setDecodeError(true),
657
+ onLoad: handleImageLoad
658
+ })
659
+ })), /* @__PURE__ */ jsx("a", {
660
+ className: attribution,
661
+ href: "https://markerjs.com",
662
+ rel: "noreferrer",
663
+ target: "_blank",
664
+ children: "marker.js"
665
+ })]
666
+ })]
667
+ }),
668
+ /* @__PURE__ */ jsxs("div", {
669
+ className: footer,
670
+ children: [
671
+ /* @__PURE__ */ jsx("button", {
672
+ className: ghostButton,
673
+ type: "button",
674
+ onClick: onSkip,
675
+ children: "Upload without editing"
676
+ }),
677
+ /* @__PURE__ */ jsx("div", { className: footerSpacer }),
678
+ /* @__PURE__ */ jsx("button", {
679
+ className: secondaryButton,
680
+ type: "button",
681
+ onClick: onCancel,
682
+ children: "Cancel"
683
+ }),
684
+ /* @__PURE__ */ jsx("button", {
685
+ className: primaryButton,
686
+ disabled: decodeError || exporting,
687
+ type: "button",
688
+ onClick: handleConfirm,
689
+ children: "Upload"
690
+ })
691
+ ]
692
+ })
693
+ ]
694
+ });
695
+ };
696
+ //#endregion
697
+ //#region src/ImageEditModalPlugin.tsx
698
+ var ImageEditModalPlugin = () => {
699
+ const preprocess = useImagePreprocess();
700
+ const portalTheme = usePortalTheme();
701
+ const portalThemeRef = useRef(portalTheme);
702
+ portalThemeRef.current = portalTheme;
703
+ useEffect(() => {
704
+ if (!preprocess) return;
705
+ const fn = (file) => new Promise((resolve) => {
706
+ let settled = false;
707
+ const settle = (result) => {
708
+ if (settled) return;
709
+ settled = true;
710
+ resolve(result);
711
+ };
712
+ const dismiss = presentDialog({
713
+ className: fullscreenPopup,
714
+ content: () => /* @__PURE__ */ jsx(ImageEditModal, {
715
+ file,
716
+ onCancel: () => {
717
+ settle(null);
718
+ dismiss();
719
+ },
720
+ onConfirm: (edited) => {
721
+ settle(edited);
722
+ dismiss();
723
+ },
724
+ onSkip: () => {
725
+ settle("skip");
726
+ dismiss();
727
+ }
728
+ }),
729
+ onClose: () => settle(null),
730
+ portalClassName: portalThemeRef.current.className,
731
+ showCloseButton: false,
732
+ theme: portalThemeRef.current.theme
733
+ });
734
+ });
735
+ return preprocess.register(fn);
736
+ }, [preprocess]);
737
+ return null;
738
+ };
739
+ //#endregion
740
+ export { ImageEditModal, ImageEditModalPlugin };
@@ -0,0 +1,16 @@
1
+ import { AnnotationState } from '@markerjs/markerjs3';
2
+ import { CropRect } from './crop';
3
+ export interface CropRebase {
4
+ bitmapUrl: string;
5
+ markerState: AnnotationState | null;
6
+ }
7
+ export declare function applyCropRebase(sourceUrl: string, rect: CropRect, markerState: AnnotationState | null): Promise<CropRebase>;
8
+ export interface ExportInput {
9
+ hasRebasedBitmap: boolean;
10
+ markerState: AnnotationState | null;
11
+ original: File;
12
+ pendingCropRect: CropRect | null;
13
+ sourceUrl: string;
14
+ }
15
+ export declare function exportResult({ hasRebasedBitmap, markerState, original, pendingCropRect, sourceUrl, }: ExportInput): Promise<File>;
16
+ //# sourceMappingURL=pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAI3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAIvC,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;CACrC;AAED,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,QAAQ,EACd,WAAW,EAAE,eAAe,GAAG,IAAI,GAClC,OAAO,CAAC,UAAU,CAAC,CAarB;AAED,MAAM,WAAW,WAAW;IAC1B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,QAAQ,EAAE,IAAI,CAAC;IACf,eAAe,EAAE,QAAQ,GAAG,IAAI,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,YAAY,CAAC,EACjC,gBAAgB,EAChB,WAAW,EACX,QAAQ,EACR,eAAe,EACf,SAAS,GACV,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAa7B"}
@@ -0,0 +1,3 @@
1
+ import { AnnotationState } from '@markerjs/markerjs3';
2
+ export declare function rasterizeAnnotations(imageSrc: string, state: AnnotationState): Promise<HTMLCanvasElement>;
3
+ //# sourceMappingURL=rasterize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rasterize.d.ts","sourceRoot":"","sources":["../src/rasterize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAK3D,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,iBAAiB,CAAC,CAQ5B"}
@@ -0,0 +1,2 @@
1
+ @keyframes marching-ants{0%{background-position:0 0,0 100%,0 0,100% 0}to{background-position:20px 0,-20px 100%,0 -20px,100% 20px}}:root{--rc-drag-handle-size:12px;--rc-drag-handle-mobile-size:24px;--rc-drag-handle-bg-colour:#0003;--rc-drag-bar-size:6px;--rc-border-color:#ffffffb3;--rc-focus-color:#08f}.ReactCrop{cursor:crosshair;max-width:100%;display:inline-block;position:relative}.ReactCrop *,.ReactCrop :before,.ReactCrop :after{box-sizing:border-box}.ReactCrop--disabled,.ReactCrop--locked{cursor:inherit}.ReactCrop__child-wrapper{max-height:inherit;overflow:hidden}.ReactCrop__child-wrapper>img,.ReactCrop__child-wrapper>video{max-width:100%;max-height:inherit;display:block}.ReactCrop:not(.ReactCrop--disabled) .ReactCrop__child-wrapper>img,.ReactCrop:not(.ReactCrop--disabled) .ReactCrop__child-wrapper>video,.ReactCrop:not(.ReactCrop--disabled) .ReactCrop__crop-selection{touch-action:none}.ReactCrop__crop-mask{pointer-events:none;width:calc(100% + .5px);height:calc(100% + .5px);position:absolute;top:0;bottom:0;left:0;right:0}.ReactCrop__crop-selection{cursor:move;position:absolute;top:0;left:0;transform:translateZ(0)}.ReactCrop--disabled .ReactCrop__crop-selection{cursor:inherit}.ReactCrop--circular-crop .ReactCrop__crop-selection{border-radius:50%}.ReactCrop--circular-crop .ReactCrop__crop-selection:after{pointer-events:none;content:"";border:1px solid var(--rc-border-color);opacity:.3;position:absolute;top:-1px;bottom:-1px;left:-1px;right:-1px}.ReactCrop--no-animate .ReactCrop__crop-selection{outline:1px dashed #fff}.ReactCrop__crop-selection:not(.ReactCrop--no-animate .ReactCrop__crop-selection){color:#fff;background-image:linear-gradient(90deg,#fff 50%,#444 50%),linear-gradient(90deg,#fff 50%,#444 50%),linear-gradient(#fff 50%,#444 50%),linear-gradient(#fff 50%,#444 50%);background-position:0 0,0 100%,0 0,100% 0;background-repeat:repeat-x,repeat-x,repeat-y,repeat-y;background-size:10px 1px,10px 1px,1px 10px,1px 10px;animation:1s linear infinite marching-ants}.ReactCrop__crop-selection:focus{outline:2px solid var(--rc-focus-color);outline-offset:-1px}.ReactCrop--invisible-crop .ReactCrop__crop-mask,.ReactCrop--invisible-crop .ReactCrop__crop-selection{display:none}.ReactCrop__rule-of-thirds-vt:before,.ReactCrop__rule-of-thirds-vt:after,.ReactCrop__rule-of-thirds-hz:before,.ReactCrop__rule-of-thirds-hz:after{content:"";background-color:#fff6;display:block;position:absolute}.ReactCrop__rule-of-thirds-vt:before,.ReactCrop__rule-of-thirds-vt:after{width:1px;height:100%}.ReactCrop__rule-of-thirds-vt:before{left:33.3333%}.ReactCrop__rule-of-thirds-vt:after{left:66.6667%}.ReactCrop__rule-of-thirds-hz:before,.ReactCrop__rule-of-thirds-hz:after{width:100%;height:1px}.ReactCrop__rule-of-thirds-hz:before{top:33.3333%}.ReactCrop__rule-of-thirds-hz:after{top:66.6667%}.ReactCrop__drag-handle{width:var(--rc-drag-handle-size);height:var(--rc-drag-handle-size);background-color:var(--rc-drag-handle-bg-colour);border:1px solid var(--rc-border-color);position:absolute}.ReactCrop__drag-handle:focus{background:var(--rc-focus-color)}.ReactCrop .ord-nw{cursor:nw-resize;top:0;left:0;transform:translate(-50%,-50%)}.ReactCrop .ord-n{cursor:n-resize;top:0;left:50%;transform:translate(-50%,-50%)}.ReactCrop .ord-ne{cursor:ne-resize;top:0;right:0;transform:translate(50%,-50%)}.ReactCrop .ord-e{cursor:e-resize;top:50%;right:0;transform:translate(50%,-50%)}.ReactCrop .ord-se{cursor:se-resize;bottom:0;right:0;transform:translate(50%,50%)}.ReactCrop .ord-s{cursor:s-resize;bottom:0;left:50%;transform:translate(-50%,50%)}.ReactCrop .ord-sw{cursor:sw-resize;bottom:0;left:0;transform:translate(-50%,50%)}.ReactCrop .ord-w{cursor:w-resize;top:50%;left:0;transform:translate(-50%,-50%)}.ReactCrop__disabled .ReactCrop__drag-handle{cursor:inherit}.ReactCrop__drag-bar{position:absolute}.ReactCrop__drag-bar.ord-n{width:100%;height:var(--rc-drag-bar-size);top:0;left:0;transform:translateY(-50%)}.ReactCrop__drag-bar.ord-e{width:var(--rc-drag-bar-size);height:100%;top:0;right:0;transform:translate(50%)}.ReactCrop__drag-bar.ord-s{width:100%;height:var(--rc-drag-bar-size);bottom:0;left:0;transform:translateY(50%)}.ReactCrop__drag-bar.ord-w{width:var(--rc-drag-bar-size);height:100%;top:0;left:0;transform:translate(-50%)}.ReactCrop--new-crop .ReactCrop__drag-bar,.ReactCrop--new-crop .ReactCrop__drag-handle,.ReactCrop--fixed-aspect .ReactCrop__drag-bar,.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-n,.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-e,.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-s,.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-w{display:none}@media (pointer:coarse){.ReactCrop .ord-n,.ReactCrop .ord-e,.ReactCrop .ord-s,.ReactCrop .ord-w{display:none}.ReactCrop__drag-handle{width:var(--rc-drag-handle-mobile-size);height:var(--rc-drag-handle-mobile-size)}}:root{--rc-text:#000;--rc-text-secondary:#262626;--rc-text-tertiary:#737373;--rc-text-quaternary:#a3a3a3;--rc-bg:#fff;--rc-bg-secondary:#fafafa;--rc-bg-tertiary:#f5f5f5;--rc-fill:#e8e8e8;--rc-fill-secondary:#eee;--rc-fill-tertiary:#f5f5f5;--rc-fill-quaternary:#fafafa;--rc-border:#f5f5f5;--rc-accent:#2563eb;--rc-accent-light:#2563eb20;--rc-link:#2563eb;--rc-code-text:#404040;--rc-code-bg:#f5f5f5;--rc-hr-border:#e5e5e5;--rc-quote-border:#2563eb;--rc-quote-bg:#f5f5f5;--rc-alert-info:#006bb7;--rc-alert-warning:#c50;--rc-alert-tip:#1c0;--rc-alert-caution:#c01;--rc-alert-important:#50c;--rc-max-width:700px;--rc-shadow-top-bar:0 8px 30px #0000001f, 0 2px 8px #0000000f;--rc-shadow-modal:0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;--rc-shadow-menu:0 1px 4px #0000000a, 0 4px 16px #00000014;--rc-space-xs:4px;--rc-space-sm:8px;--rc-space-md:16px;--rc-space-lg:24px;--rc-space-xl:32px;--rc-font-family-sans:"PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-font-family-serif:"Noto Serif CJK SC", "Source Han Serif SC", "Source Han Serif", "source-han-serif-sc", "Songti SC", STSong, "华文宋体", serif;--rc-font-family-kai:"楷体", KaiTi, STKaiti, "Kaiti SC", "LXGW WenKai", "霞鹜文楷", "Noto Serif CJK SC", serif;--rc-font-mono:"SF Mono", SFMono-Regular, ui-monospace, "DejaVu Sans Mono", Menlo, Consolas, monospace;--rc-font-size-2xs:.625em;--rc-font-size-xs:.75em;--rc-font-size-sm:.8125em;--rc-font-size-md:.875em;--rc-font-size-lg:1.25em;--rc-font-size-base:16px;--rc-font-size-small:14px;--rc-line-height:1.7;--rc-line-height-tight:1.4;--rc-font-family:"PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-radius-sm:4px;--rc-radius-md:8px;--rc-radius-lg:12px}:root.dark,[data-theme=dark]{--rc-text:#fafafa;--rc-text-secondary:#a3a3a3;--rc-text-tertiary:#737373;--rc-text-quaternary:#525252;--rc-bg:#0a0a0a;--rc-bg-secondary:#171717;--rc-bg-tertiary:#262626;--rc-fill:#2a2a2a;--rc-fill-secondary:#222;--rc-fill-tertiary:#1a1a1a;--rc-fill-quaternary:#141414;--rc-border:#262626;--rc-accent:#60a5fa;--rc-accent-light:#60a5fa20;--rc-link:#60a5fa;--rc-code-text:#d4d4d4;--rc-code-bg:#262626;--rc-hr-border:#262626;--rc-quote-border:#60a5fa;--rc-quote-bg:#262626;--rc-alert-info:#7db9e5;--rc-alert-warning:#da864a;--rc-alert-tip:#54da48;--rc-alert-caution:#e16973;--rc-alert-important:#9966e0;--rc-shadow-top-bar:0 8px 30px #00000073, 0 2px 8px #0000004d;--rc-shadow-modal:0 10px 15px -3px #0006, 0 4px 6px -4px #00000059;--rc-shadow-menu:0 1px 4px #00000040, 0 4px 16px #0006}._1lodjav0{--rc-text:#000;--rc-text-secondary:#262626;--rc-text-tertiary:#737373;--rc-text-quaternary:#a3a3a3;--rc-bg:#fff;--rc-bg-secondary:#fafafa;--rc-bg-tertiary:#f5f5f5;--rc-fill:#e8e8e8;--rc-fill-secondary:#eee;--rc-fill-tertiary:#f5f5f5;--rc-fill-quaternary:#fafafa;--rc-border:#f5f5f5;--rc-accent:#2563eb;--rc-accent-light:#2563eb20;--rc-link:#2563eb;--rc-code-text:#404040;--rc-code-bg:#f5f5f5;--rc-hr-border:#e5e5e5;--rc-quote-border:#2563eb;--rc-quote-bg:#f5f5f5;--rc-alert-info:#006bb7;--rc-alert-warning:#c50;--rc-alert-tip:#1c0;--rc-alert-caution:#c01;--rc-alert-important:#50c;--rc-max-width:700px;--rc-shadow-top-bar:0 8px 30px #0000001f, 0 2px 8px #0000000f;--rc-shadow-modal:0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;--rc-shadow-menu:0 1px 4px #0000000a, 0 4px 16px #00000014;--rc-space-xs:4px;--rc-space-sm:8px;--rc-space-md:16px;--rc-space-lg:24px;--rc-space-xl:32px;--rc-font-family-sans:"PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-font-family-serif:"Noto Serif CJK SC", "Source Han Serif SC", "Source Han Serif", "source-han-serif-sc", "Songti SC", STSong, "华文宋体", serif;--rc-font-family-kai:"楷体", KaiTi, STKaiti, "Kaiti SC", "LXGW WenKai", "霞鹜文楷", "Noto Serif CJK SC", serif;--rc-font-mono:"SF Mono", SFMono-Regular, ui-monospace, "DejaVu Sans Mono", Menlo, Consolas, monospace;--rc-font-size-2xs:.625em;--rc-font-size-xs:.75em;--rc-font-size-sm:.8125em;--rc-font-size-md:.875em;--rc-font-size-lg:1.25em;--rc-font-size-base:16px;--rc-font-size-small:14px;--rc-line-height:1.7;--rc-line-height-tight:1.4;--rc-font-family:"PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-radius-sm:4px;--rc-radius-md:8px;--rc-radius-lg:12px}._1lodjav1{--rc-text:#000;--rc-text-secondary:#262626;--rc-text-tertiary:#737373;--rc-text-quaternary:#a3a3a3;--rc-bg:#fff;--rc-bg-secondary:#fafafa;--rc-bg-tertiary:#f5f5f5;--rc-fill:#e8e8e8;--rc-fill-secondary:#eee;--rc-fill-tertiary:#f5f5f5;--rc-fill-quaternary:#fafafa;--rc-border:#f5f5f5;--rc-accent:#2563eb;--rc-accent-light:#2563eb20;--rc-link:#2563eb;--rc-code-text:#404040;--rc-code-bg:#f5f5f5;--rc-hr-border:#e5e5e5;--rc-quote-border:#2563eb;--rc-quote-bg:#f5f5f5;--rc-alert-info:#006bb7;--rc-alert-warning:#c50;--rc-alert-tip:#1c0;--rc-alert-caution:#c01;--rc-alert-important:#50c;--rc-max-width:700px;--rc-shadow-top-bar:0 8px 30px #0000001f, 0 2px 8px #0000000f;--rc-shadow-modal:0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;--rc-shadow-menu:0 1px 4px #0000000a, 0 4px 16px #00000014;--rc-space-xs:4px;--rc-space-sm:8px;--rc-space-md:16px;--rc-space-lg:24px;--rc-space-xl:32px;--rc-font-family-sans:"PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-font-family-serif:"Noto Serif CJK SC", "Source Han Serif SC", "Source Han Serif", "source-han-serif-sc", "Songti SC", STSong, "华文宋体", serif;--rc-font-family-kai:"楷体", KaiTi, STKaiti, "Kaiti SC", "LXGW WenKai", "霞鹜文楷", "Noto Serif CJK SC", serif;--rc-font-mono:"SF Mono", SFMono-Regular, ui-monospace, "DejaVu Sans Mono", Menlo, Consolas, monospace;--rc-font-size-2xs:.625em;--rc-font-size-xs:.75em;--rc-font-size-sm:.8125em;--rc-font-size-md:.875em;--rc-font-size-lg:1.25em;--rc-font-size-base:16px;--rc-font-size-small:14px;--rc-line-height:1.8;--rc-line-height-tight:1.4;--rc-font-family:"Noto Serif CJK SC", "Source Han Serif SC", "Source Han Serif", "source-han-serif-sc", "Songti SC", STSong, "华文宋体", serif;--rc-radius-sm:4px;--rc-radius-md:8px;--rc-radius-lg:12px}._1lodjav2{--rc-text:#000;--rc-text-secondary:#262626;--rc-text-tertiary:#737373;--rc-text-quaternary:#a3a3a3;--rc-bg:#fff;--rc-bg-secondary:#fafafa;--rc-bg-tertiary:#f5f5f5;--rc-fill:#e8e8e8;--rc-fill-secondary:#eee;--rc-fill-tertiary:#f5f5f5;--rc-fill-quaternary:#fafafa;--rc-border:#f5f5f5;--rc-accent:#2563eb;--rc-accent-light:#2563eb20;--rc-link:#2563eb;--rc-code-text:#404040;--rc-code-bg:#f5f5f5;--rc-hr-border:#e5e5e5;--rc-quote-border:#a3a3a3;--rc-quote-bg:#fafafa;--rc-alert-info:#006bb7;--rc-alert-warning:#c50;--rc-alert-tip:#1c0;--rc-alert-caution:#c01;--rc-alert-important:#50c;--rc-max-width:none;--rc-shadow-top-bar:0 8px 30px #0000001f, 0 2px 8px #0000000f;--rc-shadow-modal:0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;--rc-shadow-menu:0 1px 4px #0000000a, 0 4px 16px #00000014;--rc-space-xs:2px;--rc-space-sm:4px;--rc-space-md:10px;--rc-space-lg:16px;--rc-space-xl:20px;--rc-font-family-sans:"PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-font-family-serif:"Noto Serif CJK SC", "Source Han Serif SC", "Source Han Serif", "source-han-serif-sc", "Songti SC", STSong, "华文宋体", serif;--rc-font-family-kai:"楷体", KaiTi, STKaiti, "Kaiti SC", "LXGW WenKai", "霞鹜文楷", "Noto Serif CJK SC", serif;--rc-font-mono:"SF Mono", SFMono-Regular, ui-monospace, "DejaVu Sans Mono", Menlo, Consolas, monospace;--rc-font-size-2xs:.625em;--rc-font-size-xs:.75em;--rc-font-size-sm:.8125em;--rc-font-size-md:.875em;--rc-font-size-lg:1.25em;--rc-font-size-base:14px;--rc-font-size-small:12px;--rc-line-height:1.5;--rc-line-height-tight:1.3;--rc-font-family:"PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-radius-sm:3px;--rc-radius-md:6px;--rc-radius-lg:12px}.dark ._1lodjav0,[data-theme=dark] ._1lodjav0,.dark._1lodjav0,[data-theme=dark]._1lodjav0,.dark ._1lodjav1,[data-theme=dark] ._1lodjav1,.dark._1lodjav1,[data-theme=dark]._1lodjav1,.dark ._1lodjav2,[data-theme=dark] ._1lodjav2,.dark._1lodjav2,[data-theme=dark]._1lodjav2{--rc-text:#fafafa;--rc-text-secondary:#a3a3a3;--rc-text-tertiary:#737373;--rc-text-quaternary:#525252;--rc-bg:#0a0a0a;--rc-bg-secondary:#171717;--rc-bg-tertiary:#262626;--rc-fill:#2a2a2a;--rc-fill-secondary:#222;--rc-fill-tertiary:#1a1a1a;--rc-fill-quaternary:#141414;--rc-border:#262626;--rc-accent:#60a5fa;--rc-accent-light:#60a5fa20;--rc-link:#60a5fa;--rc-code-text:#d4d4d4;--rc-code-bg:#262626;--rc-hr-border:#262626;--rc-quote-border:#60a5fa;--rc-quote-bg:#262626;--rc-alert-info:#7db9e5;--rc-alert-warning:#da864a;--rc-alert-tip:#54da48;--rc-alert-caution:#e16973;--rc-alert-important:#9966e0;--rc-shadow-top-bar:0 8px 30px #00000073, 0 2px 8px #0000004d;--rc-shadow-modal:0 10px 15px -3px #0006, 0 4px 6px -4px #00000059;--rc-shadow-menu:0 1px 4px #00000040, 0 4px 16px #0006}@keyframes _1257qq20{0%{opacity:0}to{opacity:1}}@keyframes _1257qq21{0%{opacity:1}to{opacity:0}}._1257qq22._1257qq22{border-radius:0;flex-direction:column;gap:0;width:100vw;max-width:100vw;height:100vh;max-height:100vh;margin:0;padding:0;display:flex;position:fixed;top:0;bottom:0;left:0;right:0;overflow:hidden;transform:none}._1257qq22._1257qq22[data-open]{animation:.15s ease-out _1257qq20}._1257qq22._1257qq22[data-closed]{animation:.1s ease-in _1257qq21}._1257qq23{background-color:var(--rc-bg);min-height:0;color:var(--rc-text);font-family:var(--rc-font-family-sans);flex-direction:column;flex:1;display:flex}._1257qq24{border-bottom:1px solid var(--rc-border);flex-shrink:0;align-items:center;gap:12px;height:48px;padding:0 16px;display:flex}._1257qq25{font-size:14px;font-weight:600}._1257qq26{flex:1;align-items:center;gap:8px;min-width:0;display:flex}._1257qq27{display:contents}._1257qq28{flex:1;min-height:0;display:flex}._1257qq29{border-right:1px solid var(--rc-border);flex-direction:column;flex-shrink:0;align-items:center;gap:4px;width:56px;padding:12px 0;display:flex}._1257qq2a{border-radius:var(--rc-radius-sm);color:#737373;cursor:pointer;background-color:#0000;border:none;justify-content:center;align-items:center;width:36px;height:36px;display:flex}._1257qq2a:hover:not(:disabled){background-color:var(--rc-fill-secondary);color:var(--rc-text)}._1257qq2a:disabled{color:#a3a3a3;cursor:default;opacity:.55}._1257qq2b{background-color:var(--rc-fill);color:var(--rc-text)}._1257qq2b:hover:not(:disabled){background-color:var(--rc-fill)}._1257qq2c{background-color:var(--rc-border);width:28px;height:1px;margin:6px 0}._1257qq2d{background:repeating-conic-gradient(var(--rc-bg-tertiary) 0% 25%, var(--rc-bg) 0% 50%) 50% / 20px 20px;flex:1;justify-content:center;align-items:center;min-width:0;min-height:0;padding:24px;display:flex;position:relative;overflow:hidden}._1257qq2e{max-width:100%;max-height:100%;box-shadow:var(--rc-shadow-modal)}._1257qq2f{max-width:100%;display:block}._1257qq2e .ReactCrop__child-wrapper>._1257qq2f{max-height:calc(100vh - 152px)}._1257qq2g{width:100%;min-width:0;height:100%;min-height:0;display:flex;position:relative}._1257qq2h{align-items:center;gap:6px;margin-left:auto;display:flex}._1257qq2i{cursor:pointer;border:1px solid #73737359;border-radius:50%;width:18px;height:18px;padding:0}._1257qq2j{box-shadow:0 0 0 2px var(--rc-bg), 0 0 0 4px #737373}._1257qq2k{border-radius:var(--rc-radius-sm);color:#737373;cursor:pointer;background-color:#0000;border:none;justify-content:center;align-items:center;width:26px;height:26px;padding:0;display:flex}._1257qq2k:hover:not(:disabled){background-color:var(--rc-fill-secondary);color:var(--rc-text)}._1257qq2k:disabled{color:#a3a3a3;cursor:default;opacity:.55}._1257qq2l{background-color:var(--rc-fill);color:var(--rc-text)}._1257qq2m{background-color:currentColor;border-radius:50%}._1257qq2n{background-color:var(--rc-border);width:1px;height:18px;margin:0 4px}._1257qq2o{border-radius:var(--rc-radius-md);background-color:var(--rc-bg);color:#737373;box-shadow:var(--rc-shadow-modal);flex-direction:column;align-items:center;gap:8px;padding:24px;font-size:13px;display:flex}._1257qq2p{color:#a3a3a3;font-size:11px;text-decoration:none;position:absolute;bottom:8px;right:10px}._1257qq2p:hover{color:#737373;text-decoration:underline}._1257qq2q{border-top:1px solid var(--rc-border);flex-shrink:0;align-items:center;gap:8px;height:56px;padding:0 16px;display:flex}._1257qq2r{flex:1}._1257qq2s{border-radius:var(--rc-radius-sm);cursor:pointer;justify-content:center;align-items:center;height:32px;padding:0 14px;font-size:13px;font-weight:500;display:inline-flex}._1257qq2s:disabled{opacity:.5;cursor:default}._1257qq2t{color:#737373;background-color:#0000;border:none}._1257qq2t:hover:not(:disabled){background-color:var(--rc-fill-secondary);color:var(--rc-text)}._1257qq2u{height:26px;margin-left:auto;padding:0 10px;font-size:12px}._1257qq2v{border:1px solid var(--rc-border);background-color:var(--rc-bg);color:var(--rc-text)}._1257qq2v:hover:not(:disabled){background-color:var(--rc-fill-secondary)}._1257qq2w{background-color:var(--rc-text);color:var(--rc-bg);border:1px solid #0000}._1257qq2w:hover:not(:disabled){opacity:.85}
2
+ /*$vite$:1*/
@@ -0,0 +1,32 @@
1
+ declare const _fullscreenPopup: string;
2
+ export { _fullscreenPopup as fullscreenPopup };
3
+ export declare const root: string;
4
+ export declare const topBar: string;
5
+ export declare const topBarTitle: string;
6
+ export declare const topBarControls: string;
7
+ export declare const toolGroup: string;
8
+ export declare const body: string;
9
+ export declare const toolRail: string;
10
+ export declare const toolButton: string;
11
+ export declare const toolButtonActive: string;
12
+ export declare const toolDivider: string;
13
+ export declare const canvasArea: string;
14
+ export declare const cropSurface: string;
15
+ export declare const cropImage: string;
16
+ export declare const annotationSurface: string;
17
+ export declare const optionsBar: string;
18
+ export declare const swatch: string;
19
+ export declare const swatchActive: string;
20
+ export declare const optionButton: string;
21
+ export declare const optionButtonActive: string;
22
+ export declare const strokeDot: string;
23
+ export declare const optionDivider: string;
24
+ export declare const errorState: string;
25
+ export declare const attribution: string;
26
+ export declare const footer: string;
27
+ export declare const footerSpacer: string;
28
+ export declare const ghostButton: string;
29
+ export declare const topBarGhostButton: string;
30
+ export declare const secondaryButton: string;
31
+ export declare const primaryButton: string;
32
+ //# sourceMappingURL=styles.css.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.css.d.ts","sourceRoot":"","sources":["../src/styles.css.ts"],"names":[],"mappings":"AAcA,QAAA,MAAM,gBAAgB,QAAY,CAAC;AA2BnC,OAAO,EAAE,gBAAgB,IAAI,eAAe,EAAE,CAAC;AAE/C,eAAO,MAAM,IAAI,QAQf,CAAC;AAEH,eAAO,MAAM,MAAM,QAQjB,CAAC;AAEH,eAAO,MAAM,WAAW,QAGtB,CAAC;AAEH,eAAO,MAAM,cAAc,QAMzB,CAAC;AAEH,eAAO,MAAM,SAAS,QAEpB,CAAC;AAEH,eAAO,MAAM,IAAI,QAIf,CAAC;AAEH,eAAO,MAAM,QAAQ,QASnB,CAAC;AAEH,eAAO,MAAM,UAAU,QAsBrB,CAAC;AAEH,eAAO,MAAM,gBAAgB,QAQ3B,CAAC;AAEH,eAAO,MAAM,WAAW,QAKtB,CAAC;AAEH,eAAO,MAAM,UAAU,QAYrB,CAAC;AAEH,eAAO,MAAM,WAAW,QAItB,CAAC;AAEH,eAAO,MAAM,SAAS,QAGpB,CAAC;AAUH,eAAO,MAAM,iBAAiB,QAO5B,CAAC;AAEH,eAAO,MAAM,UAAU,QAKrB,CAAC;AAEH,eAAO,MAAM,MAAM,QAOjB,CAAC;AAEH,eAAO,MAAM,YAAY,QAEvB,CAAC;AAEH,eAAO,MAAM,YAAY,QAuBvB,CAAC;AAEH,eAAO,MAAM,kBAAkB,QAG7B,CAAC;AAEH,eAAO,MAAM,SAAS,QAGpB,CAAC;AAEH,eAAO,MAAM,aAAa,QAKxB,CAAC;AAEH,eAAO,MAAM,UAAU,QAWrB,CAAC;AAEH,eAAO,MAAM,WAAW,QAatB,CAAC;AAEH,eAAO,MAAM,MAAM,QAQjB,CAAC;AAEH,eAAO,MAAM,YAAY,QAEvB,CAAC;AAoBH,eAAO,MAAM,WAAW,QAatB,CAAC;AAEH,eAAO,MAAM,iBAAiB,QAQ5B,CAAC;AAEH,eAAO,MAAM,eAAe,QAY1B,CAAC;AAEH,eAAO,MAAM,aAAa,QAYxB,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { AnnotationState } from '@markerjs/markerjs3';
2
+ import { RefObject } from 'react';
3
+ import { Crop } from 'react-image-crop';
4
+ import { CropRect } from './crop';
5
+ export type EditorTool = 'crop' | 'arrow' | 'pen' | 'rect' | 'ellipse' | 'text' | 'counter' | 'cover';
6
+ export interface ImageNaturalSize {
7
+ height: number;
8
+ width: number;
9
+ }
10
+ export declare const FULL_IMAGE_CROP: Crop;
11
+ export interface ImageEditorState {
12
+ activeTool: EditorTool;
13
+ confirmCropAndSwitch: (tool: EditorTool) => Promise<void>;
14
+ crop: Crop;
15
+ croppedAreaPixels: CropRect | null;
16
+ hasPendingCrop: boolean;
17
+ markerStateRef: RefObject<AnnotationState | null>;
18
+ naturalSize: ImageNaturalSize | null;
19
+ rebasedBitmapUrl: string | null;
20
+ resetCrop: () => void;
21
+ setActiveTool: (tool: EditorTool) => void;
22
+ setCrop: (crop: Crop) => void;
23
+ setCroppedAreaPixels: (area: CropRect | null) => void;
24
+ setNaturalSize: (size: ImageNaturalSize | null) => void;
25
+ sourceUrl: string;
26
+ }
27
+ export declare function useImageEditorState(objectUrl: string): ImageEditorState;
28
+ //# sourceMappingURL=useImageEditorState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useImageEditorState.d.ts","sourceRoot":"","sources":["../src/useImageEditorState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAIvC,MAAM,MAAM,UAAU,GAClB,MAAM,GACN,OAAO,GACP,KAAK,GACL,MAAM,GACN,SAAS,GACT,MAAM,GACN,SAAS,GACT,OAAO,CAAC;AAEZ,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAGD,eAAO,MAAM,eAAe,EAAE,IAAyD,CAAC;AAExF,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,UAAU,CAAC;IAEvB,oBAAoB,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,IAAI,EAAE,IAAI,CAAC;IAEX,iBAAiB,EAAE,QAAQ,GAAG,IAAI,CAAC;IACnC,cAAc,EAAE,OAAO,CAAC;IAExB,cAAc,EAAE,SAAS,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IAClD,WAAW,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAErC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IAC1C,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAC9B,oBAAoB,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC;IACtD,cAAc,EAAE,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,KAAK,IAAI,CAAC;IACxD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAgEvE"}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@haklex/rich-plugin-image-editor",
3
+ "version": "0.24.0",
4
+ "description": "Pre-upload image edit modal plugin (crop + annotate)",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/Innei/haklex.git",
8
+ "directory": "packages/rich-plugin-image-editor"
9
+ },
10
+ "license": "MIT",
11
+ "type": "module",
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/index.mjs",
15
+ "types": "./dist/index.d.ts"
16
+ },
17
+ "./style.css": "./dist/rich-plugin-image-editor.css"
18
+ },
19
+ "main": "./dist/index.mjs",
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "dependencies": {
24
+ "@markerjs/markerjs3": "^3.10.0",
25
+ "react-image-crop": "^11.0.10"
26
+ },
27
+ "devDependencies": {
28
+ "@types/react": "^19.2.15",
29
+ "@types/react-dom": "^19.2.3",
30
+ "@vanilla-extract/css": "^1.20.1",
31
+ "@vanilla-extract/vite-plugin": "^5.2.2",
32
+ "lucide-react": "^1.17.0",
33
+ "react": "19.2.6",
34
+ "react-dom": "19.2.6",
35
+ "typescript": "^5.9.3",
36
+ "unplugin-dts": "^1.0.1",
37
+ "vite": "^8.0.14"
38
+ },
39
+ "peerDependencies": {
40
+ "lucide-react": "^1.0.0",
41
+ "react": ">=19",
42
+ "react-dom": ">=19",
43
+ "@haklex/rich-editor": "0.24.0",
44
+ "@haklex/rich-editor-ui": "0.24.0",
45
+ "@haklex/rich-style-token": "0.24.0"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "scripts": {
51
+ "build": "vite build",
52
+ "dev:build": "vite build --watch"
53
+ },
54
+ "types": "./dist/index.d.ts"
55
+ }