@haklex/rich-ext-gallery 0.4.0 → 0.5.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,406 @@
1
+ import { C as galleryImageDragHandle, D as galleryImageThumb, E as galleryImageList, O as galleryImageThumbPlaceholder, S as galleryImageDeleteBtn, T as galleryImageInput, _ as galleryFooterInfo, a as galleryDialogEmpty, b as galleryImageActions, c as galleryDialogPopup, d as galleryEditLabel, f as galleryEditOverlay, g as galleryFooterBtnSave, h as galleryFooterBtnCancel, i as galleryDialogBody, l as galleryDialogTitle, m as galleryFooterActions, o as galleryDialogFooter, p as galleryEmptyState, r as galleryAddBtn, s as galleryDialogHeader, t as GalleryRenderer, u as galleryEditContainer, v as galleryHeaderActions, w as galleryImageFields, x as galleryImageCard, y as galleryHeaderClose } from "./GalleryRenderer-CyyWUTT0.js";
2
+ import { i as _defineProperty, r as GalleryNode } from "./GalleryNode-CKcqJG5T.js";
3
+ import { $getNodeByKey, $insertNodes } from "lexical";
4
+ import { GripVertical, ImageIcon, Images, Pencil, Plus, Trash2, X } from "lucide-react";
5
+ import { createElement, useCallback, useRef, useState } from "react";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ import { DndContext, DragOverlay, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
8
+ import { SortableContext, arrayMove, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
9
+ import { CSS } from "@dnd-kit/utilities";
10
+ import { useColorScheme } from "@haklex/rich-editor";
11
+ import { SegmentedControl, presentDialog } from "@haklex/rich-editor-ui";
12
+ import { usePortalTheme, vars } from "@haklex/rich-style-token";
13
+ import { createPortal } from "react-dom";
14
+ //#region src/GalleryEditNode.ts
15
+ var GalleryEditNode = class GalleryEditNode extends GalleryNode {
16
+ static clone(node) {
17
+ return new GalleryEditNode({
18
+ images: node.__images.map((img) => ({ ...img })),
19
+ layout: node.__layout
20
+ }, node.__key);
21
+ }
22
+ constructor(payload, key) {
23
+ super(payload, key);
24
+ }
25
+ static importJSON(serializedNode) {
26
+ return new GalleryEditNode({
27
+ images: serializedNode.images,
28
+ layout: serializedNode.layout
29
+ });
30
+ }
31
+ decorate(editor, _config) {
32
+ const nodeKey = this.__key;
33
+ return createElement(GalleryRenderer, {
34
+ images: this.__images,
35
+ layout: this.__layout,
36
+ onImagesChange: (images) => {
37
+ editor.update(() => {
38
+ const node = $getNodeByKey(nodeKey);
39
+ if (node) node.setImages(images);
40
+ });
41
+ },
42
+ onLayoutChange: (layout) => {
43
+ editor.update(() => {
44
+ const node = $getNodeByKey(nodeKey);
45
+ if (node) node.setLayout(layout);
46
+ });
47
+ }
48
+ });
49
+ }
50
+ };
51
+ _defineProperty(GalleryEditNode, "commandItems", [{
52
+ title: "Gallery",
53
+ icon: createElement(Images, { size: 20 }),
54
+ description: "Image gallery grid",
55
+ keywords: [
56
+ "gallery",
57
+ "images",
58
+ "grid"
59
+ ],
60
+ section: "MEDIA",
61
+ placement: ["slash", "toolbar"],
62
+ group: "insert",
63
+ onSelect: (editor) => {
64
+ editor.update(() => {
65
+ $insertNodes([$createGalleryEditNode({ images: [] })]);
66
+ });
67
+ }
68
+ }]);
69
+ function $createGalleryEditNode(payload) {
70
+ return new GalleryEditNode(payload);
71
+ }
72
+ function $isGalleryEditNode(node) {
73
+ return node instanceof GalleryEditNode;
74
+ }
75
+ //#endregion
76
+ //#region src/GalleryEditRenderer.tsx
77
+ var layoutItems = [
78
+ {
79
+ value: "grid",
80
+ label: "Grid"
81
+ },
82
+ {
83
+ value: "masonry",
84
+ label: "Masonry"
85
+ },
86
+ {
87
+ value: "carousel",
88
+ label: "Carousel"
89
+ }
90
+ ];
91
+ var nextId = 0;
92
+ function genId() {
93
+ return `img-${++nextId}`;
94
+ }
95
+ var SortableImageCard = ({ id, image, index, isLast, lastInputRef, onUpdate, onRemove }) => {
96
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
97
+ return /* @__PURE__ */ jsxs("div", {
98
+ className: galleryImageCard,
99
+ ref: setNodeRef,
100
+ style: {
101
+ transform: CSS.Transform.toString(transform),
102
+ transition: isDragging ? void 0 : transition,
103
+ opacity: isDragging ? .4 : 1
104
+ },
105
+ children: [
106
+ /* @__PURE__ */ jsx("div", {
107
+ className: galleryImageDragHandle,
108
+ ...attributes,
109
+ ...listeners,
110
+ children: /* @__PURE__ */ jsx(GripVertical, { size: 14 })
111
+ }),
112
+ /* @__PURE__ */ jsx("div", {
113
+ className: galleryImageThumb,
114
+ children: image.src ? /* @__PURE__ */ jsx("img", {
115
+ alt: image.alt || "",
116
+ src: image.src
117
+ }) : /* @__PURE__ */ jsx("span", {
118
+ className: galleryImageThumbPlaceholder,
119
+ children: /* @__PURE__ */ jsx(ImageIcon, { size: 20 })
120
+ })
121
+ }),
122
+ /* @__PURE__ */ jsxs("div", {
123
+ className: galleryImageFields,
124
+ children: [/* @__PURE__ */ jsx("input", {
125
+ className: galleryImageInput,
126
+ placeholder: "Image URL",
127
+ ref: isLast ? lastInputRef : void 0,
128
+ type: "text",
129
+ value: image.src,
130
+ onChange: (e) => onUpdate(index, "src", e.target.value)
131
+ }), /* @__PURE__ */ jsx("input", {
132
+ className: galleryImageInput,
133
+ placeholder: "Alt text (optional)",
134
+ type: "text",
135
+ value: image.alt || "",
136
+ onChange: (e) => onUpdate(index, "alt", e.target.value)
137
+ })]
138
+ }),
139
+ /* @__PURE__ */ jsx("div", {
140
+ className: galleryImageActions,
141
+ children: /* @__PURE__ */ jsx("button", {
142
+ className: galleryImageDeleteBtn,
143
+ title: "Remove",
144
+ type: "button",
145
+ onClick: () => onRemove(index),
146
+ children: /* @__PURE__ */ jsx(Trash2, { size: 14 })
147
+ })
148
+ })
149
+ ]
150
+ });
151
+ };
152
+ var DragOverlayCard = ({ image }) => /* @__PURE__ */ jsxs("div", {
153
+ className: galleryImageCard,
154
+ style: { opacity: .9 },
155
+ children: [
156
+ /* @__PURE__ */ jsx("div", {
157
+ className: galleryImageDragHandle,
158
+ children: /* @__PURE__ */ jsx(GripVertical, { size: 14 })
159
+ }),
160
+ /* @__PURE__ */ jsx("div", {
161
+ className: galleryImageThumb,
162
+ children: image.src ? /* @__PURE__ */ jsx("img", {
163
+ alt: image.alt || "",
164
+ src: image.src
165
+ }) : /* @__PURE__ */ jsx("span", {
166
+ className: galleryImageThumbPlaceholder,
167
+ children: /* @__PURE__ */ jsx(ImageIcon, { size: 20 })
168
+ })
169
+ }),
170
+ /* @__PURE__ */ jsx("div", {
171
+ className: galleryImageFields,
172
+ children: /* @__PURE__ */ jsx("span", {
173
+ style: {
174
+ fontSize: vars.typography.fontSizeSm,
175
+ overflow: "hidden",
176
+ textOverflow: "ellipsis",
177
+ whiteSpace: "nowrap"
178
+ },
179
+ children: image.src || "No URL"
180
+ })
181
+ })
182
+ ]
183
+ });
184
+ var GalleryEditorDialogContent = ({ dismiss, initialImages, initialLayout, onImagesChange, onLayoutChange }) => {
185
+ const { className: portalClassName } = usePortalTheme();
186
+ const [entries, setEntries] = useState(() => initialImages.map((img) => ({
187
+ id: genId(),
188
+ image: { ...img }
189
+ })));
190
+ const [layout, setLayout] = useState(initialLayout);
191
+ const newInputRef = useRef(null);
192
+ const [dragActiveId, setDragActiveId] = useState(null);
193
+ const itemIds = entries.map((e) => e.id);
194
+ const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }));
195
+ const handleAddImage = useCallback(() => {
196
+ setEntries((prev) => [...prev, {
197
+ id: genId(),
198
+ image: {
199
+ src: "",
200
+ alt: ""
201
+ }
202
+ }]);
203
+ requestAnimationFrame(() => {
204
+ newInputRef.current?.focus();
205
+ });
206
+ }, []);
207
+ const handleRemoveImage = useCallback((index) => {
208
+ setEntries((prev) => prev.filter((_, i) => i !== index));
209
+ }, []);
210
+ const handleUpdateImage = useCallback((index, field, value) => {
211
+ setEntries((prev) => prev.map((entry, i) => i === index ? {
212
+ ...entry,
213
+ image: {
214
+ ...entry.image,
215
+ [field]: value
216
+ }
217
+ } : entry));
218
+ }, []);
219
+ const handleDragEnd = useCallback((event) => {
220
+ const { active, over } = event;
221
+ setDragActiveId(null);
222
+ if (!over || active.id === over.id) return;
223
+ setEntries((prev) => {
224
+ const oldIndex = prev.findIndex((e) => e.id === String(active.id));
225
+ const newIndex = prev.findIndex((e) => e.id === String(over.id));
226
+ if (oldIndex === -1 || newIndex === -1) return prev;
227
+ return arrayMove(prev, oldIndex, newIndex);
228
+ });
229
+ }, []);
230
+ const handleSave = useCallback(() => {
231
+ onImagesChange(entries.map((e) => e.image).filter((img) => img.src.trim()));
232
+ onLayoutChange(layout);
233
+ dismiss();
234
+ }, [
235
+ entries,
236
+ layout,
237
+ onImagesChange,
238
+ onLayoutChange,
239
+ dismiss
240
+ ]);
241
+ const dragActiveEntry = dragActiveId ? entries.find((e) => e.id === dragActiveId) : null;
242
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
243
+ /* @__PURE__ */ jsxs("div", {
244
+ className: galleryDialogHeader,
245
+ children: [
246
+ /* @__PURE__ */ jsxs("div", {
247
+ className: galleryDialogTitle,
248
+ children: [/* @__PURE__ */ jsx(Images, { size: 18 }), /* @__PURE__ */ jsx("span", { children: "Gallery Editor" })]
249
+ }),
250
+ /* @__PURE__ */ jsx("div", {
251
+ className: galleryHeaderActions,
252
+ children: /* @__PURE__ */ jsx(SegmentedControl, {
253
+ items: layoutItems,
254
+ value: layout,
255
+ onChange: setLayout
256
+ })
257
+ }),
258
+ /* @__PURE__ */ jsx("button", {
259
+ className: galleryHeaderClose,
260
+ type: "button",
261
+ onClick: dismiss,
262
+ children: /* @__PURE__ */ jsx(X, { size: 18 })
263
+ })
264
+ ]
265
+ }),
266
+ /* @__PURE__ */ jsx("div", {
267
+ className: galleryDialogBody,
268
+ children: entries.length === 0 ? /* @__PURE__ */ jsxs("div", {
269
+ className: galleryDialogEmpty,
270
+ children: [
271
+ /* @__PURE__ */ jsx(ImageIcon, { size: 32 }),
272
+ /* @__PURE__ */ jsx("span", { children: "Add your first image" }),
273
+ /* @__PURE__ */ jsxs("button", {
274
+ className: galleryAddBtn,
275
+ style: { maxWidth: 200 },
276
+ type: "button",
277
+ onClick: handleAddImage,
278
+ children: [/* @__PURE__ */ jsx(Plus, { size: 14 }), "Add Image"]
279
+ })
280
+ ]
281
+ }) : /* @__PURE__ */ jsxs("div", {
282
+ className: galleryImageList,
283
+ children: [/* @__PURE__ */ jsxs(DndContext, {
284
+ collisionDetection: closestCenter,
285
+ sensors,
286
+ onDragCancel: () => setDragActiveId(null),
287
+ onDragEnd: handleDragEnd,
288
+ onDragStart: (event) => {
289
+ setDragActiveId(String(event.active.id));
290
+ },
291
+ children: [/* @__PURE__ */ jsx(SortableContext, {
292
+ items: itemIds,
293
+ strategy: verticalListSortingStrategy,
294
+ children: entries.map((entry, index) => /* @__PURE__ */ jsx(SortableImageCard, {
295
+ id: entry.id,
296
+ image: entry.image,
297
+ index,
298
+ isLast: index === entries.length - 1,
299
+ lastInputRef: newInputRef,
300
+ onRemove: handleRemoveImage,
301
+ onUpdate: handleUpdateImage
302
+ }, entry.id))
303
+ }), typeof document !== "undefined" ? createPortal(/* @__PURE__ */ jsx("div", {
304
+ className: portalClassName,
305
+ children: /* @__PURE__ */ jsx(DragOverlay, {
306
+ dropAnimation: null,
307
+ children: dragActiveEntry ? /* @__PURE__ */ jsx(DragOverlayCard, { image: dragActiveEntry.image }) : null
308
+ })
309
+ }), document.body) : null]
310
+ }), /* @__PURE__ */ jsxs("button", {
311
+ className: galleryAddBtn,
312
+ type: "button",
313
+ onClick: handleAddImage,
314
+ children: [/* @__PURE__ */ jsx(Plus, { size: 14 }), "Add Image"]
315
+ })]
316
+ })
317
+ }),
318
+ /* @__PURE__ */ jsxs("div", {
319
+ className: galleryDialogFooter,
320
+ children: [/* @__PURE__ */ jsx("span", {
321
+ className: galleryFooterInfo,
322
+ children: `${entries.filter((e) => e.image.src.trim()).length} ${entries.filter((e) => e.image.src.trim()).length !== 1 ? "images" : "image"}`
323
+ }), /* @__PURE__ */ jsxs("div", {
324
+ className: galleryFooterActions,
325
+ children: [/* @__PURE__ */ jsx("button", {
326
+ className: galleryFooterBtnCancel,
327
+ type: "button",
328
+ onClick: dismiss,
329
+ children: "Cancel"
330
+ }), /* @__PURE__ */ jsx("button", {
331
+ className: galleryFooterBtnSave,
332
+ type: "button",
333
+ onClick: handleSave,
334
+ children: "Save"
335
+ })]
336
+ })]
337
+ })
338
+ ] });
339
+ };
340
+ var GalleryEditRenderer = ({ images, layout, onImagesChange, onLayoutChange }) => {
341
+ const { className: portalClassName } = usePortalTheme();
342
+ const theme = useColorScheme();
343
+ const handleOpenEditor = useCallback(() => {
344
+ if (!onImagesChange || !onLayoutChange) return;
345
+ presentDialog({
346
+ content: ({ dismiss }) => /* @__PURE__ */ jsx(GalleryEditorDialogContent, {
347
+ dismiss,
348
+ initialImages: images,
349
+ initialLayout: layout,
350
+ onImagesChange,
351
+ onLayoutChange
352
+ }),
353
+ className: galleryDialogPopup,
354
+ portalClassName,
355
+ theme,
356
+ showCloseButton: false,
357
+ clickOutsideToDismiss: false
358
+ });
359
+ }, [
360
+ images,
361
+ layout,
362
+ onImagesChange,
363
+ onLayoutChange,
364
+ portalClassName,
365
+ theme
366
+ ]);
367
+ if (!onImagesChange) return /* @__PURE__ */ jsx(GalleryRenderer, {
368
+ images,
369
+ layout
370
+ });
371
+ if (images.length === 0) return /* @__PURE__ */ jsxs("div", {
372
+ className: galleryEditContainer,
373
+ children: [/* @__PURE__ */ jsxs("div", {
374
+ className: galleryEmptyState,
375
+ children: [/* @__PURE__ */ jsx(ImageIcon, { size: 32 }), /* @__PURE__ */ jsx("span", { children: "Empty gallery" })]
376
+ }), /* @__PURE__ */ jsx("button", {
377
+ className: galleryEditOverlay,
378
+ type: "button",
379
+ onClick: handleOpenEditor,
380
+ children: /* @__PURE__ */ jsxs("span", {
381
+ className: galleryEditLabel,
382
+ children: [/* @__PURE__ */ jsx(Pencil, { size: 16 }), "Edit Gallery"]
383
+ })
384
+ })]
385
+ });
386
+ return /* @__PURE__ */ jsxs("div", {
387
+ className: galleryEditContainer,
388
+ children: [/* @__PURE__ */ jsx(GalleryRenderer, {
389
+ images,
390
+ layout
391
+ }), /* @__PURE__ */ jsx("button", {
392
+ className: galleryEditOverlay,
393
+ type: "button",
394
+ onClick: handleOpenEditor,
395
+ children: /* @__PURE__ */ jsxs("span", {
396
+ className: galleryEditLabel,
397
+ children: [/* @__PURE__ */ jsx(Pencil, { size: 16 }), "Edit Gallery"]
398
+ })
399
+ })]
400
+ });
401
+ };
402
+ //#endregion
403
+ //#region src/edit.ts
404
+ var galleryEditNodes = [GalleryEditNode];
405
+ //#endregion
406
+ export { GalleryEditNode as a, $isGalleryEditNode as i, GalleryEditRenderer as n, $createGalleryEditNode as r, galleryEditNodes as t };
package/dist/edit.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { Klass, LexicalNode } from 'lexical';
2
+ export { $createGalleryEditNode, $isGalleryEditNode, GalleryEditNode, } from './GalleryEditNode';
3
+ export { GalleryEditRenderer } from './GalleryEditRenderer';
4
+ export declare const galleryEditNodes: Array<Klass<LexicalNode>>;
5
+ //# sourceMappingURL=edit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../src/edit.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAA;AAErB,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAIjD,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,eAAe,GAChB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAE3D,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAqB,CAAA"}
package/dist/edit.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { a as GalleryEditNode, i as $isGalleryEditNode, n as GalleryEditRenderer, r as $createGalleryEditNode, t as galleryEditNodes } from "./edit-BQnC2MpC.js";
2
+ export { $createGalleryEditNode, $isGalleryEditNode, GalleryEditNode, GalleryEditRenderer, galleryEditNodes };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,4 @@
1
- import { Klass, LexicalNode } from 'lexical';
2
- export { $createGalleryEditNode, $isGalleryEditNode, GalleryEditNode, } from './GalleryEditNode';
3
- export { GalleryEditRenderer } from './GalleryEditRenderer';
4
- export type { GalleryNodePayload, SerializedGalleryNode } from './GalleryNode';
5
- export { $createGalleryNode, $isGalleryNode, GalleryNode } from './GalleryNode';
6
- export { default, GalleryRenderer } from './GalleryRenderer';
7
- export declare const galleryNodes: Array<Klass<LexicalNode>>;
8
- export declare const galleryEditNodes: Array<Klass<LexicalNode>>;
1
+ export * from './edit';
2
+ export * from './node';
3
+ export * from './renderer';
9
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAA;AAErB,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAKjD,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,eAAe,GAChB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC3D,YAAY,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC9E,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC/E,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAE5D,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAiB,CAAA;AACpE,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAqB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAA;AACtB,cAAc,QAAQ,CAAA;AACtB,cAAc,YAAY,CAAA"}