@haklex/rich-renderer-image 0.0.82 → 0.0.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,644 +1,703 @@
1
- import { C as root, S as replaceUploadArea, _ as imageState, a as editInput, c as editToolbar, d as editToolbarVisible, f as editTrigger, g as image, h as frameEditMode, i as editFieldIcon, l as editToolbarButton, m as frame, o as editPanel, r as editField, s as editPlaceholder, t as ImageRenderer, u as editToolbarButtonDanger, v as imageVisible, w as semanticClassNames } from "./ImageRenderer-BUTTEOl6.js";
1
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { useRendererMode } from "@haklex/rich-editor";
3
3
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
4
- import { Provider, atom, createStore, useAtomValue, useSetAtom, useStore } from "jotai";
5
- import { useCallback, useEffect, useRef, useState } from "react";
6
- import { computeImageMeta, decodeThumbHash } from "@haklex/rich-editor/renderers";
7
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
- import { ActionBar, ActionButton, Popover, PopoverPanel, PopoverTrigger, SegmentedControl } from "@haklex/rich-editor-ui";
9
- import { Captions, Copy, Download, ExternalLink, ImageIcon, Loader2, Replace, Trash2, Type, Upload } from "lucide-react";
10
- import { $createImageNode, $isImageNode } from "@haklex/rich-editor/nodes";
4
+ import { atom, createStore, Provider, useStore, useAtomValue, useSetAtom } from "jotai";
5
+ import { useRef, useState, useEffect, useCallback } from "react";
6
+ import { decodeThumbHash, computeImageMeta } from "@haklex/rich-editor/renderers";
7
+ import { ActionBar, ActionButton, SegmentedControl, Popover, PopoverTrigger, PopoverPanel } from "@haklex/rich-editor-ui";
8
+ import { ImageIcon, Type, Captions, Loader2, Upload, Replace, ExternalLink, Copy, Download, Trash2 } from "lucide-react";
9
+ import { e as editField, s as semanticClassNames, a as editFieldIcon, b as editInput, r as replaceUploadArea, p as panelHint, c as replacePreview, d as editToolbar, f as editToolbarVisible, g as editToolbarButton, h as editPanel, i as editToolbarButtonDanger, j as editPlaceholder, I as ImageRenderer, k as editTrigger, l as root, m as image, n as imageVisible, o as loader, q as errorBadge, t as frame, u as frameEditMode, v as imageState, w as caption } from "./ImageRenderer-DyeaT08P.js";
10
+ import { $isImageNode, $createImageNode } from "@haklex/rich-editor/nodes";
11
11
  import { useImageUpload } from "@haklex/rich-editor/plugins";
12
12
  import { $getNearestNodeFromDOMNode } from "lexical";
13
- //#region src/atoms.ts
14
- function getCaptionText(altText, caption) {
15
- if (caption) return caption;
16
- if (!altText) return void 0;
17
- if (altText.startsWith("!") || altText.startsWith("¡")) return altText.slice(1);
13
+ function getCaptionText(altText, caption2) {
14
+ if (caption2) return caption2;
15
+ if (!altText) return void 0;
16
+ if (altText.startsWith("!") || altText.startsWith("¡")) {
17
+ return altText.slice(1);
18
+ }
19
+ return void 0;
18
20
  }
19
- var srcAtom = atom("");
20
- var altTextAtom = atom("");
21
- var widthAtom = atom();
22
- var heightAtom = atom();
23
- var captionAtom = atom();
24
- var thumbhashAtom = atom();
25
- var accentAtom = atom();
26
- var loadStateAtom = atom("loading");
27
- var hoveringAtom = atom(false);
28
- var metaOpenAtom = atom(false);
29
- var replaceOpenAtom = atom(false);
30
- var toolbarVisibleAtom = atom((get) => get(hoveringAtom) || get(metaOpenAtom) || get(replaceOpenAtom));
31
- var focusCaptionOnOpenAtom = atom(false);
32
- var editSrcAtom = atom("");
33
- var editAltTextAtom = atom("");
34
- var editCaptionAtom = atom("");
35
- var replaceModeAtom = atom("upload");
36
- var replaceUrlAtom = atom("");
37
- var replacePreviewAtom = atom(null);
38
- var replaceMetaAtom = atom(null);
39
- var replaceLoadingAtom = atom(false);
40
- var replaceErrorAtom = atom(null);
41
- var wrapperRefAtom = atom({ current: null });
42
- var fileInputRefAtom = atom({ current: null });
43
- var placeholderUrlAtom = atom((get) => {
44
- const thumbhash = get(thumbhashAtom);
45
- return thumbhash ? decodeThumbHash(thumbhash) : void 0;
21
+ const srcAtom = atom("");
22
+ const altTextAtom = atom("");
23
+ const widthAtom = atom();
24
+ const heightAtom = atom();
25
+ const captionAtom = atom();
26
+ const thumbhashAtom = atom();
27
+ const accentAtom = atom();
28
+ const loadStateAtom = atom("loading");
29
+ const hoveringAtom = atom(false);
30
+ const metaOpenAtom = atom(false);
31
+ const replaceOpenAtom = atom(false);
32
+ const toolbarVisibleAtom = atom(
33
+ (get) => get(hoveringAtom) || get(metaOpenAtom) || get(replaceOpenAtom)
34
+ );
35
+ const focusCaptionOnOpenAtom = atom(false);
36
+ const editSrcAtom = atom("");
37
+ const editAltTextAtom = atom("");
38
+ const editCaptionAtom = atom("");
39
+ const replaceModeAtom = atom("upload");
40
+ const replaceUrlAtom = atom("");
41
+ const replacePreviewAtom = atom(null);
42
+ const replaceMetaAtom = atom(null);
43
+ const replaceLoadingAtom = atom(false);
44
+ const replaceErrorAtom = atom(null);
45
+ const wrapperRefAtom = atom({
46
+ current: null
46
47
  });
47
- var captionTextAtom = atom((get) => getCaptionText(get(altTextAtom), get(captionAtom)));
48
- var frameStyleAtom = atom((get) => {
49
- const placeholderUrl = get(placeholderUrlAtom);
50
- const accent = get(accentAtom);
51
- const loadState = get(loadStateAtom);
52
- const width = get(widthAtom);
53
- const height = get(heightAtom);
54
- return {
55
- backgroundColor: loadState !== "loaded" && !placeholderUrl ? accent || "#f5f5f5" : "transparent",
56
- backgroundImage: placeholderUrl && loadState !== "loaded" ? `url(${placeholderUrl})` : void 0,
57
- backgroundSize: "cover",
58
- width: width ? Math.min(width, 1200) : void 0,
59
- maxWidth: "100%",
60
- ...width && height ? { aspectRatio: `${width} / ${height}` } : {}
61
- };
48
+ const fileInputRefAtom = atom({
49
+ current: null
62
50
  });
63
- //#endregion
64
- //#region src/ImageEditContext.tsx
65
- function ImageEditProvider({ props, children }) {
66
- const wrapperRef = useRef(null);
67
- const fileInputRef = useRef(null);
68
- const [store] = useState(() => {
69
- const s = createStore();
70
- s.set(srcAtom, props.src);
71
- s.set(altTextAtom, props.altText);
72
- s.set(widthAtom, props.width);
73
- s.set(heightAtom, props.height);
74
- s.set(captionAtom, props.caption);
75
- s.set(thumbhashAtom, props.thumbhash);
76
- s.set(accentAtom, props.accent);
77
- s.set(editSrcAtom, props.src);
78
- s.set(editAltTextAtom, props.altText);
79
- s.set(editCaptionAtom, props.caption || "");
80
- s.set(wrapperRefAtom, wrapperRef);
81
- s.set(fileInputRefAtom, fileInputRef);
82
- s.set(loadStateAtom, props.src ? "loading" : "error");
83
- s.set(replaceOpenAtom, !props.src);
84
- return s;
85
- });
86
- useEffect(() => {
87
- store.set(srcAtom, props.src);
88
- store.set(altTextAtom, props.altText);
89
- store.set(widthAtom, props.width);
90
- store.set(heightAtom, props.height);
91
- store.set(captionAtom, props.caption);
92
- store.set(thumbhashAtom, props.thumbhash);
93
- store.set(accentAtom, props.accent);
94
- }, [
95
- store,
96
- props.src,
97
- props.altText,
98
- props.width,
99
- props.height,
100
- props.caption,
101
- props.thumbhash,
102
- props.accent
103
- ]);
104
- useEffect(() => {
105
- store.set(editSrcAtom, props.src);
106
- store.set(editAltTextAtom, props.altText);
107
- store.set(editCaptionAtom, props.caption || "");
108
- }, [
109
- store,
110
- props.src,
111
- props.altText,
112
- props.caption
113
- ]);
114
- useEffect(() => {
115
- store.set(loadStateAtom, props.src ? "loading" : "error");
116
- if (!props.src) store.set(replaceOpenAtom, true);
117
- }, [store, props.src]);
118
- return /* @__PURE__ */ jsx(Provider, {
119
- store,
120
- children
121
- });
51
+ const placeholderUrlAtom = atom((get) => {
52
+ const thumbhash = get(thumbhashAtom);
53
+ return thumbhash ? decodeThumbHash(thumbhash) : void 0;
54
+ });
55
+ const captionTextAtom = atom((get) => getCaptionText(get(altTextAtom), get(captionAtom)));
56
+ const frameStyleAtom = atom((get) => {
57
+ const placeholderUrl = get(placeholderUrlAtom);
58
+ const accent = get(accentAtom);
59
+ const loadState = get(loadStateAtom);
60
+ const width = get(widthAtom);
61
+ const height = get(heightAtom);
62
+ return {
63
+ backgroundColor: loadState !== "loaded" && !placeholderUrl ? accent || "#f5f5f5" : "transparent",
64
+ backgroundImage: placeholderUrl && loadState !== "loaded" ? `url(${placeholderUrl})` : void 0,
65
+ backgroundSize: "cover",
66
+ width: width ? Math.min(width, 1200) : void 0,
67
+ maxWidth: "100%",
68
+ ...width && height ? { aspectRatio: `${width} / ${height}` } : {}
69
+ };
70
+ });
71
+ function ImageEditProvider({
72
+ props,
73
+ children
74
+ }) {
75
+ const wrapperRef = useRef(null);
76
+ const fileInputRef = useRef(null);
77
+ const [store] = useState(() => {
78
+ const s = createStore();
79
+ s.set(srcAtom, props.src);
80
+ s.set(altTextAtom, props.altText);
81
+ s.set(widthAtom, props.width);
82
+ s.set(heightAtom, props.height);
83
+ s.set(captionAtom, props.caption);
84
+ s.set(thumbhashAtom, props.thumbhash);
85
+ s.set(accentAtom, props.accent);
86
+ s.set(editSrcAtom, props.src);
87
+ s.set(editAltTextAtom, props.altText);
88
+ s.set(editCaptionAtom, props.caption || "");
89
+ s.set(wrapperRefAtom, wrapperRef);
90
+ s.set(fileInputRefAtom, fileInputRef);
91
+ s.set(loadStateAtom, props.src ? "loading" : "error");
92
+ s.set(replaceOpenAtom, !props.src);
93
+ return s;
94
+ });
95
+ useEffect(() => {
96
+ store.set(srcAtom, props.src);
97
+ store.set(altTextAtom, props.altText);
98
+ store.set(widthAtom, props.width);
99
+ store.set(heightAtom, props.height);
100
+ store.set(captionAtom, props.caption);
101
+ store.set(thumbhashAtom, props.thumbhash);
102
+ store.set(accentAtom, props.accent);
103
+ }, [
104
+ store,
105
+ props.src,
106
+ props.altText,
107
+ props.width,
108
+ props.height,
109
+ props.caption,
110
+ props.thumbhash,
111
+ props.accent
112
+ ]);
113
+ useEffect(() => {
114
+ store.set(editSrcAtom, props.src);
115
+ store.set(editAltTextAtom, props.altText);
116
+ store.set(editCaptionAtom, props.caption || "");
117
+ }, [store, props.src, props.altText, props.caption]);
118
+ useEffect(() => {
119
+ store.set(loadStateAtom, props.src ? "loading" : "error");
120
+ if (!props.src) store.set(replaceOpenAtom, true);
121
+ }, [store, props.src]);
122
+ return /* @__PURE__ */ jsx(Provider, { store, children });
122
123
  }
123
- //#endregion
124
- //#region src/useImageActions.ts
125
124
  function isSafeImageSrc(src) {
126
- return !/^(?:javascript\s*:|vbscript\s*:|data\s*:(?!image\/))/i.test(src);
125
+ return !/^(?:javascript\s*:|vbscript\s*:|data\s*:(?!image\/))/i.test(src);
127
126
  }
128
127
  function readAsDataUrl(file) {
129
- return new Promise((resolve, reject) => {
130
- const reader = new FileReader();
131
- reader.onload = () => resolve(String(reader.result));
132
- reader.onerror = () => reject(reader.error ?? /* @__PURE__ */ new Error("File read failed"));
133
- reader.readAsDataURL(file);
134
- });
128
+ return new Promise((resolve, reject) => {
129
+ const reader = new FileReader();
130
+ reader.onload = () => resolve(String(reader.result));
131
+ reader.onerror = () => reject(reader.error ?? new Error("File read failed"));
132
+ reader.readAsDataURL(file);
133
+ });
135
134
  }
136
135
  function loadImageByUrl(src) {
137
- return new Promise((resolve, reject) => {
138
- const image = new Image();
139
- image.onload = () => {
140
- resolve({
141
- width: image.naturalWidth || image.width,
142
- height: image.naturalHeight || image.height
143
- });
144
- };
145
- image.onerror = () => reject(/* @__PURE__ */ new Error("Image load failed"));
146
- image.src = src;
147
- });
136
+ return new Promise((resolve, reject) => {
137
+ const image2 = new Image();
138
+ image2.onload = () => {
139
+ resolve({
140
+ width: image2.naturalWidth || image2.width,
141
+ height: image2.naturalHeight || image2.height
142
+ });
143
+ };
144
+ image2.onerror = () => reject(new Error("Image load failed"));
145
+ image2.src = src;
146
+ });
148
147
  }
149
148
  function useImageActions() {
150
- const store = useStore();
151
- const [editor] = useLexicalComposerContext();
152
- const uploadImage = useImageUpload();
153
- const withImageNode = useCallback((updater) => {
154
- const wrapperRef = store.get(wrapperRefAtom);
155
- if (!wrapperRef.current) return;
156
- editor.update(() => {
157
- const node = $getNearestNodeFromDOMNode(wrapperRef.current);
158
- if ($isImageNode(node)) updater(node);
159
- });
160
- }, [editor, store]);
161
- const closeReplacePanel = useCallback(() => {
162
- store.set(replaceOpenAtom, false);
163
- store.set(replaceModeAtom, "upload");
164
- store.set(replaceUrlAtom, "");
165
- store.set(replacePreviewAtom, null);
166
- store.set(replaceMetaAtom, null);
167
- store.set(replaceLoadingAtom, false);
168
- store.set(replaceErrorAtom, null);
169
- }, [store]);
170
- const commitMeta = useCallback(() => {
171
- const editSrc = store.get(editSrcAtom).trim();
172
- if (!editSrc || !isSafeImageSrc(editSrc)) return;
173
- withImageNode((node) => {
174
- node.setSrc(editSrc);
175
- node.setAltText(store.get(editAltTextAtom).trim());
176
- node.setCaption(store.get(editCaptionAtom).trim() || void 0);
177
- });
178
- store.set(metaOpenAtom, false);
179
- }, [store, withImageNode]);
180
- const handleDelete = useCallback(() => {
181
- const wrapperRef = store.get(wrapperRefAtom);
182
- if (!wrapperRef.current) return;
183
- editor.update(() => {
184
- const node = $getNearestNodeFromDOMNode(wrapperRef.current);
185
- if (node) node.remove();
186
- });
187
- store.set(metaOpenAtom, false);
188
- closeReplacePanel();
189
- }, [
190
- closeReplacePanel,
191
- editor,
192
- store
193
- ]);
194
- const handleOpen = useCallback(() => {
195
- const src = store.get(srcAtom);
196
- if (src) window.open(src, "_blank", "noopener,noreferrer");
197
- }, [store]);
198
- const handleDownload = useCallback(() => {
199
- const src = store.get(srcAtom);
200
- if (!src) return;
201
- const link = document.createElement("a");
202
- link.href = src;
203
- link.download = store.get(altTextAtom) || "image";
204
- link.click();
205
- }, [store]);
206
- const handleDuplicate = useCallback(() => {
207
- withImageNode((node) => {
208
- const copy = $createImageNode({
209
- src: node.getSrc(),
210
- altText: node.getAltText(),
211
- width: node.getWidth(),
212
- height: node.getHeight(),
213
- caption: node.getCaption(),
214
- thumbhash: node.getThumbhash(),
215
- accent: node.getAccent()
216
- });
217
- node.insertAfter(copy);
218
- });
219
- }, [withImageNode]);
220
- return {
221
- commitMeta,
222
- closeReplacePanel,
223
- handleReplaceFile: useCallback(async (file) => {
224
- if (!file || !file.type.startsWith("image/")) return;
225
- store.set(replaceLoadingAtom, true);
226
- store.set(replaceErrorAtom, null);
227
- try {
228
- const fallbackUploader = async (nextFile) => ({
229
- src: await readAsDataUrl(nextFile),
230
- altText: nextFile.name
231
- });
232
- const uploader = uploadImage ?? fallbackUploader;
233
- const [result, meta] = await Promise.all([uploader(file), computeImageMeta(file)]);
234
- withImageNode((node) => {
235
- node.setSrc(result.src);
236
- node.setAltText(result.altText ?? file.name);
237
- node.setDimensions(result.width ?? meta.width, result.height ?? meta.height);
238
- node.setThumbhash(result.thumbhash ?? meta.thumbhash);
239
- });
240
- closeReplacePanel();
241
- } catch {
242
- store.set(replaceErrorAtom, "Failed to replace image");
243
- } finally {
244
- store.set(replaceLoadingAtom, false);
245
- }
246
- }, [
247
- closeReplacePanel,
248
- store,
249
- uploadImage,
250
- withImageNode
251
- ]),
252
- handlePreviewUrl: useCallback(async () => {
253
- const url = store.get(replaceUrlAtom).trim();
254
- if (!url || !isSafeImageSrc(url)) {
255
- store.set(replaceErrorAtom, "Invalid image URL");
256
- return;
257
- }
258
- store.set(replaceLoadingAtom, true);
259
- store.set(replaceErrorAtom, null);
260
- try {
261
- const meta = await loadImageByUrl(url);
262
- store.set(replacePreviewAtom, url);
263
- store.set(replaceMetaAtom, meta);
264
- } catch {
265
- store.set(replacePreviewAtom, null);
266
- store.set(replaceMetaAtom, null);
267
- store.set(replaceErrorAtom, "Image URL could not be loaded");
268
- } finally {
269
- store.set(replaceLoadingAtom, false);
270
- }
271
- }, [store]),
272
- handleReplaceByUrl: useCallback(() => {
273
- const preview = store.get(replacePreviewAtom);
274
- if (!preview || !isSafeImageSrc(preview)) return;
275
- const meta = store.get(replaceMetaAtom);
276
- withImageNode((node) => {
277
- node.setSrc(preview);
278
- node.setDimensions(meta?.width, meta?.height);
279
- node.setThumbhash(void 0);
280
- });
281
- closeReplacePanel();
282
- }, [
283
- closeReplacePanel,
284
- store,
285
- withImageNode
286
- ]),
287
- handleReplaceOpenChange: useCallback((nextOpen) => {
288
- store.set(replaceOpenAtom, nextOpen);
289
- if (!nextOpen) closeReplacePanel();
290
- }, [closeReplacePanel, store]),
291
- handleOpen,
292
- handleDuplicate,
293
- handleDownload,
294
- handleDelete
295
- };
149
+ const store = useStore();
150
+ const [editor] = useLexicalComposerContext();
151
+ const uploadImage = useImageUpload();
152
+ const withImageNode = useCallback(
153
+ (updater) => {
154
+ const wrapperRef = store.get(wrapperRefAtom);
155
+ if (!wrapperRef.current) return;
156
+ editor.update(() => {
157
+ const node = $getNearestNodeFromDOMNode(wrapperRef.current);
158
+ if ($isImageNode(node)) updater(node);
159
+ });
160
+ },
161
+ [editor, store]
162
+ );
163
+ const closeReplacePanel = useCallback(() => {
164
+ store.set(replaceOpenAtom, false);
165
+ store.set(replaceModeAtom, "upload");
166
+ store.set(replaceUrlAtom, "");
167
+ store.set(replacePreviewAtom, null);
168
+ store.set(replaceMetaAtom, null);
169
+ store.set(replaceLoadingAtom, false);
170
+ store.set(replaceErrorAtom, null);
171
+ }, [store]);
172
+ const commitMeta = useCallback(() => {
173
+ const editSrc = store.get(editSrcAtom).trim();
174
+ if (!editSrc || !isSafeImageSrc(editSrc)) return;
175
+ withImageNode((node) => {
176
+ node.setSrc(editSrc);
177
+ node.setAltText(store.get(editAltTextAtom).trim());
178
+ node.setCaption(store.get(editCaptionAtom).trim() || void 0);
179
+ });
180
+ store.set(metaOpenAtom, false);
181
+ }, [store, withImageNode]);
182
+ const handleDelete = useCallback(() => {
183
+ const wrapperRef = store.get(wrapperRefAtom);
184
+ if (!wrapperRef.current) return;
185
+ editor.update(() => {
186
+ const node = $getNearestNodeFromDOMNode(wrapperRef.current);
187
+ if (node) node.remove();
188
+ });
189
+ store.set(metaOpenAtom, false);
190
+ closeReplacePanel();
191
+ }, [closeReplacePanel, editor, store]);
192
+ const handleOpen = useCallback(() => {
193
+ const src = store.get(srcAtom);
194
+ if (src) window.open(src, "_blank", "noopener,noreferrer");
195
+ }, [store]);
196
+ const handleDownload = useCallback(() => {
197
+ const src = store.get(srcAtom);
198
+ if (!src) return;
199
+ const link = document.createElement("a");
200
+ link.href = src;
201
+ link.download = store.get(altTextAtom) || "image";
202
+ link.click();
203
+ }, [store]);
204
+ const handleDuplicate = useCallback(() => {
205
+ withImageNode((node) => {
206
+ const copy = $createImageNode({
207
+ src: node.getSrc(),
208
+ altText: node.getAltText(),
209
+ width: node.getWidth(),
210
+ height: node.getHeight(),
211
+ caption: node.getCaption(),
212
+ thumbhash: node.getThumbhash(),
213
+ accent: node.getAccent()
214
+ });
215
+ node.insertAfter(copy);
216
+ });
217
+ }, [withImageNode]);
218
+ const handleReplaceFile = useCallback(
219
+ async (file) => {
220
+ if (!file || !file.type.startsWith("image/")) return;
221
+ store.set(replaceLoadingAtom, true);
222
+ store.set(replaceErrorAtom, null);
223
+ try {
224
+ const fallbackUploader = async (nextFile) => ({
225
+ src: await readAsDataUrl(nextFile),
226
+ altText: nextFile.name
227
+ });
228
+ const uploader = uploadImage ?? fallbackUploader;
229
+ const [result, meta] = await Promise.all([uploader(file), computeImageMeta(file)]);
230
+ withImageNode((node) => {
231
+ node.setSrc(result.src);
232
+ node.setAltText(result.altText ?? file.name);
233
+ node.setDimensions(result.width ?? meta.width, result.height ?? meta.height);
234
+ node.setThumbhash(result.thumbhash ?? meta.thumbhash);
235
+ });
236
+ closeReplacePanel();
237
+ } catch {
238
+ store.set(replaceErrorAtom, "Failed to replace image");
239
+ } finally {
240
+ store.set(replaceLoadingAtom, false);
241
+ }
242
+ },
243
+ [closeReplacePanel, store, uploadImage, withImageNode]
244
+ );
245
+ const handlePreviewUrl = useCallback(async () => {
246
+ const url = store.get(replaceUrlAtom).trim();
247
+ if (!url || !isSafeImageSrc(url)) {
248
+ store.set(replaceErrorAtom, "Invalid image URL");
249
+ return;
250
+ }
251
+ store.set(replaceLoadingAtom, true);
252
+ store.set(replaceErrorAtom, null);
253
+ try {
254
+ const meta = await loadImageByUrl(url);
255
+ store.set(replacePreviewAtom, url);
256
+ store.set(replaceMetaAtom, meta);
257
+ } catch {
258
+ store.set(replacePreviewAtom, null);
259
+ store.set(replaceMetaAtom, null);
260
+ store.set(replaceErrorAtom, "Image URL could not be loaded");
261
+ } finally {
262
+ store.set(replaceLoadingAtom, false);
263
+ }
264
+ }, [store]);
265
+ const handleReplaceByUrl = useCallback(() => {
266
+ const preview = store.get(replacePreviewAtom);
267
+ if (!preview || !isSafeImageSrc(preview)) return;
268
+ const meta = store.get(replaceMetaAtom);
269
+ withImageNode((node) => {
270
+ node.setSrc(preview);
271
+ node.setDimensions(meta?.width, meta?.height);
272
+ node.setThumbhash(void 0);
273
+ });
274
+ closeReplacePanel();
275
+ }, [closeReplacePanel, store, withImageNode]);
276
+ const handleReplaceOpenChange = useCallback(
277
+ (nextOpen) => {
278
+ store.set(replaceOpenAtom, nextOpen);
279
+ if (!nextOpen) closeReplacePanel();
280
+ },
281
+ [closeReplacePanel, store]
282
+ );
283
+ return {
284
+ commitMeta,
285
+ closeReplacePanel,
286
+ handleReplaceFile,
287
+ handlePreviewUrl,
288
+ handleReplaceByUrl,
289
+ handleReplaceOpenChange,
290
+ handleOpen,
291
+ handleDuplicate,
292
+ handleDownload,
293
+ handleDelete
294
+ };
296
295
  }
297
- //#endregion
298
- //#region src/EditMetaPopover.tsx
299
296
  function EditMetaPopover() {
300
- const editSrc = useAtomValue(editSrcAtom);
301
- const setEditSrc = useSetAtom(editSrcAtom);
302
- const editAltText = useAtomValue(editAltTextAtom);
303
- const setEditAltText = useSetAtom(editAltTextAtom);
304
- const editCaption = useAtomValue(editCaptionAtom);
305
- const setEditCaption = useSetAtom(editCaptionAtom);
306
- const setMetaOpen = useSetAtom(metaOpenAtom);
307
- const { commitMeta } = useImageActions();
308
- const captionInputRef = useRef(null);
309
- const focusCaptionOnOpen = useAtomValue(focusCaptionOnOpenAtom);
310
- const setFocusCaptionOnOpen = useSetAtom(focusCaptionOnOpenAtom);
311
- useEffect(() => {
312
- if (focusCaptionOnOpen && captionInputRef.current) {
313
- captionInputRef.current.focus();
314
- captionInputRef.current.select();
315
- setFocusCaptionOnOpen(false);
316
- }
317
- }, [focusCaptionOnOpen, setFocusCaptionOnOpen]);
318
- return /* @__PURE__ */ jsxs(Fragment, { children: [
319
- /* @__PURE__ */ jsxs("div", {
320
- className: `${editField} ${semanticClassNames.editField}`,
321
- children: [/* @__PURE__ */ jsx(ImageIcon, {
322
- className: `${editFieldIcon} ${semanticClassNames.editFieldIcon}`,
323
- size: 14
324
- }), /* @__PURE__ */ jsx("input", {
325
- className: `${editInput} ${semanticClassNames.editInput}`,
326
- placeholder: "Image URL",
327
- type: "url",
328
- value: editSrc,
329
- onChange: (event) => setEditSrc(event.target.value)
330
- })]
331
- }),
332
- /* @__PURE__ */ jsxs("div", {
333
- className: `${editField} ${semanticClassNames.editField}`,
334
- children: [/* @__PURE__ */ jsx(Type, {
335
- className: `${editFieldIcon} ${semanticClassNames.editFieldIcon}`,
336
- size: 14
337
- }), /* @__PURE__ */ jsx("input", {
338
- className: `${editInput} ${semanticClassNames.editInput}`,
339
- placeholder: "Alt text",
340
- type: "text",
341
- value: editAltText,
342
- onChange: (event) => setEditAltText(event.target.value)
343
- })]
344
- }),
345
- /* @__PURE__ */ jsxs("div", {
346
- className: `${editField} ${semanticClassNames.editField}`,
347
- children: [/* @__PURE__ */ jsx(Captions, {
348
- className: `${editFieldIcon} ${semanticClassNames.editFieldIcon}`,
349
- size: 14
350
- }), /* @__PURE__ */ jsx("input", {
351
- className: `${editInput} ${semanticClassNames.editInput}`,
352
- placeholder: "Caption (optional)",
353
- ref: captionInputRef,
354
- type: "text",
355
- value: editCaption,
356
- onChange: (event) => setEditCaption(event.target.value)
357
- })]
358
- }),
359
- /* @__PURE__ */ jsxs(ActionBar, { children: [/* @__PURE__ */ jsx(ActionButton, {
360
- onClick: () => setMetaOpen(false),
361
- children: "Close"
362
- }), /* @__PURE__ */ jsx(ActionButton, {
363
- variant: "accent",
364
- onClick: commitMeta,
365
- children: "Save"
366
- })] })
367
- ] });
297
+ const editSrc = useAtomValue(editSrcAtom);
298
+ const setEditSrc = useSetAtom(editSrcAtom);
299
+ const editAltText = useAtomValue(editAltTextAtom);
300
+ const setEditAltText = useSetAtom(editAltTextAtom);
301
+ const editCaption = useAtomValue(editCaptionAtom);
302
+ const setEditCaption = useSetAtom(editCaptionAtom);
303
+ const setMetaOpen = useSetAtom(metaOpenAtom);
304
+ const { commitMeta } = useImageActions();
305
+ const captionInputRef = useRef(null);
306
+ const focusCaptionOnOpen = useAtomValue(focusCaptionOnOpenAtom);
307
+ const setFocusCaptionOnOpen = useSetAtom(focusCaptionOnOpenAtom);
308
+ useEffect(() => {
309
+ if (focusCaptionOnOpen && captionInputRef.current) {
310
+ captionInputRef.current.focus();
311
+ captionInputRef.current.select();
312
+ setFocusCaptionOnOpen(false);
313
+ }
314
+ }, [focusCaptionOnOpen, setFocusCaptionOnOpen]);
315
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
316
+ /* @__PURE__ */ jsxs("div", { className: `${editField} ${semanticClassNames.editField}`, children: [
317
+ /* @__PURE__ */ jsx(
318
+ ImageIcon,
319
+ {
320
+ className: `${editFieldIcon} ${semanticClassNames.editFieldIcon}`,
321
+ size: 14
322
+ }
323
+ ),
324
+ /* @__PURE__ */ jsx(
325
+ "input",
326
+ {
327
+ className: `${editInput} ${semanticClassNames.editInput}`,
328
+ placeholder: "Image URL",
329
+ type: "url",
330
+ value: editSrc,
331
+ onChange: (event) => setEditSrc(event.target.value)
332
+ }
333
+ )
334
+ ] }),
335
+ /* @__PURE__ */ jsxs("div", { className: `${editField} ${semanticClassNames.editField}`, children: [
336
+ /* @__PURE__ */ jsx(
337
+ Type,
338
+ {
339
+ className: `${editFieldIcon} ${semanticClassNames.editFieldIcon}`,
340
+ size: 14
341
+ }
342
+ ),
343
+ /* @__PURE__ */ jsx(
344
+ "input",
345
+ {
346
+ className: `${editInput} ${semanticClassNames.editInput}`,
347
+ placeholder: "Alt text",
348
+ type: "text",
349
+ value: editAltText,
350
+ onChange: (event) => setEditAltText(event.target.value)
351
+ }
352
+ )
353
+ ] }),
354
+ /* @__PURE__ */ jsxs("div", { className: `${editField} ${semanticClassNames.editField}`, children: [
355
+ /* @__PURE__ */ jsx(
356
+ Captions,
357
+ {
358
+ className: `${editFieldIcon} ${semanticClassNames.editFieldIcon}`,
359
+ size: 14
360
+ }
361
+ ),
362
+ /* @__PURE__ */ jsx(
363
+ "input",
364
+ {
365
+ className: `${editInput} ${semanticClassNames.editInput}`,
366
+ placeholder: "Caption (optional)",
367
+ ref: captionInputRef,
368
+ type: "text",
369
+ value: editCaption,
370
+ onChange: (event) => setEditCaption(event.target.value)
371
+ }
372
+ )
373
+ ] }),
374
+ /* @__PURE__ */ jsxs(ActionBar, { children: [
375
+ /* @__PURE__ */ jsx(ActionButton, { onClick: () => setMetaOpen(false), children: "Close" }),
376
+ /* @__PURE__ */ jsx(ActionButton, { variant: "accent", onClick: commitMeta, children: "Save" })
377
+ ] })
378
+ ] });
368
379
  }
369
- //#endregion
370
- //#region src/ReplacePanel.tsx
371
- var replaceModeItems = [{
372
- value: "upload",
373
- label: "Upload"
374
- }, {
375
- value: "url",
376
- label: "URL"
377
- }];
380
+ const replaceModeItems = [
381
+ { value: "upload", label: "Upload" },
382
+ { value: "url", label: "URL" }
383
+ ];
378
384
  function ReplacePanel() {
379
- const replaceMode = useAtomValue(replaceModeAtom);
380
- const setReplaceMode = useSetAtom(replaceModeAtom);
381
- const replaceUrl = useAtomValue(replaceUrlAtom);
382
- const setReplaceUrl = useSetAtom(replaceUrlAtom);
383
- const replacePreview$1 = useAtomValue(replacePreviewAtom);
384
- const replaceError = useAtomValue(replaceErrorAtom);
385
- const replaceLoading = useAtomValue(replaceLoadingAtom);
386
- const fileInputRef = useAtomValue(fileInputRefAtom);
387
- const { handleReplaceFile, handlePreviewUrl, handleReplaceByUrl } = useImageActions();
388
- return /* @__PURE__ */ jsxs(Fragment, { children: [
389
- /* @__PURE__ */ jsx(SegmentedControl, {
390
- fullWidth: true,
391
- items: replaceModeItems,
392
- value: replaceMode,
393
- onChange: setReplaceMode
394
- }),
395
- replaceMode === "upload" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("input", {
396
- accept: "image/*",
397
- ref: fileInputRef,
398
- style: { display: "none" },
399
- type: "file",
400
- onChange: (event) => {
401
- handleReplaceFile(event.currentTarget.files?.[0] ?? null);
402
- event.currentTarget.value = "";
403
- }
404
- }), /* @__PURE__ */ jsx("button", {
405
- className: `${replaceUploadArea} ${semanticClassNames.replaceUploadArea}`,
406
- type: "button",
407
- onClick: () => fileInputRef.current?.click(),
408
- children: replaceLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Loader2, { size: 16 }), /* @__PURE__ */ jsx("span", { children: "Uploading image..." })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Upload, { size: 16 }), /* @__PURE__ */ jsx("span", { children: "Click to upload image" })] })
409
- })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
410
- className: `${editField} ${semanticClassNames.editField}`,
411
- children: [/* @__PURE__ */ jsx(ImageIcon, {
412
- className: `${editFieldIcon} ${semanticClassNames.editFieldIcon}`,
413
- size: 14
414
- }), /* @__PURE__ */ jsx("input", {
415
- className: `${editInput} ${semanticClassNames.editInput}`,
416
- placeholder: "https://example.com/image.jpg",
417
- type: "url",
418
- value: replaceUrl,
419
- onChange: (event) => setReplaceUrl(event.target.value),
420
- onKeyDown: (event) => {
421
- if (event.key === "Enter") {
422
- event.preventDefault();
423
- handlePreviewUrl();
424
- }
425
- }
426
- })]
427
- }), /* @__PURE__ */ jsxs(ActionBar, { children: [/* @__PURE__ */ jsx(ActionButton, {
428
- disabled: replaceLoading || !replaceUrl.trim(),
429
- onClick: handlePreviewUrl,
430
- children: "Preview"
431
- }), /* @__PURE__ */ jsx(ActionButton, {
432
- disabled: !replacePreview$1,
433
- variant: "accent",
434
- onClick: handleReplaceByUrl,
435
- children: "Apply"
436
- })] })] }),
437
- replaceError && /* @__PURE__ */ jsx("span", {
438
- className: `_1n94osfw ${semanticClassNames.panelHint}`,
439
- children: replaceError
440
- }),
441
- replacePreview$1 && /* @__PURE__ */ jsx("div", {
442
- className: `_1n94osfv ${semanticClassNames.replacePreview}`,
443
- children: /* @__PURE__ */ jsx("img", {
444
- alt: "Replace preview",
445
- src: replacePreview$1
446
- })
447
- })
448
- ] });
385
+ const replaceMode = useAtomValue(replaceModeAtom);
386
+ const setReplaceMode = useSetAtom(replaceModeAtom);
387
+ const replaceUrl = useAtomValue(replaceUrlAtom);
388
+ const setReplaceUrl = useSetAtom(replaceUrlAtom);
389
+ const replacePreview$1 = useAtomValue(replacePreviewAtom);
390
+ const replaceError = useAtomValue(replaceErrorAtom);
391
+ const replaceLoading = useAtomValue(replaceLoadingAtom);
392
+ const fileInputRef = useAtomValue(fileInputRefAtom);
393
+ const { handleReplaceFile, handlePreviewUrl, handleReplaceByUrl } = useImageActions();
394
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
395
+ /* @__PURE__ */ jsx(
396
+ SegmentedControl,
397
+ {
398
+ fullWidth: true,
399
+ items: replaceModeItems,
400
+ value: replaceMode,
401
+ onChange: setReplaceMode
402
+ }
403
+ ),
404
+ replaceMode === "upload" ? /* @__PURE__ */ jsxs(Fragment, { children: [
405
+ /* @__PURE__ */ jsx(
406
+ "input",
407
+ {
408
+ accept: "image/*",
409
+ ref: fileInputRef,
410
+ style: { display: "none" },
411
+ type: "file",
412
+ onChange: (event) => {
413
+ const file = event.currentTarget.files?.[0] ?? null;
414
+ void handleReplaceFile(file);
415
+ event.currentTarget.value = "";
416
+ }
417
+ }
418
+ ),
419
+ /* @__PURE__ */ jsx(
420
+ "button",
421
+ {
422
+ className: `${replaceUploadArea} ${semanticClassNames.replaceUploadArea}`,
423
+ type: "button",
424
+ onClick: () => fileInputRef.current?.click(),
425
+ children: replaceLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
426
+ /* @__PURE__ */ jsx(Loader2, { size: 16 }),
427
+ /* @__PURE__ */ jsx("span", { children: "Uploading image..." })
428
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
429
+ /* @__PURE__ */ jsx(Upload, { size: 16 }),
430
+ /* @__PURE__ */ jsx("span", { children: "Click to upload image" })
431
+ ] })
432
+ }
433
+ )
434
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
435
+ /* @__PURE__ */ jsxs("div", { className: `${editField} ${semanticClassNames.editField}`, children: [
436
+ /* @__PURE__ */ jsx(
437
+ ImageIcon,
438
+ {
439
+ className: `${editFieldIcon} ${semanticClassNames.editFieldIcon}`,
440
+ size: 14
441
+ }
442
+ ),
443
+ /* @__PURE__ */ jsx(
444
+ "input",
445
+ {
446
+ className: `${editInput} ${semanticClassNames.editInput}`,
447
+ placeholder: "https://example.com/image.jpg",
448
+ type: "url",
449
+ value: replaceUrl,
450
+ onChange: (event) => setReplaceUrl(event.target.value),
451
+ onKeyDown: (event) => {
452
+ if (event.key === "Enter") {
453
+ event.preventDefault();
454
+ handlePreviewUrl();
455
+ }
456
+ }
457
+ }
458
+ )
459
+ ] }),
460
+ /* @__PURE__ */ jsxs(ActionBar, { children: [
461
+ /* @__PURE__ */ jsx(
462
+ ActionButton,
463
+ {
464
+ disabled: replaceLoading || !replaceUrl.trim(),
465
+ onClick: handlePreviewUrl,
466
+ children: "Preview"
467
+ }
468
+ ),
469
+ /* @__PURE__ */ jsx(ActionButton, { disabled: !replacePreview$1, variant: "accent", onClick: handleReplaceByUrl, children: "Apply" })
470
+ ] })
471
+ ] }),
472
+ replaceError && /* @__PURE__ */ jsx("span", { className: `${panelHint} ${semanticClassNames.panelHint}`, children: replaceError }),
473
+ replacePreview$1 && /* @__PURE__ */ jsx("div", { className: `${replacePreview} ${semanticClassNames.replacePreview}`, children: /* @__PURE__ */ jsx("img", { alt: "Replace preview", src: replacePreview$1 }) })
474
+ ] });
449
475
  }
450
- //#endregion
451
- //#region src/ImageEditToolbar.tsx
452
476
  function ImageEditToolbar() {
453
- const toolbarVisible = useAtomValue(toolbarVisibleAtom);
454
- const metaOpen = useAtomValue(metaOpenAtom);
455
- const setMetaOpen = useSetAtom(metaOpenAtom);
456
- const replaceOpen = useAtomValue(replaceOpenAtom);
457
- const { handleReplaceOpenChange, handleOpen, handleDuplicate, handleDownload, handleDelete } = useImageActions();
458
- return /* @__PURE__ */ jsxs("div", {
459
- className: `${editToolbar} ${semanticClassNames.editToolbar} ${toolbarVisible ? `${editToolbarVisible} ${semanticClassNames.editToolbarVisible}` : ""}`,
460
- children: [
461
- /* @__PURE__ */ jsxs(Popover, {
462
- open: metaOpen,
463
- onOpenChange: (nextOpen) => {
464
- setMetaOpen(nextOpen);
465
- if (nextOpen) handleReplaceOpenChange(false);
466
- },
467
- children: [/* @__PURE__ */ jsx(PopoverTrigger, {
468
- className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
469
- title: "Edit details",
470
- children: /* @__PURE__ */ jsx(Type, { size: 14 })
471
- }), /* @__PURE__ */ jsx(PopoverPanel, {
472
- className: editPanel,
473
- side: "bottom",
474
- sideOffset: 8,
475
- children: /* @__PURE__ */ jsx(EditMetaPopover, {})
476
- })]
477
- }),
478
- /* @__PURE__ */ jsxs(Popover, {
479
- open: replaceOpen,
480
- onOpenChange: (nextOpen) => {
481
- handleReplaceOpenChange(nextOpen);
482
- if (nextOpen) setMetaOpen(false);
483
- },
484
- children: [/* @__PURE__ */ jsx(PopoverTrigger, {
485
- className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
486
- title: "Replace image",
487
- children: /* @__PURE__ */ jsx(Replace, { size: 14 })
488
- }), /* @__PURE__ */ jsx(PopoverPanel, {
489
- className: editPanel,
490
- side: "bottom",
491
- sideOffset: 8,
492
- children: /* @__PURE__ */ jsx(ReplacePanel, {})
493
- })]
494
- }),
495
- /* @__PURE__ */ jsx("button", {
496
- className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
497
- title: "Open source",
498
- type: "button",
499
- onClick: handleOpen,
500
- onMouseDown: (e) => {
501
- e.preventDefault();
502
- e.stopPropagation();
503
- },
504
- children: /* @__PURE__ */ jsx(ExternalLink, { size: 14 })
505
- }),
506
- /* @__PURE__ */ jsx("button", {
507
- className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
508
- title: "Duplicate",
509
- type: "button",
510
- onClick: handleDuplicate,
511
- onMouseDown: (e) => {
512
- e.preventDefault();
513
- e.stopPropagation();
514
- },
515
- children: /* @__PURE__ */ jsx(Copy, { size: 14 })
516
- }),
517
- /* @__PURE__ */ jsx("button", {
518
- className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
519
- title: "Download",
520
- type: "button",
521
- onClick: handleDownload,
522
- onMouseDown: (e) => {
523
- e.preventDefault();
524
- e.stopPropagation();
525
- },
526
- children: /* @__PURE__ */ jsx(Download, { size: 14 })
527
- }),
528
- /* @__PURE__ */ jsx("button", {
529
- className: `${editToolbarButton} ${semanticClassNames.editToolbarButton} ${editToolbarButtonDanger} ${semanticClassNames.editToolbarButtonDanger}`,
530
- title: "Remove image",
531
- type: "button",
532
- onClick: handleDelete,
533
- onMouseDown: (e) => {
534
- e.preventDefault();
535
- e.stopPropagation();
536
- },
537
- children: /* @__PURE__ */ jsx(Trash2, { size: 14 })
538
- })
539
- ]
540
- });
477
+ const toolbarVisible = useAtomValue(toolbarVisibleAtom);
478
+ const metaOpen = useAtomValue(metaOpenAtom);
479
+ const setMetaOpen = useSetAtom(metaOpenAtom);
480
+ const replaceOpen = useAtomValue(replaceOpenAtom);
481
+ const { handleReplaceOpenChange, handleOpen, handleDuplicate, handleDownload, handleDelete } = useImageActions();
482
+ return /* @__PURE__ */ jsxs(
483
+ "div",
484
+ {
485
+ className: `${editToolbar} ${semanticClassNames.editToolbar} ${toolbarVisible ? `${editToolbarVisible} ${semanticClassNames.editToolbarVisible}` : ""}`,
486
+ children: [
487
+ /* @__PURE__ */ jsxs(
488
+ Popover,
489
+ {
490
+ open: metaOpen,
491
+ onOpenChange: (nextOpen) => {
492
+ setMetaOpen(nextOpen);
493
+ if (nextOpen) handleReplaceOpenChange(false);
494
+ },
495
+ children: [
496
+ /* @__PURE__ */ jsx(
497
+ PopoverTrigger,
498
+ {
499
+ className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
500
+ title: "Edit details",
501
+ children: /* @__PURE__ */ jsx(Type, { size: 14 })
502
+ }
503
+ ),
504
+ /* @__PURE__ */ jsx(PopoverPanel, { className: editPanel, side: "bottom", sideOffset: 8, children: /* @__PURE__ */ jsx(EditMetaPopover, {}) })
505
+ ]
506
+ }
507
+ ),
508
+ /* @__PURE__ */ jsxs(
509
+ Popover,
510
+ {
511
+ open: replaceOpen,
512
+ onOpenChange: (nextOpen) => {
513
+ handleReplaceOpenChange(nextOpen);
514
+ if (nextOpen) setMetaOpen(false);
515
+ },
516
+ children: [
517
+ /* @__PURE__ */ jsx(
518
+ PopoverTrigger,
519
+ {
520
+ className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
521
+ title: "Replace image",
522
+ children: /* @__PURE__ */ jsx(Replace, { size: 14 })
523
+ }
524
+ ),
525
+ /* @__PURE__ */ jsx(PopoverPanel, { className: editPanel, side: "bottom", sideOffset: 8, children: /* @__PURE__ */ jsx(ReplacePanel, {}) })
526
+ ]
527
+ }
528
+ ),
529
+ /* @__PURE__ */ jsx(
530
+ "button",
531
+ {
532
+ className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
533
+ title: "Open source",
534
+ type: "button",
535
+ onClick: handleOpen,
536
+ onMouseDown: (e) => {
537
+ e.preventDefault();
538
+ e.stopPropagation();
539
+ },
540
+ children: /* @__PURE__ */ jsx(ExternalLink, { size: 14 })
541
+ }
542
+ ),
543
+ /* @__PURE__ */ jsx(
544
+ "button",
545
+ {
546
+ className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
547
+ title: "Duplicate",
548
+ type: "button",
549
+ onClick: handleDuplicate,
550
+ onMouseDown: (e) => {
551
+ e.preventDefault();
552
+ e.stopPropagation();
553
+ },
554
+ children: /* @__PURE__ */ jsx(Copy, { size: 14 })
555
+ }
556
+ ),
557
+ /* @__PURE__ */ jsx(
558
+ "button",
559
+ {
560
+ className: `${editToolbarButton} ${semanticClassNames.editToolbarButton}`,
561
+ title: "Download",
562
+ type: "button",
563
+ onClick: handleDownload,
564
+ onMouseDown: (e) => {
565
+ e.preventDefault();
566
+ e.stopPropagation();
567
+ },
568
+ children: /* @__PURE__ */ jsx(Download, { size: 14 })
569
+ }
570
+ ),
571
+ /* @__PURE__ */ jsx(
572
+ "button",
573
+ {
574
+ className: `${editToolbarButton} ${semanticClassNames.editToolbarButton} ${editToolbarButtonDanger} ${semanticClassNames.editToolbarButtonDanger}`,
575
+ title: "Remove image",
576
+ type: "button",
577
+ onClick: handleDelete,
578
+ onMouseDown: (e) => {
579
+ e.preventDefault();
580
+ e.stopPropagation();
581
+ },
582
+ children: /* @__PURE__ */ jsx(Trash2, { size: 14 })
583
+ }
584
+ )
585
+ ]
586
+ }
587
+ );
541
588
  }
542
- //#endregion
543
- //#region src/ReplacePopover.tsx
544
589
  function ReplacePopover() {
545
- const replaceOpen = useAtomValue(replaceOpenAtom);
546
- const { handleReplaceOpenChange } = useImageActions();
547
- return /* @__PURE__ */ jsxs(Popover, {
548
- open: replaceOpen,
549
- onOpenChange: handleReplaceOpenChange,
550
- children: [/* @__PURE__ */ jsxs(PopoverTrigger, {
551
- className: `${editPlaceholder} ${semanticClassNames.editPlaceholder}`,
552
- children: [/* @__PURE__ */ jsx(ImageIcon, { size: 24 }), /* @__PURE__ */ jsx("span", { children: "Click to add image" })]
553
- }), /* @__PURE__ */ jsx(PopoverPanel, {
554
- className: editPanel,
555
- side: "bottom",
556
- sideOffset: 8,
557
- children: /* @__PURE__ */ jsx(ReplacePanel, {})
558
- })]
559
- });
590
+ const replaceOpen = useAtomValue(replaceOpenAtom);
591
+ const { handleReplaceOpenChange } = useImageActions();
592
+ return /* @__PURE__ */ jsxs(Popover, { open: replaceOpen, onOpenChange: handleReplaceOpenChange, children: [
593
+ /* @__PURE__ */ jsxs(
594
+ PopoverTrigger,
595
+ {
596
+ className: `${editPlaceholder} ${semanticClassNames.editPlaceholder}`,
597
+ children: [
598
+ /* @__PURE__ */ jsx(ImageIcon, { size: 24 }),
599
+ /* @__PURE__ */ jsx("span", { children: "Click to add image" })
600
+ ]
601
+ }
602
+ ),
603
+ /* @__PURE__ */ jsx(PopoverPanel, { className: editPanel, side: "bottom", sideOffset: 8, children: /* @__PURE__ */ jsx(ReplacePanel, {}) })
604
+ ] });
560
605
  }
561
- //#endregion
562
- //#region src/ImageEditRenderer.tsx
563
- var frameStateSemanticClass = {
564
- loading: semanticClassNames.frameLoading,
565
- loaded: semanticClassNames.frameLoaded,
566
- error: semanticClassNames.frameError
606
+ const frameStateSemanticClass = {
607
+ loading: semanticClassNames.frameLoading,
608
+ loaded: semanticClassNames.frameLoaded,
609
+ error: semanticClassNames.frameError
567
610
  };
568
611
  function ImageEditRenderer(props) {
569
- if (useRendererMode() !== "editor") return /* @__PURE__ */ jsx(ImageRenderer, { ...props });
570
- return /* @__PURE__ */ jsx(ImageEditRendererInner, { ...props });
612
+ const mode = useRendererMode();
613
+ if (mode !== "editor") {
614
+ return /* @__PURE__ */ jsx(ImageRenderer, { ...props });
615
+ }
616
+ return /* @__PURE__ */ jsx(ImageEditRendererInner, { ...props });
571
617
  }
572
618
  function ImageEditRendererInner(props) {
573
- const [editor] = useLexicalComposerContext();
574
- if (!editor.isEditable()) return /* @__PURE__ */ jsx(ImageRenderer, { ...props });
575
- return /* @__PURE__ */ jsx(ImageEditProvider, {
576
- props,
577
- children: /* @__PURE__ */ jsx(ImageEditContent, {})
578
- });
619
+ const [editor] = useLexicalComposerContext();
620
+ const editable = editor.isEditable();
621
+ if (!editable) {
622
+ return /* @__PURE__ */ jsx(ImageRenderer, { ...props });
623
+ }
624
+ return /* @__PURE__ */ jsx(ImageEditProvider, { props, children: /* @__PURE__ */ jsx(ImageEditContent, {}) });
579
625
  }
580
626
  function ImageEditContent() {
581
- const wrapperRef = useAtomValue(wrapperRefAtom);
582
- const setHovering = useSetAtom(hoveringAtom);
583
- const src = useAtomValue(srcAtom);
584
- const loadState = useAtomValue(loadStateAtom);
585
- const setLoadState = useSetAtom(loadStateAtom);
586
- const frameStyle = useAtomValue(frameStyleAtom);
587
- const altText = useAtomValue(altTextAtom);
588
- const width = useAtomValue(widthAtom);
589
- const height = useAtomValue(heightAtom);
590
- const captionText = useAtomValue(captionTextAtom);
591
- const setMetaOpen = useSetAtom(metaOpenAtom);
592
- const setReplaceOpen = useSetAtom(replaceOpenAtom);
593
- const setFocusCaptionOnOpen = useSetAtom(focusCaptionOnOpenAtom);
594
- const handleCaptionClick = useCallback((e) => {
595
- e.stopPropagation();
596
- const scrollY = window.scrollY;
597
- setReplaceOpen(false);
598
- setMetaOpen(true);
599
- setFocusCaptionOnOpen(true);
600
- requestAnimationFrame(() => {
601
- window.scrollTo({ top: scrollY });
602
- });
603
- }, [
604
- setMetaOpen,
605
- setReplaceOpen,
606
- setFocusCaptionOnOpen
607
- ]);
608
- return /* @__PURE__ */ jsxs("div", {
609
- className: `${editTrigger} ${semanticClassNames.editTrigger}`,
610
- ref: wrapperRef,
611
- onMouseEnter: () => setHovering(true),
612
- onMouseLeave: () => setHovering(false),
613
- children: [src ? /* @__PURE__ */ jsxs("figure", {
614
- className: `${root} ${semanticClassNames.root}`,
615
- children: [/* @__PURE__ */ jsxs("div", {
616
- className: `${frame} ${semanticClassNames.frame} ${frameEditMode} ${imageState[loadState]} ${frameStateSemanticClass[loadState]}`.trim(),
617
- style: frameStyle,
618
- children: [
619
- /* @__PURE__ */ jsx("img", {
620
- alt: altText,
621
- className: `${image} ${loadState === "loaded" ? imageVisible : ""} ${semanticClassNames.image}`,
622
- height,
623
- loading: "lazy",
624
- src,
625
- width,
626
- onError: () => setLoadState("error"),
627
- onLoad: () => setLoadState("loaded")
628
- }),
629
- loadState === "loading" && /* @__PURE__ */ jsx("span", { className: `_1n94osfa ${semanticClassNames.loader}` }),
630
- loadState === "error" && /* @__PURE__ */ jsx("span", {
631
- className: `_1n94osfb ${semanticClassNames.errorBadge}`,
632
- children: "Image failed to load"
633
- })
634
- ]
635
- }), captionText && /* @__PURE__ */ jsx("figcaption", {
636
- className: `_1n94osfc ${semanticClassNames.caption}`,
637
- onClick: handleCaptionClick,
638
- children: captionText
639
- })]
640
- }) : /* @__PURE__ */ jsx(ReplacePopover, {}), src && /* @__PURE__ */ jsx(ImageEditToolbar, {})]
641
- });
627
+ const wrapperRef = useAtomValue(wrapperRefAtom);
628
+ const setHovering = useSetAtom(hoveringAtom);
629
+ const src = useAtomValue(srcAtom);
630
+ const loadState = useAtomValue(loadStateAtom);
631
+ const setLoadState = useSetAtom(loadStateAtom);
632
+ const frameStyle = useAtomValue(frameStyleAtom);
633
+ const altText = useAtomValue(altTextAtom);
634
+ const width = useAtomValue(widthAtom);
635
+ const height = useAtomValue(heightAtom);
636
+ const captionText = useAtomValue(captionTextAtom);
637
+ const setMetaOpen = useSetAtom(metaOpenAtom);
638
+ const setReplaceOpen = useSetAtom(replaceOpenAtom);
639
+ const setFocusCaptionOnOpen = useSetAtom(focusCaptionOnOpenAtom);
640
+ const handleCaptionClick = useCallback(
641
+ (e) => {
642
+ e.stopPropagation();
643
+ const scrollY = window.scrollY;
644
+ setReplaceOpen(false);
645
+ setMetaOpen(true);
646
+ setFocusCaptionOnOpen(true);
647
+ requestAnimationFrame(() => {
648
+ window.scrollTo({ top: scrollY });
649
+ });
650
+ },
651
+ [setMetaOpen, setReplaceOpen, setFocusCaptionOnOpen]
652
+ );
653
+ return /* @__PURE__ */ jsxs(
654
+ "div",
655
+ {
656
+ className: `${editTrigger} ${semanticClassNames.editTrigger}`,
657
+ ref: wrapperRef,
658
+ onMouseEnter: () => setHovering(true),
659
+ onMouseLeave: () => setHovering(false),
660
+ children: [
661
+ src ? /* @__PURE__ */ jsxs("figure", { className: `${root} ${semanticClassNames.root}`, children: [
662
+ /* @__PURE__ */ jsxs(
663
+ "div",
664
+ {
665
+ className: `${frame} ${semanticClassNames.frame} ${frameEditMode} ${imageState[loadState]} ${frameStateSemanticClass[loadState]}`.trim(),
666
+ style: frameStyle,
667
+ children: [
668
+ /* @__PURE__ */ jsx(
669
+ "img",
670
+ {
671
+ alt: altText,
672
+ className: `${image} ${loadState === "loaded" ? imageVisible : ""} ${semanticClassNames.image}`,
673
+ height,
674
+ loading: "lazy",
675
+ src,
676
+ width,
677
+ onError: () => setLoadState("error"),
678
+ onLoad: () => setLoadState("loaded")
679
+ }
680
+ ),
681
+ loadState === "loading" && /* @__PURE__ */ jsx("span", { className: `${loader} ${semanticClassNames.loader}` }),
682
+ loadState === "error" && /* @__PURE__ */ jsx("span", { className: `${errorBadge} ${semanticClassNames.errorBadge}`, children: "Image failed to load" })
683
+ ]
684
+ }
685
+ ),
686
+ captionText && /* @__PURE__ */ jsx(
687
+ "figcaption",
688
+ {
689
+ className: `${caption} ${semanticClassNames.caption}`,
690
+ onClick: handleCaptionClick,
691
+ children: captionText
692
+ }
693
+ )
694
+ ] }) : /* @__PURE__ */ jsx(ReplacePopover, {}),
695
+ src && /* @__PURE__ */ jsx(ImageEditToolbar, {})
696
+ ]
697
+ }
698
+ );
642
699
  }
643
- //#endregion
644
- export { ImageEditRenderer, ImageRenderer };
700
+ export {
701
+ ImageEditRenderer,
702
+ ImageRenderer
703
+ };