@avament-pub/image-editor 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.
@@ -0,0 +1,4 @@
1
+ import type { ImageEditorLabels } from "./image-editor.types";
2
+ export declare const DEFAULT_IMAGE_EDITOR_LABELS: ImageEditorLabels;
3
+ export declare function mergeImageEditorLabels(overrides?: Partial<ImageEditorLabels>): ImageEditorLabels;
4
+ //# sourceMappingURL=image-editor-labels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-editor-labels.d.ts","sourceRoot":"","sources":["../src/image-editor-labels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,eAAO,MAAM,2BAA2B,EAAE,iBA2BzC,CAAC;AAEF,wBAAgB,sBAAsB,CACpC,SAAS,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GACrC,iBAAiB,CAGnB"}
@@ -0,0 +1,34 @@
1
+ export const DEFAULT_IMAGE_EDITOR_LABELS = {
2
+ cancel: "Cancel",
3
+ save: "Save",
4
+ loading: "Loading image…",
5
+ loadFailed: "Could not load image for editing.",
6
+ saveFailed: "Could not save edited image.",
7
+ cropFailed: "Could not crop image.",
8
+ rotateFailed: "Could not rotate image.",
9
+ applyCrop: "Apply crop",
10
+ toolSelect: "Select",
11
+ toolPen: "Draw",
12
+ toolLine: "Line",
13
+ toolArrow: "Arrow",
14
+ toolRect: "Rectangle",
15
+ toolEllipse: "Ellipse",
16
+ toolText: "Text",
17
+ toolEraser: "Eraser",
18
+ toolCrop: "Crop",
19
+ rotateCcw: "Rotate left",
20
+ rotateCw: "Rotate right",
21
+ undo: "Undo",
22
+ redo: "Redo",
23
+ stroke: "Stroke width",
24
+ shapeOutline: "Outline",
25
+ shapeFilled: "Filled",
26
+ textPlaceholder: "Text",
27
+ applyText: "Apply text",
28
+ };
29
+ export function mergeImageEditorLabels(overrides) {
30
+ if (!overrides)
31
+ return DEFAULT_IMAGE_EDITOR_LABELS;
32
+ return Object.assign(Object.assign({}, DEFAULT_IMAGE_EDITOR_LABELS), overrides);
33
+ }
34
+ //# sourceMappingURL=image-editor-labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-editor-labels.js","sourceRoot":"","sources":["../src/image-editor-labels.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,2BAA2B,GAAsB;IAC5D,MAAM,EAAE,QAAQ;IAChB,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,gBAAgB;IACzB,UAAU,EAAE,mCAAmC;IAC/C,UAAU,EAAE,8BAA8B;IAC1C,UAAU,EAAE,uBAAuB;IACnC,YAAY,EAAE,yBAAyB;IACvC,SAAS,EAAE,YAAY;IACvB,UAAU,EAAE,QAAQ;IACpB,OAAO,EAAE,MAAM;IACf,QAAQ,EAAE,MAAM;IAChB,SAAS,EAAE,OAAO;IAClB,QAAQ,EAAE,WAAW;IACrB,WAAW,EAAE,SAAS;IACtB,QAAQ,EAAE,MAAM;IAChB,UAAU,EAAE,QAAQ;IACpB,QAAQ,EAAE,MAAM;IAChB,SAAS,EAAE,aAAa;IACxB,QAAQ,EAAE,cAAc;IACxB,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,cAAc;IACtB,YAAY,EAAE,SAAS;IACvB,WAAW,EAAE,QAAQ;IACrB,eAAe,EAAE,MAAM;IACvB,SAAS,EAAE,YAAY;CACxB,CAAC;AAEF,MAAM,UAAU,sBAAsB,CACpC,SAAsC;IAEtC,IAAI,CAAC,SAAS;QAAE,OAAO,2BAA2B,CAAC;IACnD,uCAAY,2BAA2B,GAAK,SAAS,EAAG;AAC1D,CAAC"}
@@ -0,0 +1,39 @@
1
+ import type { ImageEditorRotateDirection, ImageEditorShapeStyle, ImageEditorTool } from "./image-editor.types";
2
+ export declare const IMAGE_EDITOR_COLORS: readonly ["#ef4444", "#f59e0b", "#22c55e", "#3b82f6", "#a855f7", "#ffffff", "#111827"];
3
+ export declare const IMAGE_EDITOR_DEFAULT_TEXT_SIZE = 56;
4
+ export declare const IMAGE_EDITOR_DEFAULT_TEXT_WIDTH = 320;
5
+ /** Convert #rrggbb to rgba() for filled-shape translucency. */
6
+ export declare function hexToRgba(hex: string, alpha: number): string;
7
+ export declare function shapeFillAndStroke(color: string, style: ImageEditorShapeStyle): {
8
+ fill: string;
9
+ stroke: string;
10
+ };
11
+ export declare function isShapeDrawTool(tool: ImageEditorTool): boolean;
12
+ /**
13
+ * Load image bytes for Fabric (blob/data/CORS). Host apps with auth proxies
14
+ * should pass a custom `resolveSrc` instead of relying on this default.
15
+ */
16
+ export declare function loadImageSourceBlob(src: string): Promise<Blob>;
17
+ export declare function getCachedEditorObjectUrl(cacheKey: string): string | undefined;
18
+ /**
19
+ * Resolve a blob: object URL for the editor (cached + deduped in-flight).
20
+ * Call while the preview is open so Edit opens instantly.
21
+ */
22
+ export declare function ensureEditorObjectUrl(src: string, options: {
23
+ cacheKey: string;
24
+ /** Optional host-specific loader (e.g. authenticated proxy). */
25
+ loadBlob?: (src: string) => Promise<Blob>;
26
+ }): Promise<string>;
27
+ export declare function revokeEditorObjectUrls(cacheKeys: string[]): void;
28
+ export declare function blobToDataUrl(blob: Blob): Promise<string>;
29
+ export declare function editedAttachmentFileName(originalName: string | undefined, contentTypeHint?: string): string;
30
+ /** Rotate a raster data URL 90°; dimensions swap. */
31
+ export declare function rotateImageDataUrl(dataUrl: string, direction: ImageEditorRotateDirection): Promise<{
32
+ dataUrl: string;
33
+ width: number;
34
+ height: number;
35
+ }>;
36
+ /** Default resolveSrc used when the host does not inject one. */
37
+ export declare function defaultResolveSrc(src: string): Promise<string>;
38
+ export declare function cn(...parts: Array<string | false | null | undefined>): string;
39
+ //# sourceMappingURL=image-editor-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-editor-utils.d.ts","sourceRoot":"","sources":["../src/image-editor-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,qBAAqB,EACrB,eAAe,EAChB,MAAM,sBAAsB,CAAC;AAE9B,eAAO,MAAM,mBAAmB,wFAQtB,CAAC;AAEX,eAAO,MAAM,8BAA8B,KAAK,CAAC;AACjD,eAAO,MAAM,+BAA+B,MAAM,CAAC;AAQnD,+DAA+D;AAC/D,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAU5D;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,qBAAqB,GAC3B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAWlC;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAO9D;AA6DD;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYpE;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,GACf,MAAM,GAAG,SAAS,CAEpB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;IACP,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C,GACA,OAAO,CAAC,MAAM,CAAC,CAyBjB;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAShE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAOzD;AAQD,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,eAAe,CAAC,EAAE,MAAM,GACvB,MAAM,CAKR;AAWD,qDAAqD;AACrD,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,0BAA0B,GACpC,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAwB7D;AAED,iEAAiE;AACjE,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAIpE;AAED,wBAAgB,EAAE,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,KAAK,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,MAAM,CAE7E"}
@@ -0,0 +1,218 @@
1
+ export const IMAGE_EDITOR_COLORS = [
2
+ "#ef4444",
3
+ "#f59e0b",
4
+ "#22c55e",
5
+ "#3b82f6",
6
+ "#a855f7",
7
+ "#ffffff",
8
+ "#111827",
9
+ ];
10
+ export const IMAGE_EDITOR_DEFAULT_TEXT_SIZE = 56;
11
+ export const IMAGE_EDITOR_DEFAULT_TEXT_WIDTH = 320;
12
+ const CORS_TIMEOUT_MS = 3000;
13
+ /** cacheKey → object URL for instant editor open */
14
+ const editorObjectUrlCache = new Map();
15
+ const editorObjectUrlInflight = new Map();
16
+ /** Convert #rrggbb to rgba() for filled-shape translucency. */
17
+ export function hexToRgba(hex, alpha) {
18
+ const normalized = hex.trim().replace("#", "");
19
+ if (normalized.length !== 6)
20
+ return `rgba(0,0,0,${alpha})`;
21
+ const r = Number.parseInt(normalized.slice(0, 2), 16);
22
+ const g = Number.parseInt(normalized.slice(2, 4), 16);
23
+ const b = Number.parseInt(normalized.slice(4, 6), 16);
24
+ if ([r, g, b].some((channel) => Number.isNaN(channel))) {
25
+ return `rgba(0,0,0,${alpha})`;
26
+ }
27
+ return `rgba(${r},${g},${b},${alpha})`;
28
+ }
29
+ export function shapeFillAndStroke(color, style) {
30
+ if (style === "filled") {
31
+ return {
32
+ fill: hexToRgba(color, 0.45),
33
+ stroke: color,
34
+ };
35
+ }
36
+ return {
37
+ fill: "rgba(0,0,0,0)",
38
+ stroke: color,
39
+ };
40
+ }
41
+ export function isShapeDrawTool(tool) {
42
+ return (tool === "line" ||
43
+ tool === "arrow" ||
44
+ tool === "rect" ||
45
+ tool === "ellipse");
46
+ }
47
+ function abortSignalTimeout(ms) {
48
+ if (typeof AbortSignal !== "undefined" && "timeout" in AbortSignal) {
49
+ return AbortSignal.timeout(ms);
50
+ }
51
+ const controller = new AbortController();
52
+ window.setTimeout(() => controller.abort(), ms);
53
+ return controller.signal;
54
+ }
55
+ async function fetchCorsBlob(src) {
56
+ const response = await fetch(src, {
57
+ mode: "cors",
58
+ credentials: "omit",
59
+ cache: "force-cache",
60
+ signal: abortSignalTimeout(CORS_TIMEOUT_MS),
61
+ });
62
+ if (!response.ok)
63
+ throw new Error("image_load_failed");
64
+ return response.blob();
65
+ }
66
+ /** Decode via Image + canvas when CDN allows CORS. */
67
+ async function decodeCorsImageToBlob(src) {
68
+ const img = new Image();
69
+ img.crossOrigin = "anonymous";
70
+ img.decoding = "async";
71
+ await new Promise((resolve, reject) => {
72
+ const timer = window.setTimeout(() => reject(new Error("image_load_failed")), CORS_TIMEOUT_MS);
73
+ img.onload = () => {
74
+ window.clearTimeout(timer);
75
+ resolve();
76
+ };
77
+ img.onerror = () => {
78
+ window.clearTimeout(timer);
79
+ reject(new Error("image_load_failed"));
80
+ };
81
+ img.src = src;
82
+ });
83
+ const canvas = document.createElement("canvas");
84
+ canvas.width = img.naturalWidth || 1;
85
+ canvas.height = img.naturalHeight || 1;
86
+ const ctx = canvas.getContext("2d");
87
+ if (!ctx)
88
+ throw new Error("image_load_failed");
89
+ ctx.drawImage(img, 0, 0);
90
+ return new Promise((resolve, reject) => {
91
+ canvas.toBlob((result) => result ? resolve(result) : reject(new Error("image_load_failed")), "image/jpeg", 0.92);
92
+ });
93
+ }
94
+ /**
95
+ * Load image bytes for Fabric (blob/data/CORS). Host apps with auth proxies
96
+ * should pass a custom `resolveSrc` instead of relying on this default.
97
+ */
98
+ export async function loadImageSourceBlob(src) {
99
+ if (src.startsWith("blob:") || src.startsWith("data:")) {
100
+ const response = await fetch(src);
101
+ if (!response.ok)
102
+ throw new Error("image_load_failed");
103
+ return response.blob();
104
+ }
105
+ try {
106
+ return await fetchCorsBlob(src);
107
+ }
108
+ catch (_a) {
109
+ return decodeCorsImageToBlob(src);
110
+ }
111
+ }
112
+ export function getCachedEditorObjectUrl(cacheKey) {
113
+ return editorObjectUrlCache.get(cacheKey);
114
+ }
115
+ /**
116
+ * Resolve a blob: object URL for the editor (cached + deduped in-flight).
117
+ * Call while the preview is open so Edit opens instantly.
118
+ */
119
+ export function ensureEditorObjectUrl(src, options) {
120
+ const { cacheKey, loadBlob } = options;
121
+ const cached = editorObjectUrlCache.get(cacheKey);
122
+ if (cached)
123
+ return Promise.resolve(cached);
124
+ const inflight = editorObjectUrlInflight.get(cacheKey);
125
+ if (inflight)
126
+ return inflight;
127
+ const promise = (async () => {
128
+ if (src.startsWith("blob:") || src.startsWith("data:")) {
129
+ editorObjectUrlCache.set(cacheKey, src);
130
+ return src;
131
+ }
132
+ const blob = loadBlob
133
+ ? await loadBlob(src)
134
+ : await loadImageSourceBlob(src);
135
+ const objectUrl = URL.createObjectURL(blob);
136
+ editorObjectUrlCache.set(cacheKey, objectUrl);
137
+ return objectUrl;
138
+ })().finally(() => {
139
+ editorObjectUrlInflight.delete(cacheKey);
140
+ });
141
+ editorObjectUrlInflight.set(cacheKey, promise);
142
+ return promise;
143
+ }
144
+ export function revokeEditorObjectUrls(cacheKeys) {
145
+ for (const key of cacheKeys) {
146
+ const url = editorObjectUrlCache.get(key);
147
+ if (url === null || url === void 0 ? void 0 : url.startsWith("blob:")) {
148
+ URL.revokeObjectURL(url);
149
+ }
150
+ editorObjectUrlCache.delete(key);
151
+ editorObjectUrlInflight.delete(key);
152
+ }
153
+ }
154
+ export function blobToDataUrl(blob) {
155
+ return new Promise((resolve, reject) => {
156
+ const reader = new FileReader();
157
+ reader.onload = () => resolve(String(reader.result));
158
+ reader.onerror = () => reject(new Error("image_load_failed"));
159
+ reader.readAsDataURL(blob);
160
+ });
161
+ }
162
+ function guessImageExtension(name, contentTypeHint) {
163
+ const type = (contentTypeHint !== null && contentTypeHint !== void 0 ? contentTypeHint : "").toLowerCase();
164
+ if (type.includes("png") || name.toLowerCase().endsWith(".png"))
165
+ return "png";
166
+ return "jpg";
167
+ }
168
+ export function editedAttachmentFileName(originalName, contentTypeHint) {
169
+ const raw = (originalName === null || originalName === void 0 ? void 0 : originalName.trim()) || "attachment.jpg";
170
+ const withoutExt = raw.replace(/\.[^.]+$/, "") || "attachment";
171
+ const extension = guessImageExtension(raw, contentTypeHint);
172
+ return `${withoutExt}-edited.${extension}`;
173
+ }
174
+ function loadHtmlImage(src) {
175
+ return new Promise((resolve, reject) => {
176
+ const img = new Image();
177
+ img.onload = () => resolve(img);
178
+ img.onerror = () => reject(new Error("image_load_failed"));
179
+ img.src = src;
180
+ });
181
+ }
182
+ /** Rotate a raster data URL 90°; dimensions swap. */
183
+ export async function rotateImageDataUrl(dataUrl, direction) {
184
+ const img = await loadHtmlImage(dataUrl);
185
+ const srcW = Math.max(1, img.naturalWidth || img.width || 1);
186
+ const srcH = Math.max(1, img.naturalHeight || img.height || 1);
187
+ const canvas = document.createElement("canvas");
188
+ canvas.width = srcH;
189
+ canvas.height = srcW;
190
+ const ctx = canvas.getContext("2d");
191
+ if (!ctx)
192
+ throw new Error("image_rotate_failed");
193
+ if (direction === "cw") {
194
+ ctx.translate(srcH, 0);
195
+ ctx.rotate(Math.PI / 2);
196
+ }
197
+ else {
198
+ ctx.translate(0, srcW);
199
+ ctx.rotate(-Math.PI / 2);
200
+ }
201
+ ctx.drawImage(img, 0, 0);
202
+ return {
203
+ dataUrl: canvas.toDataURL("image/png"),
204
+ width: srcH,
205
+ height: srcW,
206
+ };
207
+ }
208
+ /** Default resolveSrc used when the host does not inject one. */
209
+ export async function defaultResolveSrc(src) {
210
+ if (src.startsWith("blob:") || src.startsWith("data:"))
211
+ return src;
212
+ const blob = await loadImageSourceBlob(src);
213
+ return URL.createObjectURL(blob);
214
+ }
215
+ export function cn(...parts) {
216
+ return parts.filter(Boolean).join(" ");
217
+ }
218
+ //# sourceMappingURL=image-editor-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-editor-utils.js","sourceRoot":"","sources":["../src/image-editor-utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,256 @@
1
+ .avie {
2
+ --avie-bg: rgba(0, 0, 0, 0.5);
3
+ --avie-toolbar-bg: rgba(255, 255, 255, 0.1);
4
+ --avie-border: rgba(255, 255, 255, 0.15);
5
+ --avie-text: rgba(255, 255, 255, 0.85);
6
+ --avie-text-muted: rgba(255, 255, 255, 0.7);
7
+ --avie-error: #fca5a5;
8
+ --avie-accent: #0c66e4;
9
+ --avie-save: #059669;
10
+ --avie-canvas-bg: #0b1220;
11
+ --avie-ring-offset: rgba(0, 0, 0, 0.4);
12
+
13
+ display: flex;
14
+ flex-direction: column;
15
+ width: 100%;
16
+ height: 100%;
17
+ min-height: 0;
18
+ background: var(--avie-bg);
19
+ color: var(--avie-text);
20
+ font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica,
21
+ Arial, sans-serif;
22
+ box-sizing: border-box;
23
+ }
24
+
25
+ .avie *,
26
+ .avie *::before,
27
+ .avie *::after {
28
+ box-sizing: border-box;
29
+ }
30
+
31
+ .avie-toolbar {
32
+ display: flex;
33
+ flex-wrap: wrap;
34
+ flex-shrink: 0;
35
+ align-items: center;
36
+ gap: 4px;
37
+ padding: 6px 8px;
38
+ border-bottom: 1px solid var(--avie-border);
39
+ background: var(--avie-toolbar-bg);
40
+ backdrop-filter: blur(12px);
41
+ }
42
+
43
+ .avie-btn {
44
+ display: inline-flex;
45
+ align-items: center;
46
+ justify-content: center;
47
+ height: 32px;
48
+ min-width: 32px;
49
+ padding: 0 6px;
50
+ border: 0;
51
+ border-radius: 6px;
52
+ background: transparent;
53
+ color: var(--avie-text);
54
+ cursor: pointer;
55
+ transition: background 0.15s ease, color 0.15s ease, opacity 0.15s ease;
56
+ }
57
+
58
+ .avie-btn:hover:not(:disabled) {
59
+ background: rgba(255, 255, 255, 0.15);
60
+ color: #fff;
61
+ }
62
+
63
+ .avie-btn:disabled {
64
+ opacity: 0.45;
65
+ cursor: not-allowed;
66
+ }
67
+
68
+ .avie-btn[aria-pressed="true"],
69
+ .avie-btn.is-active {
70
+ background: rgba(255, 255, 255, 0.2);
71
+ color: #fff;
72
+ }
73
+
74
+ .avie-btn-sm {
75
+ height: 28px;
76
+ min-width: 28px;
77
+ padding: 0 8px;
78
+ font-size: 12px;
79
+ gap: 4px;
80
+ }
81
+
82
+ .avie-btn-accent {
83
+ background: var(--avie-accent);
84
+ color: #fff;
85
+ }
86
+
87
+ .avie-btn-accent:hover:not(:disabled) {
88
+ background: color-mix(in srgb, var(--avie-accent) 90%, #000);
89
+ color: #fff;
90
+ }
91
+
92
+ .avie-btn-save {
93
+ background: var(--avie-save);
94
+ color: #fff;
95
+ }
96
+
97
+ .avie-btn-save:hover:not(:disabled) {
98
+ background: color-mix(in srgb, var(--avie-save) 90%, #000);
99
+ color: #fff;
100
+ }
101
+
102
+ .avie-icon {
103
+ width: 16px;
104
+ height: 16px;
105
+ flex-shrink: 0;
106
+ }
107
+
108
+ .avie-icon-sm {
109
+ width: 14px;
110
+ height: 14px;
111
+ }
112
+
113
+ .avie-line-icon {
114
+ display: block;
115
+ width: 16px;
116
+ height: 2px;
117
+ background: currentColor;
118
+ transform: rotate(-35deg);
119
+ }
120
+
121
+ .avie-divider {
122
+ width: 1px;
123
+ height: 20px;
124
+ margin: 0 4px;
125
+ background: rgba(255, 255, 255, 0.2);
126
+ }
127
+
128
+ .avie-swatches {
129
+ display: flex;
130
+ align-items: center;
131
+ gap: 4px;
132
+ }
133
+
134
+ .avie-swatch {
135
+ width: 20px;
136
+ height: 20px;
137
+ padding: 0;
138
+ border: 1px solid rgba(255, 255, 255, 0.3);
139
+ border-radius: 999px;
140
+ cursor: pointer;
141
+ }
142
+
143
+ .avie-swatch:disabled {
144
+ opacity: 0.45;
145
+ cursor: not-allowed;
146
+ }
147
+
148
+ .avie-swatch.is-active {
149
+ box-shadow:
150
+ 0 0 0 2px #fff,
151
+ 0 0 0 3px var(--avie-ring-offset);
152
+ }
153
+
154
+ .avie-stroke {
155
+ display: flex;
156
+ align-items: center;
157
+ margin-inline-start: 4px;
158
+ font-size: 11px;
159
+ color: rgba(255, 255, 255, 0.75);
160
+ }
161
+
162
+ .avie-stroke input[type="range"] {
163
+ width: 80px;
164
+ accent-color: #fff;
165
+ }
166
+
167
+ .avie-shape-toggle {
168
+ display: flex;
169
+ align-items: center;
170
+ gap: 2px;
171
+ margin-inline-start: 4px;
172
+ padding: 2px;
173
+ border: 1px solid var(--avie-border);
174
+ border-radius: 6px;
175
+ }
176
+
177
+ .avie-actions {
178
+ display: flex;
179
+ align-items: center;
180
+ gap: 4px;
181
+ margin-inline-start: auto;
182
+ }
183
+
184
+ .avie-text-bar {
185
+ display: flex;
186
+ flex-shrink: 0;
187
+ align-items: center;
188
+ gap: 8px;
189
+ padding: 6px 8px;
190
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
191
+ background: rgba(0, 0, 0, 0.4);
192
+ }
193
+
194
+ .avie-text-input {
195
+ flex: 1;
196
+ height: 32px;
197
+ padding: 0 10px;
198
+ border: 1px solid rgba(255, 255, 255, 0.2);
199
+ border-radius: 6px;
200
+ background: rgba(255, 255, 255, 0.1);
201
+ color: #fff;
202
+ font-size: 14px;
203
+ outline: none;
204
+ }
205
+
206
+ .avie-text-input::placeholder {
207
+ color: rgba(255, 255, 255, 0.5);
208
+ }
209
+
210
+ .avie-stage {
211
+ position: relative;
212
+ flex: 1;
213
+ min-height: 0;
214
+ overflow: hidden;
215
+ padding: 8px;
216
+ }
217
+
218
+ .avie-stage-inner {
219
+ display: flex;
220
+ align-items: center;
221
+ justify-content: center;
222
+ width: 100%;
223
+ height: 100%;
224
+ }
225
+
226
+ .avie-stage canvas {
227
+ max-width: 100%;
228
+ max-height: 100%;
229
+ }
230
+
231
+ .avie-status {
232
+ position: absolute;
233
+ inset: 0;
234
+ display: flex;
235
+ align-items: center;
236
+ justify-content: center;
237
+ font-size: 14px;
238
+ color: var(--avie-text-muted);
239
+ pointer-events: none;
240
+ }
241
+
242
+ .avie-status.is-error {
243
+ color: var(--avie-error);
244
+ }
245
+
246
+ .avie-sr-only {
247
+ position: absolute;
248
+ width: 1px;
249
+ height: 1px;
250
+ padding: 0;
251
+ margin: -1px;
252
+ overflow: hidden;
253
+ clip: rect(0, 0, 0, 0);
254
+ white-space: nowrap;
255
+ border: 0;
256
+ }
@@ -0,0 +1,4 @@
1
+ import type { ImageEditorProps } from "./image-editor.types";
2
+ import "./image-editor.css";
3
+ export declare function ImageEditor({ src, fileName, labels: labelsProp, resolveSrc, onCancel, onSave, className, }: ImageEditorProps): import("react/jsx-runtime").JSX.Element;
4
+ //# sourceMappingURL=image-editor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-editor.d.ts","sourceRoot":"","sources":["../src/image-editor.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,gBAAgB,EAEjB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,oBAAoB,CAAC;AA2H5B,wBAAgB,WAAW,CAAC,EAC1B,GAAG,EACH,QAAQ,EACR,MAAM,EAAE,UAAU,EAClB,UAAU,EACV,QAAQ,EACR,MAAM,EACN,SAAS,GACV,EAAE,gBAAgB,2CAyPlB"}
@@ -0,0 +1,51 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { mergeImageEditorLabels } from "./image-editor-labels";
5
+ import { IMAGE_EDITOR_COLORS, cn } from "./image-editor-utils";
6
+ import { useImageEditor } from "./use-image-editor";
7
+ import "./image-editor.css";
8
+ function Icon({ children, className, }) {
9
+ return (_jsx("svg", { className: cn("avie-icon", className), viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: children }));
10
+ }
11
+ const Icons = {
12
+ select: (_jsxs(Icon, { children: [_jsx("path", { d: "M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z" }), _jsx("path", { d: "M13 13l6 6" })] })),
13
+ pen: (_jsxs(Icon, { children: [_jsx("path", { d: "M12 20h9" }), _jsx("path", { d: "M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z" })] })),
14
+ arrow: (_jsxs(Icon, { children: [_jsx("path", { d: "M7 17L17 7" }), _jsx("path", { d: "M7 7h10v10" })] })),
15
+ rect: (_jsx(Icon, { children: _jsx("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }) })),
16
+ ellipse: (_jsx(Icon, { children: _jsx("circle", { cx: "12", cy: "12", r: "10" }) })),
17
+ text: (_jsxs(Icon, { children: [_jsx("polyline", { points: "4 7 4 4 20 4 20 7" }), _jsx("line", { x1: "9", x2: "15", y1: "20", y2: "20" }), _jsx("line", { x1: "12", x2: "12", y1: "4", y2: "20" })] })),
18
+ eraser: (_jsxs(Icon, { children: [_jsx("path", { d: "m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21" }), _jsx("path", { d: "M22 21H7" }), _jsx("path", { d: "m5 11 9 9" })] })),
19
+ crop: (_jsxs(Icon, { children: [_jsx("path", { d: "M6 2v14a2 2 0 0 0 2 2h14" }), _jsx("path", { d: "M18 22V8a2 2 0 0 0-2-2H2" })] })),
20
+ rotateCcw: (_jsxs(Icon, { children: [_jsx("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }), _jsx("path", { d: "M3 3v5h5" })] })),
21
+ rotateCw: (_jsxs(Icon, { children: [_jsx("path", { d: "M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" }), _jsx("path", { d: "M21 3v5h-5" })] })),
22
+ undo: (_jsxs(Icon, { children: [_jsx("path", { d: "M3 7v6h6" }), _jsx("path", { d: "M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6.7 2.8L3 13" })] })),
23
+ redo: (_jsxs(Icon, { children: [_jsx("path", { d: "M21 7v6h-6" }), _jsx("path", { d: "M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6.7 2.8L21 13" })] })),
24
+ check: (_jsx(Icon, { className: "avie-icon-sm", children: _jsx("path", { d: "M20 6 9 17l-5-5" }) })),
25
+ close: (_jsxs(Icon, { className: "avie-icon-sm", children: [_jsx("path", { d: "M18 6 6 18" }), _jsx("path", { d: "m6 6 12 12" })] })),
26
+ outline: (_jsx(Icon, { className: "avie-icon-sm", children: _jsx("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", strokeDasharray: "4 3" }) })),
27
+ filled: (_jsxs(Icon, { className: "avie-icon-sm", children: [_jsx("path", { d: "m19 11-8-8-8.6 8.6a2 2 0 0 0 0 2.8l5.2 5.2c.8.8 2 .8 2.8 0L19 11Z" }), _jsx("path", { d: "m5 2 5 5" }), _jsx("path", { d: "M2 13h15" })] })),
28
+ };
29
+ export function ImageEditor({ src, fileName, labels: labelsProp, resolveSrc, onCancel, onSave, className, }) {
30
+ const labels = React.useMemo(() => mergeImageEditorLabels(labelsProp), [labelsProp]);
31
+ const { hostRef, canvasRef, textInputRef, tool, color, strokeWidth, shapeStyle, cropDraft, ready, busy, error, canUndo, canRedo, textEditor, setColor, setStrokeWidth, setShapeStyle, handleToolChange, handleUndo, handleRedo, applyCrop, applyRotate, handleSave, commitTextEditor, updateTextValue, dismissTextEditor, } = useImageEditor({
32
+ src,
33
+ fileName,
34
+ labels,
35
+ resolveSrc,
36
+ onSave,
37
+ });
38
+ const toolButton = (id, label, icon) => (_jsx("button", { type: "button", className: "avie-btn", "aria-label": label, "aria-pressed": tool === id, onClick: () => handleToolChange(id), disabled: !ready || busy, children: icon }, id));
39
+ return (_jsxs("div", { className: cn("avie", className), children: [_jsxs("div", { className: "avie-toolbar", children: [toolButton("select", labels.toolSelect, Icons.select), toolButton("pen", labels.toolPen, Icons.pen), toolButton("line", labels.toolLine, _jsx("span", { className: "avie-line-icon", "aria-hidden": true })), toolButton("arrow", labels.toolArrow, Icons.arrow), toolButton("rect", labels.toolRect, Icons.rect), toolButton("ellipse", labels.toolEllipse, Icons.ellipse), toolButton("text", labels.toolText, Icons.text), toolButton("eraser", labels.toolEraser, Icons.eraser), toolButton("crop", labels.toolCrop, Icons.crop), _jsx("button", { type: "button", className: "avie-btn", "aria-label": labels.rotateCcw, disabled: !ready || busy, onClick: () => void applyRotate("ccw"), children: Icons.rotateCcw }), _jsx("button", { type: "button", className: "avie-btn", "aria-label": labels.rotateCw, disabled: !ready || busy, onClick: () => void applyRotate("cw"), children: Icons.rotateCw }), _jsx("span", { className: "avie-divider", "aria-hidden": true }), _jsx("button", { type: "button", className: "avie-btn", "aria-label": labels.undo, disabled: !ready || busy || !canUndo, onClick: () => void handleUndo(), children: Icons.undo }), _jsx("button", { type: "button", className: "avie-btn", "aria-label": labels.redo, disabled: !ready || busy || !canRedo, onClick: () => void handleRedo(), children: Icons.redo }), _jsx("span", { className: "avie-divider", "aria-hidden": true }), _jsx("div", { className: "avie-swatches", children: IMAGE_EDITOR_COLORS.map((swatch) => (_jsx("button", { type: "button", className: cn("avie-swatch", color === swatch && "is-active"), style: { backgroundColor: swatch }, "aria-label": swatch, onClick: () => setColor(swatch), disabled: !ready || busy }, swatch))) }), _jsxs("label", { className: "avie-stroke", children: [_jsx("span", { className: "avie-sr-only", children: labels.stroke }), _jsx("input", { type: "range", min: 1, max: 24, value: strokeWidth, onChange: (event) => setStrokeWidth(Number(event.target.value)), disabled: !ready || busy })] }), tool === "rect" || tool === "ellipse" ? (_jsxs("div", { className: "avie-shape-toggle", children: [_jsx("button", { type: "button", className: cn("avie-btn", shapeStyle === "outline" && "is-active"), "aria-label": labels.shapeOutline, "aria-pressed": shapeStyle === "outline", onClick: () => setShapeStyle("outline"), disabled: !ready || busy, children: Icons.outline }), _jsx("button", { type: "button", className: cn("avie-btn", shapeStyle === "filled" && "is-active"), "aria-label": labels.shapeFilled, "aria-pressed": shapeStyle === "filled", onClick: () => setShapeStyle("filled"), disabled: !ready || busy, children: Icons.filled })] })) : null, _jsxs("div", { className: "avie-actions", children: [tool === "crop" && cropDraft ? (_jsxs("button", { type: "button", className: "avie-btn avie-btn-sm avie-btn-accent", disabled: busy, onClick: () => void applyCrop(), children: [Icons.check, labels.applyCrop] })) : null, _jsxs("button", { type: "button", className: "avie-btn avie-btn-sm", disabled: busy, onClick: onCancel, children: [Icons.close, labels.cancel] }), _jsxs("button", { type: "button", className: "avie-btn avie-btn-sm avie-btn-save", disabled: !ready || busy, onClick: () => void handleSave(), children: [Icons.check, labels.save] })] })] }), textEditor ? (_jsxs("div", { className: "avie-text-bar", children: [_jsx("input", { ref: textInputRef, className: "avie-text-input", value: textEditor.value, onChange: (event) => updateTextValue(event.target.value), onKeyDown: (event) => {
40
+ event.stopPropagation();
41
+ if (event.key === "Enter") {
42
+ event.preventDefault();
43
+ commitTextEditor();
44
+ }
45
+ if (event.key === "Escape") {
46
+ event.preventDefault();
47
+ dismissTextEditor();
48
+ }
49
+ }, placeholder: labels.textPlaceholder }), _jsx("button", { type: "button", className: "avie-btn avie-btn-sm avie-btn-accent", onClick: commitTextEditor, children: labels.applyText })] })) : null, _jsxs("div", { ref: hostRef, className: "avie-stage", children: [!ready && !error ? (_jsx("div", { className: "avie-status", children: labels.loading })) : null, error ? _jsx("div", { className: "avie-status is-error", children: error }) : null, _jsx("div", { className: "avie-stage-inner", children: _jsx("canvas", { ref: canvasRef }) })] })] }));
50
+ }
51
+ //# sourceMappingURL=image-editor.js.map