@haklex/rich-ext-gallery 0.26.1 → 0.26.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"GalleryEditRenderer.d.ts","sourceRoot":"","sources":["../src/GalleryEditRenderer.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAiB,EAAE,EAAE,MAAM,OAAO,CAAC;AAM/C,OAAO,KAAK,EAAgB,oBAAoB,EAAE,MAAM,SAAS,CAAC;AA6RlE,eAAO,MAAM,mBAAmB,EAAE,EAAE,CAAC,oBAAoB,CA6DxD,CAAC"}
1
+ {"version":3,"file":"GalleryEditRenderer.d.ts","sourceRoot":"","sources":["../src/GalleryEditRenderer.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAA8C,EAAE,EAAE,MAAM,OAAO,CAAC;AAM5E,OAAO,KAAK,EAAgB,oBAAoB,EAAE,MAAM,SAAS,CAAC;AA0alE,eAAO,MAAM,mBAAmB,EAAE,EAAE,CAAC,oBAAoB,CA+DxD,CAAC"}
@@ -6,6 +6,7 @@ import { createElement, useCallback, useRef, useState } from "react";
6
6
  import { DndContext, DragOverlay, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
7
7
  import { SortableContext, arrayMove, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
8
8
  import { CSS } from "@dnd-kit/utilities";
9
+ import { useImageUpload } from "@haklex/rich-editor/plugins";
9
10
  import { useColorScheme } from "@haklex/rich-editor/static";
10
11
  import { SegmentedControl, presentDialog } from "@haklex/rich-editor-ui";
11
12
  import { usePortalTheme, vars } from "@haklex/rich-style-token";
@@ -30,16 +31,71 @@ var nextId = 0;
30
31
  function genId() {
31
32
  return `img-${++nextId}`;
32
33
  }
33
- var SortableImageCard = ({ id, image, index, isLast, lastInputRef, onUpdate, onRemove }) => {
34
+ function readAsDataUrl(file) {
35
+ return new Promise((resolve, reject) => {
36
+ const reader = new FileReader();
37
+ reader.onload = () => resolve(String(reader.result));
38
+ reader.onerror = () => reject(reader.error ?? /* @__PURE__ */ new Error("File read failed"));
39
+ reader.readAsDataURL(file);
40
+ });
41
+ }
42
+ async function fileToGalleryImage(file, uploader) {
43
+ if (uploader) {
44
+ const result = await uploader(file);
45
+ return {
46
+ src: result.src,
47
+ alt: result.altText ?? file.name
48
+ };
49
+ }
50
+ return {
51
+ src: await readAsDataUrl(file),
52
+ alt: file.name
53
+ };
54
+ }
55
+ function extractImageFiles(list) {
56
+ if (!list) return [];
57
+ const files = [];
58
+ for (let i = 0; i < list.length; i++) {
59
+ const f = list[i];
60
+ if (f && f.type.startsWith("image/")) files.push(f);
61
+ }
62
+ return files;
63
+ }
64
+ var SortableImageCard = ({ id, image, index, isLast, lastInputRef, onUpdate, onRemove, onDropFile }) => {
34
65
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
66
+ const [dropActive, setDropActive] = useState(false);
67
+ const style = {
68
+ transform: CSS.Transform.toString(transform),
69
+ transition: isDragging ? void 0 : transition,
70
+ opacity: isDragging ? .4 : 1,
71
+ outline: dropActive ? `2px dashed ${vars.color.accent}` : void 0,
72
+ outlineOffset: dropActive ? 2 : void 0
73
+ };
74
+ const handleDragOver = (e) => {
75
+ if (!e.dataTransfer?.types.includes("Files")) return;
76
+ e.preventDefault();
77
+ e.dataTransfer.dropEffect = "copy";
78
+ if (!dropActive) setDropActive(true);
79
+ };
80
+ const handleDragLeave = (e) => {
81
+ if (e.currentTarget.contains(e.relatedTarget)) return;
82
+ setDropActive(false);
83
+ };
84
+ const handleDrop = (e) => {
85
+ const files = extractImageFiles(e.dataTransfer?.files);
86
+ setDropActive(false);
87
+ if (!files.length) return;
88
+ e.preventDefault();
89
+ e.stopPropagation();
90
+ onDropFile(index, files[0]);
91
+ };
35
92
  return /* @__PURE__ */ jsxs("div", {
36
93
  className: galleryImageCard,
37
94
  ref: setNodeRef,
38
- style: {
39
- transform: CSS.Transform.toString(transform),
40
- transition: isDragging ? void 0 : transition,
41
- opacity: isDragging ? .4 : 1
42
- },
95
+ style,
96
+ onDragLeave: handleDragLeave,
97
+ onDragOver: handleDragOver,
98
+ onDrop: handleDrop,
43
99
  children: [
44
100
  /* @__PURE__ */ jsx("div", {
45
101
  className: galleryImageDragHandle,
@@ -49,6 +105,7 @@ var SortableImageCard = ({ id, image, index, isLast, lastInputRef, onUpdate, onR
49
105
  }),
50
106
  /* @__PURE__ */ jsx("div", {
51
107
  className: galleryImageThumb,
108
+ title: "Drop an image here to replace",
52
109
  children: image.src ? /* @__PURE__ */ jsx("img", {
53
110
  alt: image.alt || "",
54
111
  src: image.src
@@ -119,7 +176,7 @@ var DragOverlayCard = ({ image }) => /* @__PURE__ */ jsxs("div", {
119
176
  })
120
177
  ]
121
178
  });
122
- var GalleryEditorDialogContent = ({ dismiss, initialImages, initialLayout, onImagesChange, onLayoutChange }) => {
179
+ var GalleryEditorDialogContent = ({ dismiss, initialImages, initialLayout, uploader, onImagesChange, onLayoutChange }) => {
123
180
  const { className: portalClassName } = usePortalTheme();
124
181
  const [entries, setEntries] = useState(() => initialImages.map((img) => ({
125
182
  id: genId(),
@@ -128,6 +185,8 @@ var GalleryEditorDialogContent = ({ dismiss, initialImages, initialLayout, onIma
128
185
  const [layout, setLayout] = useState(initialLayout);
129
186
  const newInputRef = useRef(null);
130
187
  const [dragActiveId, setDragActiveId] = useState(null);
188
+ const [bodyDropActive, setBodyDropActive] = useState(false);
189
+ const dragDepthRef = useRef(0);
131
190
  const itemIds = entries.map((e) => e.id);
132
191
  const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }));
133
192
  const handleAddImage = useCallback(() => {
@@ -145,6 +204,32 @@ var GalleryEditorDialogContent = ({ dismiss, initialImages, initialLayout, onIma
145
204
  const handleRemoveImage = useCallback((index) => {
146
205
  setEntries((prev) => prev.filter((_, i) => i !== index));
147
206
  }, []);
207
+ const appendFiles = useCallback(async (files) => {
208
+ if (!files.length) return;
209
+ const fresh = (await Promise.all(files.map(async (file) => {
210
+ try {
211
+ return await fileToGalleryImage(file, uploader);
212
+ } catch {
213
+ return null;
214
+ }
215
+ }))).filter((r) => !!r && !!r.src).map((image) => ({
216
+ id: genId(),
217
+ image
218
+ }));
219
+ if (fresh.length) setEntries((prev) => [...prev, ...fresh]);
220
+ }, [uploader]);
221
+ const replaceEntryFile = useCallback(async (index, file) => {
222
+ try {
223
+ const next = await fileToGalleryImage(file, uploader);
224
+ setEntries((prev) => prev.map((entry, i) => i === index ? {
225
+ ...entry,
226
+ image: {
227
+ src: next.src,
228
+ alt: next.alt || entry.image.alt || ""
229
+ }
230
+ } : entry));
231
+ } catch {}
232
+ }, [uploader]);
148
233
  const handleUpdateImage = useCallback((index, field, value) => {
149
234
  setEntries((prev) => prev.map((entry, i) => i === index ? {
150
235
  ...entry,
@@ -177,6 +262,29 @@ var GalleryEditorDialogContent = ({ dismiss, initialImages, initialLayout, onIma
177
262
  dismiss
178
263
  ]);
179
264
  const dragActiveEntry = dragActiveId ? entries.find((e) => e.id === dragActiveId) : null;
265
+ const handleBodyDragEnter = (e) => {
266
+ if (!e.dataTransfer?.types.includes("Files")) return;
267
+ dragDepthRef.current += 1;
268
+ setBodyDropActive(true);
269
+ };
270
+ const handleBodyDragOver = (e) => {
271
+ if (!e.dataTransfer?.types.includes("Files")) return;
272
+ e.preventDefault();
273
+ e.dataTransfer.dropEffect = "copy";
274
+ };
275
+ const handleBodyDragLeave = (e) => {
276
+ if (!e.dataTransfer?.types.includes("Files")) return;
277
+ dragDepthRef.current = Math.max(0, dragDepthRef.current - 1);
278
+ if (dragDepthRef.current === 0) setBodyDropActive(false);
279
+ };
280
+ const handleBodyDrop = (e) => {
281
+ const files = extractImageFiles(e.dataTransfer?.files);
282
+ dragDepthRef.current = 0;
283
+ setBodyDropActive(false);
284
+ if (!files.length) return;
285
+ e.preventDefault();
286
+ appendFiles(files);
287
+ };
180
288
  return /* @__PURE__ */ jsxs(Fragment, { children: [
181
289
  /* @__PURE__ */ jsxs("div", {
182
290
  className: galleryDialogHeader,
@@ -203,11 +311,20 @@ var GalleryEditorDialogContent = ({ dismiss, initialImages, initialLayout, onIma
203
311
  }),
204
312
  /* @__PURE__ */ jsx("div", {
205
313
  className: galleryDialogBody,
314
+ style: bodyDropActive ? {
315
+ outline: `2px dashed ${vars.color.accent}`,
316
+ outlineOffset: -8,
317
+ borderRadius: 8
318
+ } : void 0,
319
+ onDragEnter: handleBodyDragEnter,
320
+ onDragLeave: handleBodyDragLeave,
321
+ onDragOver: handleBodyDragOver,
322
+ onDrop: handleBodyDrop,
206
323
  children: entries.length === 0 ? /* @__PURE__ */ jsxs("div", {
207
324
  className: galleryDialogEmpty,
208
325
  children: [
209
326
  /* @__PURE__ */ jsx(ImageIcon, { size: 32 }),
210
- /* @__PURE__ */ jsx("span", { children: "Add your first image" }),
327
+ /* @__PURE__ */ jsx("span", { children: "Add your first image or drop files here" }),
211
328
  /* @__PURE__ */ jsxs("button", {
212
329
  className: galleryAddBtn,
213
330
  style: { maxWidth: 200 },
@@ -235,6 +352,7 @@ var GalleryEditorDialogContent = ({ dismiss, initialImages, initialLayout, onIma
235
352
  index,
236
353
  isLast: index === entries.length - 1,
237
354
  lastInputRef: newInputRef,
355
+ onDropFile: replaceEntryFile,
238
356
  onRemove: handleRemoveImage,
239
357
  onUpdate: handleUpdateImage
240
358
  }, entry.id))
@@ -278,6 +396,7 @@ var GalleryEditorDialogContent = ({ dismiss, initialImages, initialLayout, onIma
278
396
  var GalleryEditRenderer = ({ images, layout, onImagesChange, onLayoutChange }) => {
279
397
  const { className: portalClassName } = usePortalTheme();
280
398
  const theme = useColorScheme();
399
+ const uploader = useImageUpload();
281
400
  const handleOpenEditor = useCallback(() => {
282
401
  if (!onImagesChange || !onLayoutChange) return;
283
402
  presentDialog({
@@ -285,6 +404,7 @@ var GalleryEditRenderer = ({ images, layout, onImagesChange, onLayoutChange }) =
285
404
  dismiss,
286
405
  initialImages: images,
287
406
  initialLayout: layout,
407
+ uploader,
288
408
  onImagesChange,
289
409
  onLayoutChange
290
410
  }),
@@ -300,7 +420,8 @@ var GalleryEditRenderer = ({ images, layout, onImagesChange, onLayoutChange }) =
300
420
  onImagesChange,
301
421
  onLayoutChange,
302
422
  portalClassName,
303
- theme
423
+ theme,
424
+ uploader
304
425
  ]);
305
426
  if (!onImagesChange) return /* @__PURE__ */ jsx(GalleryRenderer, {
306
427
  images,
package/dist/edit.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  import { t as __augmentLoaded_gallery } from "./augment-DgwFVGf9.js";
2
- import { a as GalleryEditRenderer, i as GalleryEditNode, n as $createGalleryEditNode, r as $isGalleryEditNode, t as galleryEditNodes } from "./edit-U3YtpHCm.js";
2
+ import { a as GalleryEditRenderer, i as GalleryEditNode, n as $createGalleryEditNode, r as $isGalleryEditNode, t as galleryEditNodes } from "./edit-C_0ARS8I.js";
3
3
  export { $createGalleryEditNode, $isGalleryEditNode, GalleryEditNode, GalleryEditRenderer, __augmentLoaded_gallery, galleryEditNodes };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { t as __augmentLoaded_gallery } from "./augment-DgwFVGf9.js";
2
2
  import { t as GalleryRenderer } from "./GalleryRenderer-CiHBgw2X.js";
3
- import { a as GalleryEditRenderer, i as GalleryEditNode, n as $createGalleryEditNode, r as $isGalleryEditNode, t as galleryEditNodes } from "./edit-U3YtpHCm.js";
3
+ import { a as GalleryEditRenderer, i as GalleryEditNode, n as $createGalleryEditNode, r as $isGalleryEditNode, t as galleryEditNodes } from "./edit-C_0ARS8I.js";
4
4
  import { a as GALLERY_NODE_KEY, n as $isGalleryNode, r as GalleryNode, t as $createGalleryNode } from "./GalleryNode-CvAj_vjz.js";
5
5
  import { galleryNodes } from "./node.mjs";
6
6
  import "./renderer.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haklex/rich-ext-gallery",
3
- "version": "0.26.1",
3
+ "version": "0.26.3",
4
4
  "description": "Image gallery extension with drag-and-drop",
5
5
  "repository": {
6
6
  "type": "git",
@@ -60,9 +60,9 @@
60
60
  "lucide-react": "^1.0.0",
61
61
  "react": ">=19",
62
62
  "react-dom": ">=19",
63
- "@haklex/rich-editor-ui": "0.26.1",
64
- "@haklex/rich-editor": "0.26.1",
65
- "@haklex/rich-style-token": "0.26.1"
63
+ "@haklex/rich-editor": "0.26.3",
64
+ "@haklex/rich-editor-ui": "0.26.3",
65
+ "@haklex/rich-style-token": "0.26.3"
66
66
  },
67
67
  "publishConfig": {
68
68
  "access": "public"