@drawnagency/primitives 0.1.61 → 0.1.63

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.
Files changed (78) hide show
  1. package/dist/{chunk-UQ4OYRW7.js → chunk-2BHPWO7M.js} +54 -166
  2. package/dist/chunk-4E66N6UV.js +10 -0
  3. package/dist/chunk-5THS4K5D.js +123 -0
  4. package/dist/{chunk-TG43X7JO.js → chunk-75P6HWPN.js} +29 -5
  5. package/dist/{chunk-ODCJQOVO.js → chunk-7IMQVDDJ.js} +1 -1
  6. package/dist/{chunk-4OX3Z7SB.js → chunk-FECCYOPJ.js} +32 -671
  7. package/dist/{chunk-WLMJCNU7.js → chunk-I5737FJY.js} +0 -8
  8. package/dist/chunk-QYQ3DQHI.js +1201 -0
  9. package/dist/{chunk-BT7STGDW.js → chunk-TEYTLQVH.js} +0 -15
  10. package/dist/chunk-VMDKWUMT.js +652 -0
  11. package/dist/{chunk-AM5LZD6Q.js → chunk-ZYXLNJ5M.js} +6 -5
  12. package/dist/components/brandguide/ColorSwatchSettings.d.ts +1 -1
  13. package/dist/components/brandguide/ColorSwatchSettings.d.ts.map +1 -1
  14. package/dist/components/brandguide/Colors.d.ts +1 -1
  15. package/dist/components/brandguide/Colors.d.ts.map +1 -1
  16. package/dist/components/editor/index.js +4 -2
  17. package/dist/components/primitives/IconPicker.d.ts +1 -7
  18. package/dist/components/primitives/IconPicker.d.ts.map +1 -1
  19. package/dist/components/primitives/index.js +10 -7
  20. package/dist/components/sections/Colors/index.d.ts.map +1 -1
  21. package/dist/components/sections/Colors/schema.d.ts +19 -0
  22. package/dist/components/sections/Colors/schema.d.ts.map +1 -0
  23. package/dist/components/sections/DoDontMedia/DoDontMediaBlock.d.ts +20 -0
  24. package/dist/components/sections/DoDontMedia/DoDontMediaBlock.d.ts.map +1 -0
  25. package/dist/components/sections/DoDontMedia/index.d.ts +45 -0
  26. package/dist/components/sections/DoDontMedia/index.d.ts.map +1 -0
  27. package/dist/components/sections/IconList/DoDontToggle.d.ts +14 -0
  28. package/dist/components/sections/IconList/DoDontToggle.d.ts.map +1 -0
  29. package/dist/components/sections/IconList/IconList.d.ts.map +1 -1
  30. package/dist/components/sections/IconList/index.d.ts.map +1 -1
  31. package/dist/components/sections/Media/MediaBlock.d.ts +1 -3
  32. package/dist/components/sections/Media/MediaBlock.d.ts.map +1 -1
  33. package/dist/components/sections/Media/index.d.ts +0 -1
  34. package/dist/components/sections/Media/index.d.ts.map +1 -1
  35. package/dist/components/sections/all-sections.d.ts +54 -18
  36. package/dist/components/sections/all-sections.d.ts.map +1 -1
  37. package/dist/components/sections/brand-guide.d.ts +84 -0
  38. package/dist/components/sections/brand-guide.d.ts.map +1 -0
  39. package/dist/components/sections/brand-guide.js +21 -0
  40. package/dist/components/sections/core-sections.d.ts +93 -0
  41. package/dist/components/sections/core-sections.d.ts.map +1 -0
  42. package/dist/components/sections/register-schemas.js +108 -1155
  43. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  44. package/dist/components/shell/view-branch.d.ts +12 -0
  45. package/dist/components/shell/view-branch.d.ts.map +1 -0
  46. package/dist/index.js +29 -33
  47. package/dist/lib/index.js +4 -4
  48. package/dist/lib/loader.d.ts.map +1 -1
  49. package/dist/lib/migrate-sections-transform.d.ts.map +1 -1
  50. package/dist/lib/migrate-sections-transform.js +1 -1
  51. package/dist/lib/registry.d.ts +6 -1
  52. package/dist/lib/registry.d.ts.map +1 -1
  53. package/dist/media/index.js +1 -1
  54. package/dist/schemas/index.js +3 -7
  55. package/dist/schemas/shared.d.ts +0 -17
  56. package/dist/schemas/shared.d.ts.map +1 -1
  57. package/package.json +5 -1
  58. package/src/components/brandguide/ColorSwatchSettings.tsx +1 -1
  59. package/src/components/brandguide/Colors.tsx +1 -1
  60. package/src/components/primitives/IconPicker.tsx +27 -119
  61. package/src/components/sections/Colors/index.tsx +2 -1
  62. package/src/components/sections/Colors/schema.ts +24 -0
  63. package/src/components/sections/DoDontMedia/DoDontMediaBlock.tsx +35 -0
  64. package/src/components/sections/DoDontMedia/index.tsx +84 -0
  65. package/src/components/sections/IconList/DoDontToggle.tsx +76 -0
  66. package/src/components/sections/IconList/IconList.tsx +24 -14
  67. package/src/components/sections/IconList/index.tsx +1 -0
  68. package/src/components/sections/Media/MediaBlock.tsx +10 -11
  69. package/src/components/sections/Media/index.tsx +0 -15
  70. package/src/components/sections/all-sections.ts +7 -35
  71. package/src/components/sections/brand-guide.ts +13 -0
  72. package/src/components/sections/core-sections.ts +26 -0
  73. package/src/components/shell/EditorShell.tsx +7 -5
  74. package/src/components/shell/view-branch.ts +14 -0
  75. package/src/lib/loader.ts +10 -4
  76. package/src/lib/migrate-sections-transform.ts +33 -5
  77. package/src/lib/registry.ts +6 -1
  78. package/src/schemas/shared.ts +0 -18
@@ -60,19 +60,6 @@ var MediaReferenceSchema = z.discriminatedUnion("type", [
60
60
  var SingleMediaReferenceSchema = z.discriminatedUnion("type", [ImageRef, VideoRef]);
61
61
  var HexColorSchema = z.string().regex(/^#[0-9a-fA-F]{6}$/, "must be a 6-digit hex color");
62
62
  var FontNameSchema = z.string().max(120).regex(/^[a-zA-Z0-9 ,'"-]+$/, "contains invalid font-name characters");
63
- var ColorSpaceSchema = z.object({
64
- hex: HexColorSchema.optional(),
65
- rgb: z.string().optional(),
66
- cmyk: z.string().optional(),
67
- pantone: z.string().optional()
68
- }).refine(
69
- (data) => data.hex !== void 0 || data.rgb !== void 0 || data.cmyk !== void 0 || data.pantone !== void 0,
70
- { message: "At least one color space must be defined" }
71
- );
72
- var ColorItemSchema = z.object({
73
- name: z.string().optional(),
74
- spaces: z.array(ColorSpaceSchema).min(1)
75
- });
76
63
 
77
64
  // src/schemas/media.ts
78
65
  import { z as z2 } from "zod";
@@ -230,8 +217,6 @@ export {
230
217
  SingleMediaReferenceSchema,
231
218
  HexColorSchema,
232
219
  FontNameSchema,
233
- ColorSpaceSchema,
234
- ColorItemSchema,
235
220
  VariantSchema,
236
221
  MediaItemSchema,
237
222
  ImageManifestSchema,
@@ -0,0 +1,652 @@
1
+ import {
2
+ AddIcon,
3
+ DeleteIcon,
4
+ DragHandle,
5
+ IconButton,
6
+ SettingsIcon
7
+ } from "./chunk-I5737FJY.js";
8
+ import {
9
+ getMediaProvider
10
+ } from "./chunk-CBWK3KPV.js";
11
+ import {
12
+ cn
13
+ } from "./chunk-7EOSDFS3.js";
14
+
15
+ // src/components/primitives/EditablePlainText.tsx
16
+ import {
17
+ useRef as useRef2,
18
+ useEffect as useEffect2,
19
+ useCallback as useCallback2,
20
+ createElement
21
+ } from "react";
22
+
23
+ // src/components/primitives/useEditablePlainText.ts
24
+ import { useState, useEffect, useRef, useCallback } from "react";
25
+ function useEditablePlainText({
26
+ value: propValue,
27
+ onChange,
28
+ multiline = false
29
+ }) {
30
+ const [value, setValue] = useState(propValue);
31
+ const lastPropValue = useRef(propValue);
32
+ useEffect(() => {
33
+ if (propValue !== lastPropValue.current) {
34
+ setValue(propValue);
35
+ lastPropValue.current = propValue;
36
+ }
37
+ }, [propValue]);
38
+ const handleInput = useCallback((text) => {
39
+ setValue(text);
40
+ }, []);
41
+ const handleBlur = useCallback(() => {
42
+ if (value !== propValue) {
43
+ onChange(value);
44
+ lastPropValue.current = value;
45
+ }
46
+ }, [value, propValue, onChange]);
47
+ return {
48
+ value,
49
+ handleInput,
50
+ handleBlur,
51
+ shouldPreventEnter: !multiline
52
+ };
53
+ }
54
+
55
+ // src/components/primitives/EditablePlainText.tsx
56
+ function EditablePlainText({
57
+ tag,
58
+ value,
59
+ onChange,
60
+ isEditMode,
61
+ multiline = false,
62
+ placeholder,
63
+ className,
64
+ ...htmlAttrs
65
+ }) {
66
+ const ref = useRef2(null);
67
+ const {
68
+ value: localValue,
69
+ handleInput,
70
+ handleBlur,
71
+ shouldPreventEnter
72
+ } = useEditablePlainText({ value, onChange, multiline });
73
+ useEffect2(() => {
74
+ if (ref.current && ref.current.textContent !== localValue) {
75
+ ref.current.textContent = localValue;
76
+ }
77
+ }, [localValue]);
78
+ const onInput = useCallback2(() => {
79
+ if (ref.current) {
80
+ handleInput(ref.current.textContent || "");
81
+ }
82
+ }, [handleInput]);
83
+ const onKeyDown = useCallback2(
84
+ (e) => {
85
+ if (e.key === "Enter" && shouldPreventEnter) {
86
+ e.preventDefault();
87
+ }
88
+ },
89
+ [shouldPreventEnter]
90
+ );
91
+ const onPaste = useCallback2(
92
+ (e) => {
93
+ e.preventDefault();
94
+ const text = e.clipboardData.getData("text/plain");
95
+ const selection = window.getSelection();
96
+ if (selection && selection.rangeCount > 0) {
97
+ const range = selection.getRangeAt(0);
98
+ range.deleteContents();
99
+ range.insertNode(document.createTextNode(text));
100
+ range.collapse(false);
101
+ selection.removeAllRanges();
102
+ selection.addRange(range);
103
+ }
104
+ if (ref.current) {
105
+ handleInput(ref.current.textContent || "");
106
+ }
107
+ },
108
+ [handleInput]
109
+ );
110
+ return createElement(
111
+ tag,
112
+ {
113
+ ref,
114
+ className: isEditMode && placeholder ? `editable-placeholder ${className ?? ""}` : className,
115
+ contentEditable: isEditMode ? "true" : void 0,
116
+ suppressContentEditableWarning: isEditMode || void 0,
117
+ onInput: isEditMode ? onInput : void 0,
118
+ onBlur: isEditMode ? handleBlur : void 0,
119
+ onKeyDown: isEditMode ? onKeyDown : void 0,
120
+ onPaste: isEditMode ? onPaste : void 0,
121
+ "data-placeholder": isEditMode && placeholder ? placeholder : void 0,
122
+ ...htmlAttrs
123
+ },
124
+ value
125
+ );
126
+ }
127
+
128
+ // src/components/primitives/ResolvedMedia.tsx
129
+ import { ImageIcon } from "lucide-react";
130
+
131
+ // src/hooks/useResolvedMedia.ts
132
+ import { useMemo } from "react";
133
+
134
+ // src/components/shell/MediaLibraryContext.tsx
135
+ import { createContext, useContext } from "react";
136
+ var MediaLibraryContext = createContext(null);
137
+ function useMediaLibrary() {
138
+ return useContext(MediaLibraryContext);
139
+ }
140
+
141
+ // src/hooks/useResolvedMedia.ts
142
+ function useResolvedMedia(imageId) {
143
+ const ctx = useMediaLibrary();
144
+ return useMemo(() => {
145
+ if (!imageId || !ctx) {
146
+ return { src: void 0, srcset: void 0, poster: void 0, alt: "", kind: "image" };
147
+ }
148
+ const { manifest, pendingItems, pendingLocalUrls, siteConfig } = ctx;
149
+ const sizes = siteConfig?.media?.sizes ?? [640, 1080, 1920];
150
+ const manifestItem = manifest.images[imageId];
151
+ const pendingItem = pendingItems.find((i) => i.id === imageId);
152
+ const item = manifestItem ?? pendingItem;
153
+ const alt = item?.alt ?? "";
154
+ const kind = item?.kind ?? "image";
155
+ const localUrl = pendingLocalUrls[imageId];
156
+ if (localUrl) {
157
+ return { src: localUrl, srcset: void 0, poster: void 0, alt, kind };
158
+ }
159
+ if (manifestItem) {
160
+ const provider = getMediaProvider();
161
+ const resolved = provider.resolve(manifestItem, sizes);
162
+ if (resolved && resolved.tag === "img") {
163
+ return {
164
+ src: resolved.src,
165
+ srcset: "srcSet" in resolved ? resolved.srcSet : void 0,
166
+ poster: void 0,
167
+ alt,
168
+ kind
169
+ };
170
+ }
171
+ if (resolved && resolved.tag === "video") {
172
+ return { src: resolved.src, srcset: void 0, poster: resolved.poster, alt, kind };
173
+ }
174
+ }
175
+ return { src: void 0, srcset: void 0, poster: void 0, alt, kind };
176
+ }, [imageId, ctx]);
177
+ }
178
+
179
+ // src/components/primitives/ResolvedMedia.tsx
180
+ import { jsx, jsxs } from "react/jsx-runtime";
181
+ function ResolvedMedia({
182
+ imageId,
183
+ src: propSrc,
184
+ srcset: propSrcset,
185
+ alt: propAlt,
186
+ className,
187
+ imgClassName,
188
+ invertFrom
189
+ }) {
190
+ const resolved = useResolvedMedia(imageId);
191
+ const src = propSrc || resolved.src;
192
+ const srcset = propSrcset || resolved.srcset;
193
+ const poster = resolved.poster;
194
+ const alt = propAlt ?? resolved.alt;
195
+ const kind = resolved.kind;
196
+ const invertClass = invertFrom === "light" ? "invert dark:invert-0" : invertFrom === "dark" ? "dark:invert" : void 0;
197
+ const mediaClass = cn("h-full w-full", imgClassName, invertClass);
198
+ return /* @__PURE__ */ jsx("div", { className, children: src ? kind === "video" ? /* @__PURE__ */ jsx("video", { src, poster, muted: true, playsInline: true, loop: true, autoPlay: true, className: mediaClass }) : /* @__PURE__ */ jsx("img", { src, srcSet: srcset, alt, className: mediaClass }) : /* @__PURE__ */ jsxs("div", { className: "flex aspect-video h-full w-full flex-col items-center justify-center gap-1 rounded bg-base-accent text-base-contrast-light/50", children: [
199
+ /* @__PURE__ */ jsx(ImageIcon, { size: 24 }),
200
+ /* @__PURE__ */ jsx("span", { className: "text-sm", children: "No Image" })
201
+ ] }) });
202
+ }
203
+
204
+ // src/components/primitives/ImageDropZone.tsx
205
+ import { useState as useState2 } from "react";
206
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
207
+ var ACCEPTED = /^(image|video)\//;
208
+ function ImageDropZone({ onImageSelected, className, children }) {
209
+ const mediaLibrary = useMediaLibrary();
210
+ const [dragging, setDragging] = useState2(false);
211
+ if (!mediaLibrary) return /* @__PURE__ */ jsx2(Fragment, { children });
212
+ const maxFileSize = mediaLibrary.siteConfig?.media.maxFileSize;
213
+ const hasFiles = (e) => e.dataTransfer.types.includes("Files");
214
+ const handleDragOver = (e) => {
215
+ if (!hasFiles(e)) return;
216
+ e.preventDefault();
217
+ e.dataTransfer.dropEffect = "copy";
218
+ setDragging(true);
219
+ };
220
+ const handleDragLeave = (e) => {
221
+ if (!hasFiles(e)) return;
222
+ const related = e.relatedTarget;
223
+ if (related && e.currentTarget.contains(related)) return;
224
+ setDragging(false);
225
+ };
226
+ const handleDrop = (e) => {
227
+ if (!hasFiles(e)) return;
228
+ e.preventDefault();
229
+ e.stopPropagation();
230
+ setDragging(false);
231
+ const file = e.dataTransfer.files[0];
232
+ if (!file) return;
233
+ if (!ACCEPTED.test(file.type)) {
234
+ mediaLibrary.reportRejectedUploads([{ name: file.name, reason: "type" }]);
235
+ return;
236
+ }
237
+ if (maxFileSize && file.size > maxFileSize) {
238
+ mediaLibrary.reportRejectedUploads([{ name: file.name, reason: "size" }]);
239
+ return;
240
+ }
241
+ mediaLibrary.uploadFileWithCallback(file, onImageSelected);
242
+ };
243
+ return /* @__PURE__ */ jsxs2(
244
+ "div",
245
+ {
246
+ className: cn("relative", className),
247
+ onDragOver: handleDragOver,
248
+ onDragLeave: handleDragLeave,
249
+ onDrop: handleDrop,
250
+ children: [
251
+ children,
252
+ dragging && /* @__PURE__ */ jsx2("div", { className: "pointer-events-none absolute inset-0 z-10 rounded-md border-2 border-primary bg-primary/5" })
253
+ ]
254
+ }
255
+ );
256
+ }
257
+
258
+ // src/components/sections/Media/MediaBlock.tsx
259
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
260
+ function MediaBlock({ refValue, options, link, isEditMode, onRefChange }) {
261
+ const mediaLibrary = useMediaLibrary();
262
+ const fit = refValue.objectFit ?? options.objectFit;
263
+ const fitClass = fit === "cover" ? "object-cover" : "object-contain";
264
+ const showBorder = refValue.border ?? options.border;
265
+ const captionStr = refValue.caption ? Array.isArray(refValue.caption) ? refValue.caption.join("\n") : refValue.caption : "";
266
+ const replace = onRefChange ? (imageId) => onRefChange({ ...refValue, imageId }) : void 0;
267
+ const baked = refValue;
268
+ const resolved = /* @__PURE__ */ jsx3(
269
+ ResolvedMedia,
270
+ {
271
+ imageId: refValue.imageId || void 0,
272
+ src: baked.src,
273
+ srcset: baked.srcset,
274
+ alt: baked.alt,
275
+ className: "h-full w-full",
276
+ imgClassName: fitClass,
277
+ invertFrom: refValue.invertFrom
278
+ }
279
+ );
280
+ let media = isEditMode && replace ? /* @__PURE__ */ jsx3(ImageDropZone, { onImageSelected: replace, className: "h-full w-full", children: resolved }) : resolved;
281
+ if (!isEditMode && link && link.kind === "external" && link.href) {
282
+ media = /* @__PURE__ */ jsx3("a", { href: link.href, target: link.target, className: "group block", children: media });
283
+ }
284
+ const onImageClick = isEditMode && replace && mediaLibrary ? () => mediaLibrary.openSelectModal((imageId) => replace(imageId)) : void 0;
285
+ return /* @__PURE__ */ jsxs3("figure", { children: [
286
+ /* @__PURE__ */ jsx3(
287
+ "div",
288
+ {
289
+ "data-testid": "media-open-surface",
290
+ className: cn(
291
+ "overflow-hidden rounded-md",
292
+ showBorder && "border border-base-200",
293
+ options.square && "aspect-square",
294
+ onImageClick && "cursor-pointer"
295
+ ),
296
+ onClick: onImageClick,
297
+ onPointerDown: onImageClick ? (e) => e.stopPropagation() : void 0,
298
+ children: media
299
+ }
300
+ ),
301
+ options.showCaption && /* @__PURE__ */ jsx3("figcaption", { className: "mt-2 min-h-[1em] text-sm text-base-contrast-light", children: isEditMode && onRefChange ? /* @__PURE__ */ jsx3(
302
+ EditablePlainText,
303
+ {
304
+ tag: "span",
305
+ value: captionStr,
306
+ onChange: (caption) => onRefChange({ ...refValue, caption: caption || void 0 }),
307
+ isEditMode: true,
308
+ placeholder: "Caption",
309
+ className: "block min-h-[1em]"
310
+ }
311
+ ) : captionStr || " " })
312
+ ] });
313
+ }
314
+
315
+ // src/components/primitives/EditableGrid.tsx
316
+ import { useRef as useRef4, useEffect as useEffect4, useState as useState4 } from "react";
317
+ import { ImageIcon as ImageIcon2 } from "lucide-react";
318
+
319
+ // src/lib/container-grid.ts
320
+ var MAX_CONTAINER_COLUMNS = 6;
321
+ var containerGridClass = {
322
+ 1: "grid-cols-1",
323
+ 2: "grid-cols-1 @sm:grid-cols-2",
324
+ 3: "grid-cols-1 @sm:grid-cols-2 @lg:grid-cols-3",
325
+ 4: "grid-cols-1 @xs:grid-cols-2 @lg:grid-cols-4",
326
+ 5: "grid-cols-1 @xs:grid-cols-2 @md:grid-cols-3 @xl:grid-cols-5",
327
+ 6: "grid-cols-1 @xs:grid-cols-2 @md:grid-cols-3 @2xl:grid-cols-6"
328
+ };
329
+ var colSpanClass = {
330
+ 1: { 1: "" },
331
+ 2: { 1: "", 2: "col-span-1 @sm:col-span-2" },
332
+ 3: { 1: "", 2: "col-span-1 @sm:col-span-2", 3: "col-span-1 @sm:col-span-2 @lg:col-span-3" },
333
+ 4: { 1: "", 2: "col-span-1 @xs:col-span-2", 3: "col-span-1 @xs:col-span-2 @lg:col-span-3", 4: "col-span-1 @xs:col-span-2 @lg:col-span-4" },
334
+ 5: { 1: "", 2: "col-span-1 @xs:col-span-2", 3: "col-span-1 @xs:col-span-2 @md:col-span-3", 4: "col-span-1 @xs:col-span-2 @md:col-span-3 @xl:col-span-4", 5: "col-span-1 @xs:col-span-2 @md:col-span-3 @xl:col-span-5" },
335
+ 6: { 1: "", 2: "col-span-1 @xs:col-span-2", 3: "col-span-1 @xs:col-span-2 @md:col-span-3", 4: "col-span-1 @xs:col-span-2 @md:col-span-3 @2xl:col-span-4", 5: "col-span-1 @xs:col-span-2 @md:col-span-3 @2xl:col-span-5", 6: "col-span-1 @xs:col-span-2 @md:col-span-3 @2xl:col-span-6" }
336
+ };
337
+ var spacerHiddenClass = {
338
+ 1: "hidden",
339
+ 2: "hidden @sm:block",
340
+ 3: "hidden @sm:block",
341
+ 4: "hidden @xs:block",
342
+ 5: "hidden @xs:block",
343
+ 6: "hidden @xs:block"
344
+ };
345
+
346
+ // src/components/primitives/useEditableCollection.ts
347
+ import { useState as useState3, useCallback as useCallback3, useRef as useRef3, useEffect as useEffect3 } from "react";
348
+ var nextId = 0;
349
+ function uid() {
350
+ return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `_id_${nextId++}_${Date.now()}`;
351
+ }
352
+ function wrapItems(items) {
353
+ return items.map((data) => ({ id: uid(), data }));
354
+ }
355
+ function useEditableCollection({
356
+ items,
357
+ onChange,
358
+ createItem
359
+ }) {
360
+ const [wrappedItems, setWrappedItems] = useState3(
361
+ () => wrapItems(items)
362
+ );
363
+ const prevItemsRef = useRef3(items);
364
+ useEffect3(() => {
365
+ if (items === prevItemsRef.current) return;
366
+ prevItemsRef.current = items;
367
+ setWrappedItems((prev) => {
368
+ return items.map((data, i) => {
369
+ if (i < prev.length) {
370
+ return { id: prev[i].id, data };
371
+ }
372
+ return { id: uid(), data };
373
+ });
374
+ });
375
+ }, [items]);
376
+ const onReorder = useCallback3(
377
+ (fromIndex, toIndex) => {
378
+ setWrappedItems((prev) => {
379
+ const next = [...prev];
380
+ const [moved] = next.splice(fromIndex, 1);
381
+ next.splice(toIndex, 0, moved);
382
+ onChange(next.map((w) => w.data));
383
+ return next;
384
+ });
385
+ },
386
+ [onChange]
387
+ );
388
+ const onAdd = useCallback3(() => {
389
+ const newItem = createItem();
390
+ setWrappedItems((prev) => {
391
+ const next = [...prev, { id: uid(), data: newItem }];
392
+ onChange(next.map((w) => w.data));
393
+ return next;
394
+ });
395
+ }, [createItem, onChange]);
396
+ const onInsert = useCallback3(
397
+ (index) => {
398
+ const newItem = createItem();
399
+ setWrappedItems((prev) => {
400
+ const next = [...prev];
401
+ next.splice(index, 0, { id: uid(), data: newItem });
402
+ onChange(next.map((w) => w.data));
403
+ return next;
404
+ });
405
+ },
406
+ [createItem, onChange]
407
+ );
408
+ const onRemove = useCallback3(
409
+ (id) => {
410
+ setWrappedItems((prev) => {
411
+ const next = prev.filter((w) => w.id !== id);
412
+ onChange(next.map((w) => w.data));
413
+ return next;
414
+ });
415
+ },
416
+ [onChange]
417
+ );
418
+ return { wrappedItems, onReorder, onAdd, onInsert, onRemove };
419
+ }
420
+
421
+ // src/components/primitives/EditableGrid.tsx
422
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
423
+ function EditableGrid({
424
+ items,
425
+ columns,
426
+ onChange,
427
+ createItem,
428
+ isEditMode,
429
+ renderItem,
430
+ onItemSettings,
431
+ onItemImageClick,
432
+ chromeTopClass,
433
+ className
434
+ }) {
435
+ const { wrappedItems, onReorder, onAdd, onInsert, onRemove } = useEditableCollection({
436
+ items,
437
+ onChange,
438
+ createItem
439
+ });
440
+ const [dragState, setDragState] = useState4({ sourceId: null, closestEdge: null });
441
+ const [isDragging, setIsDragging] = useState4(false);
442
+ useEffect4(() => {
443
+ let cleanup;
444
+ let cancelled = false;
445
+ import("./adapter-HH47ZPGM.js").then(({ monitorForElements }) => {
446
+ if (cancelled) return;
447
+ cleanup = monitorForElements({
448
+ onDragStart: ({ source }) => {
449
+ if (source.data.dragType === "grid-cell") setIsDragging(true);
450
+ },
451
+ onDrop: ({ source }) => {
452
+ if (source.data.dragType === "grid-cell") setIsDragging(false);
453
+ }
454
+ });
455
+ });
456
+ return () => {
457
+ cancelled = true;
458
+ cleanup?.();
459
+ };
460
+ }, []);
461
+ if (!isEditMode) {
462
+ return /* @__PURE__ */ jsx4("div", { className: "@container", children: /* @__PURE__ */ jsx4("div", { className: cn("grid gap-4", containerGridClass[columns] || containerGridClass[1], className), children: wrappedItems.map((wrapped, index) => /* @__PURE__ */ jsx4("div", { children: renderItem(wrapped.data, { isEditMode: false, index }) }, wrapped.id)) }) });
463
+ }
464
+ return /* @__PURE__ */ jsx4("div", { className: "@container", children: /* @__PURE__ */ jsxs4("div", { className: cn("group/grid relative grid gap-4", containerGridClass[columns] || containerGridClass[1], className), children: [
465
+ wrappedItems.map((wrapped, index) => /* @__PURE__ */ jsx4(
466
+ GridCell,
467
+ {
468
+ id: wrapped.id,
469
+ index,
470
+ isLast: index === wrappedItems.length - 1,
471
+ isDragging,
472
+ onReorder,
473
+ onRemove,
474
+ onInsert,
475
+ onSettings: onItemSettings ? () => onItemSettings(index) : void 0,
476
+ onImageClick: onItemImageClick ? () => onItemImageClick(index) : void 0,
477
+ chromeTopClass,
478
+ dragState,
479
+ setDragState,
480
+ children: renderItem(wrapped.data, { isEditMode: true, index })
481
+ },
482
+ wrapped.id
483
+ )),
484
+ /* @__PURE__ */ jsx4(
485
+ IconButton,
486
+ {
487
+ icon: /* @__PURE__ */ jsx4(AddIcon, { size: 16 }),
488
+ label: "Add item",
489
+ size: "lg",
490
+ intent: "primary",
491
+ onClick: onAdd,
492
+ className: "absolute -bottom-6 left-1/2 z-20 -translate-x-1/2 rounded-full border border-base-200 bg-base opacity-0 transition-opacity group-hover/grid:opacity-100"
493
+ }
494
+ )
495
+ ] }) });
496
+ }
497
+ function GridCell({
498
+ id,
499
+ index,
500
+ isLast,
501
+ isDragging,
502
+ onReorder,
503
+ onRemove,
504
+ onInsert,
505
+ onSettings,
506
+ onImageClick,
507
+ chromeTopClass,
508
+ dragState,
509
+ setDragState,
510
+ children
511
+ }) {
512
+ const cellRef = useRef4(null);
513
+ const handleRef = useRef4(null);
514
+ useEffect4(() => {
515
+ const cell = cellRef.current;
516
+ const handle = handleRef.current;
517
+ if (!cell || !handle) return;
518
+ let cleanup;
519
+ let cancelled = false;
520
+ Promise.all([
521
+ import("./adapter-HH47ZPGM.js"),
522
+ import("./closest-edge-EBOXL3YW.js")
523
+ ]).then(([{ draggable, dropTargetForElements }, { attachClosestEdge, extractClosestEdge }]) => {
524
+ if (cancelled) return;
525
+ const cleanupDraggable = draggable({
526
+ element: handle,
527
+ getInitialData: () => ({ dragType: "grid-cell", id, index }),
528
+ onDragStart: () => setDragState({ sourceId: id, closestEdge: null }),
529
+ onDrop: () => setDragState({ sourceId: null, closestEdge: null })
530
+ });
531
+ const cleanupDropTarget = dropTargetForElements({
532
+ element: cell,
533
+ canDrop: ({ source }) => source.data.dragType === "grid-cell",
534
+ getData: ({ input, element }) => attachClosestEdge(
535
+ { id, index },
536
+ { input, element, allowedEdges: ["left", "right"] }
537
+ ),
538
+ onDragEnter: ({ self }) => {
539
+ const edge = extractClosestEdge(self.data);
540
+ if (edge) setDragState((prev) => ({ ...prev, closestEdge: { id, edge } }));
541
+ },
542
+ onDrag: ({ self }) => {
543
+ const edge = extractClosestEdge(self.data);
544
+ if (edge) setDragState((prev) => ({ ...prev, closestEdge: { id, edge } }));
545
+ },
546
+ onDragLeave: () => {
547
+ setDragState((prev) => prev.closestEdge?.id === id ? { ...prev, closestEdge: null } : prev);
548
+ },
549
+ onDrop: ({ source, self }) => {
550
+ const fromIndex = source.data.index;
551
+ const edge = extractClosestEdge(self.data);
552
+ let toIndex = index;
553
+ if (edge === "right") toIndex = index + 1;
554
+ if (fromIndex < toIndex) toIndex--;
555
+ if (fromIndex !== toIndex) {
556
+ onReorder(fromIndex, toIndex);
557
+ }
558
+ }
559
+ });
560
+ cleanup = () => {
561
+ cleanupDraggable();
562
+ cleanupDropTarget();
563
+ };
564
+ });
565
+ return () => {
566
+ cancelled = true;
567
+ cleanup?.();
568
+ };
569
+ }, [id, index, onReorder, setDragState]);
570
+ const isCellDragging = dragState.sourceId === id;
571
+ const showLeftEdge = dragState.closestEdge?.id === id && dragState.closestEdge.edge === "left" && dragState.sourceId !== id;
572
+ const showRightEdge = dragState.closestEdge?.id === id && dragState.closestEdge.edge === "right" && dragState.sourceId !== id;
573
+ return /* @__PURE__ */ jsxs4(
574
+ "div",
575
+ {
576
+ ref: cellRef,
577
+ className: cn(
578
+ "group/cell relative",
579
+ isCellDragging && "opacity-50"
580
+ ),
581
+ children: [
582
+ showLeftEdge && /* @__PURE__ */ jsx4("div", { className: "absolute top-0 bottom-0 left-0 z-10 w-0.5 -translate-x-2 bg-primary" }),
583
+ showRightEdge && /* @__PURE__ */ jsx4("div", { className: "absolute top-0 right-0 bottom-0 z-10 w-0.5 translate-x-2 bg-primary" }),
584
+ !isDragging && !isLast && /* @__PURE__ */ jsx4(
585
+ IconButton,
586
+ {
587
+ icon: /* @__PURE__ */ jsx4(AddIcon, { size: 12 }),
588
+ label: "Insert item",
589
+ size: "sm",
590
+ intent: "primary",
591
+ onClick: () => onInsert(index + 1),
592
+ className: "absolute top-1/2 -right-2 z-20 -translate-y-1/2 translate-x-1/2 rounded-full border border-base-200 bg-base opacity-0 group-hover/cell:opacity-100"
593
+ }
594
+ ),
595
+ /* @__PURE__ */ jsx4(
596
+ IconButton,
597
+ {
598
+ ref: handleRef,
599
+ icon: /* @__PURE__ */ jsx4(DragHandle, { size: 16 }),
600
+ label: "Drag to reorder",
601
+ className: cn("absolute left-1.5 z-10 cursor-grab bg-base/80 opacity-0 shadow-sm group-hover/cell:opacity-100 no-hover:opacity-100", chromeTopClass || "top-1.5"),
602
+ tabIndex: -1
603
+ }
604
+ ),
605
+ /* @__PURE__ */ jsxs4("div", { className: cn("absolute right-1.5 z-10 flex items-center gap-1 opacity-0 group-hover/cell:opacity-100 no-hover:opacity-100", chromeTopClass || "top-1.5"), children: [
606
+ onImageClick && /* @__PURE__ */ jsx4(
607
+ IconButton,
608
+ {
609
+ icon: /* @__PURE__ */ jsx4(ImageIcon2, { size: 16 }),
610
+ label: "Change image",
611
+ onClick: onImageClick,
612
+ className: "bg-base/80 shadow-sm"
613
+ }
614
+ ),
615
+ onSettings && /* @__PURE__ */ jsx4(
616
+ IconButton,
617
+ {
618
+ icon: /* @__PURE__ */ jsx4(SettingsIcon, { size: 16 }),
619
+ label: "Item settings",
620
+ onClick: onSettings,
621
+ className: "bg-base/80 shadow-sm"
622
+ }
623
+ ),
624
+ /* @__PURE__ */ jsx4(
625
+ IconButton,
626
+ {
627
+ icon: /* @__PURE__ */ jsx4(DeleteIcon, { size: 16 }),
628
+ label: "Delete item",
629
+ intent: "destructive",
630
+ onClick: () => onRemove(id),
631
+ className: "bg-base/80 shadow-sm"
632
+ }
633
+ )
634
+ ] }),
635
+ children
636
+ ]
637
+ }
638
+ );
639
+ }
640
+
641
+ export {
642
+ EditablePlainText,
643
+ ResolvedMedia,
644
+ ImageDropZone,
645
+ MediaBlock,
646
+ MAX_CONTAINER_COLUMNS,
647
+ containerGridClass,
648
+ colSpanClass,
649
+ spacerHiddenClass,
650
+ useEditableCollection,
651
+ EditableGrid
652
+ };