@drawnagency/primitives 0.1.59 → 0.1.61

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 (95) hide show
  1. package/dist/auth/capabilities.d.ts +16 -0
  2. package/dist/auth/capabilities.d.ts.map +1 -0
  3. package/dist/auth/index.d.ts +2 -0
  4. package/dist/auth/index.d.ts.map +1 -1
  5. package/dist/auth/index.js +3 -1
  6. package/dist/auth/types.d.ts +26 -22
  7. package/dist/auth/types.d.ts.map +1 -1
  8. package/dist/chunk-4OX3Z7SB.js +1421 -0
  9. package/dist/{chunk-RFZNNCAS.js → chunk-53DKVAB6.js} +1 -61
  10. package/dist/chunk-677NTHAZ.js +27 -0
  11. package/dist/chunk-7EOSDFS3.js +10 -0
  12. package/dist/{chunk-NTGSA3TI.js → chunk-AM5LZD6Q.js} +7 -30
  13. package/dist/{chunk-ICLXLWQ5.js → chunk-BT7STGDW.js} +31 -23
  14. package/dist/{chunk-L2JJFOXD.js → chunk-CBWK3KPV.js} +5 -1
  15. package/dist/{chunk-V7JN2DDU.js → chunk-ODCJQOVO.js} +1 -1
  16. package/dist/chunk-TZQPOR5A.js +24 -0
  17. package/dist/{chunk-XTK4BR27.js → chunk-UNWNT52N.js} +13 -0
  18. package/dist/chunk-UQ4OYRW7.js +670 -0
  19. package/dist/chunk-WDSVHKAH.js +54 -0
  20. package/dist/chunk-WLMJCNU7.js +70 -0
  21. package/dist/chunk-ZGFAYZWB.js +8 -0
  22. package/dist/components/editor/DropEdgeIndicator.d.ts +8 -0
  23. package/dist/components/editor/DropEdgeIndicator.d.ts.map +1 -0
  24. package/dist/components/editor/PagesModal.d.ts.map +1 -1
  25. package/dist/components/editor/SectionOrderingModal.d.ts.map +1 -1
  26. package/dist/components/editor/index.js +640 -0
  27. package/dist/components/primitives/defineHeadingSection.d.ts +28 -0
  28. package/dist/components/primitives/defineHeadingSection.d.ts.map +1 -0
  29. package/dist/components/primitives/index.d.ts +10 -0
  30. package/dist/components/primitives/index.d.ts.map +1 -0
  31. package/dist/components/primitives/index.js +166 -0
  32. package/dist/components/sections/Colors/index.d.ts.map +1 -1
  33. package/dist/components/sections/Container/index.d.ts.map +1 -1
  34. package/dist/components/sections/IconList/IconList.d.ts +1 -6
  35. package/dist/components/sections/IconList/IconList.d.ts.map +1 -1
  36. package/dist/components/sections/IconList/index.d.ts +11 -0
  37. package/dist/components/sections/IconList/index.d.ts.map +1 -1
  38. package/dist/components/sections/LinkHeading/index.d.ts.map +1 -1
  39. package/dist/components/sections/SubHeading/index.d.ts +0 -1
  40. package/dist/components/sections/SubHeading/index.d.ts.map +1 -1
  41. package/dist/components/sections/SubSubHeading/index.d.ts +0 -1
  42. package/dist/components/sections/SubSubHeading/index.d.ts.map +1 -1
  43. package/dist/components/sections/all-sections.d.ts +0 -2
  44. package/dist/components/sections/all-sections.d.ts.map +1 -1
  45. package/dist/components/sections/register-schemas.js +376 -2453
  46. package/dist/components/shell/BugReportFAB.d.ts.map +1 -1
  47. package/dist/hooks/useEditorPublish.d.ts.map +1 -1
  48. package/dist/hooks/useMediaPipeline.d.ts.map +1 -1
  49. package/dist/hooks/useSortableRow.d.ts +25 -0
  50. package/dist/hooks/useSortableRow.d.ts.map +1 -0
  51. package/dist/index.js +46 -37
  52. package/dist/lib/image-refs.d.ts +21 -0
  53. package/dist/lib/image-refs.d.ts.map +1 -0
  54. package/dist/lib/index.js +21 -14
  55. package/dist/lib/platform-broker.d.ts +14 -0
  56. package/dist/lib/platform-broker.d.ts.map +1 -0
  57. package/dist/lib/platform-broker.js +60 -0
  58. package/dist/lib/reorder.d.ts +12 -0
  59. package/dist/lib/reorder.d.ts.map +1 -0
  60. package/dist/lib/text.d.ts +2 -0
  61. package/dist/lib/text.d.ts.map +1 -1
  62. package/dist/media/index.js +3 -2
  63. package/dist/media/utils.d.ts.map +1 -1
  64. package/dist/schemas/index.js +17 -17
  65. package/dist/schemas/link.js +13 -0
  66. package/dist/types/database.d.ts +46 -0
  67. package/dist/types/database.d.ts.map +1 -1
  68. package/package.json +14 -2
  69. package/src/auth/capabilities.ts +26 -0
  70. package/src/auth/index.ts +3 -0
  71. package/src/auth/types.ts +36 -26
  72. package/src/components/editor/DropEdgeIndicator.tsx +13 -0
  73. package/src/components/editor/PagesModal.tsx +12 -80
  74. package/src/components/editor/SectionOrderingModal.tsx +8 -78
  75. package/src/components/primitives/defineHeadingSection.tsx +56 -0
  76. package/src/components/primitives/index.ts +11 -0
  77. package/src/components/sections/Colors/index.tsx +2 -4
  78. package/src/components/sections/Container/index.tsx +2 -4
  79. package/src/components/sections/IconList/IconList.tsx +1 -7
  80. package/src/components/sections/IconList/index.tsx +11 -5
  81. package/src/components/sections/LinkHeading/index.tsx +5 -20
  82. package/src/components/sections/SubHeading/index.tsx +6 -23
  83. package/src/components/sections/SubSubHeading/index.tsx +6 -23
  84. package/src/components/shell/BugReportFAB.tsx +13 -9
  85. package/src/hooks/useEditorPublish.ts +71 -49
  86. package/src/hooks/useMediaPipeline.ts +11 -34
  87. package/src/hooks/useSortableRow.ts +99 -0
  88. package/src/lib/image-refs.ts +53 -0
  89. package/src/lib/platform-broker.ts +72 -0
  90. package/src/lib/reorder.ts +20 -0
  91. package/src/lib/text.ts +5 -0
  92. package/src/media/utils.ts +6 -1
  93. package/src/types/database.ts +41 -0
  94. package/dist/chunk-DKOUFIP6.js +0 -35
  95. /package/dist/{chunk-ZU2MKPTG.js → chunk-3DCG4DNU.js} +0 -0
@@ -0,0 +1,640 @@
1
+ import {
2
+ DeleteButton,
3
+ DragHandle,
4
+ Kbd,
5
+ Popover,
6
+ PopoverItem,
7
+ SettingsButton,
8
+ SettingsForm,
9
+ Tooltip,
10
+ buildBlockDragData,
11
+ buildBlockDropData,
12
+ canDropBlock,
13
+ isBlockDragData
14
+ } from "../../chunk-UQ4OYRW7.js";
15
+ import {
16
+ AddIcon,
17
+ IconButton
18
+ } from "../../chunk-WLMJCNU7.js";
19
+ import "../../chunk-677NTHAZ.js";
20
+ import {
21
+ cn
22
+ } from "../../chunk-7EOSDFS3.js";
23
+ import "../../chunk-JSBRDJBE.js";
24
+
25
+ // src/components/editor/InsertButton.tsx
26
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
27
+ function InsertButton({ index, onInsert }) {
28
+ return /* @__PURE__ */ jsx(Tooltip, { content: /* @__PURE__ */ jsxs(Fragment, { children: [
29
+ /* @__PURE__ */ jsx(Kbd, { children: "Click" }),
30
+ " to add below",
31
+ /* @__PURE__ */ jsx("br", {}),
32
+ /* @__PURE__ */ jsx(Kbd, { children: "Alt+Click" }),
33
+ " to add above"
34
+ ] }), children: /* @__PURE__ */ jsx(
35
+ IconButton,
36
+ {
37
+ icon: /* @__PURE__ */ jsx(AddIcon, { size: 18 }),
38
+ label: "Add section",
39
+ className: "pointer-events-auto rounded-md text-base-contrast-light/80 hover:bg-base-contrast-light/10 hover:text-base-contrast",
40
+ tabIndex: -1,
41
+ onClick: (e) => onInsert(e.altKey ? index : index + 1)
42
+ }
43
+ ) });
44
+ }
45
+
46
+ // src/components/editor/IndicatorPill.tsx
47
+ import { useRef, useState, useCallback } from "react";
48
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
49
+ function IndicatorPill({
50
+ dots,
51
+ label,
52
+ hoverContent,
53
+ clickContent,
54
+ className,
55
+ buttonClassName,
56
+ ariaLabel
57
+ }) {
58
+ const [hoverOpen, setHoverOpen] = useState(false);
59
+ const [clickOpen, setClickOpen] = useState(false);
60
+ const buttonRef = useRef(null);
61
+ const hoverTimeout = useRef(null);
62
+ const clearHoverTimeout = useCallback(() => {
63
+ if (hoverTimeout.current) {
64
+ clearTimeout(hoverTimeout.current);
65
+ hoverTimeout.current = null;
66
+ }
67
+ }, []);
68
+ function handleMouseEnter() {
69
+ clearHoverTimeout();
70
+ if (!clickOpen && hoverContent) {
71
+ setHoverOpen(true);
72
+ }
73
+ }
74
+ function handleMouseLeave() {
75
+ clearHoverTimeout();
76
+ hoverTimeout.current = setTimeout(() => {
77
+ setHoverOpen(false);
78
+ }, 150);
79
+ }
80
+ function handleClick() {
81
+ if (!clickContent) return;
82
+ setHoverOpen(false);
83
+ clearHoverTimeout();
84
+ setClickOpen((v) => !v);
85
+ }
86
+ function handleClickClose() {
87
+ setClickOpen(false);
88
+ }
89
+ return /* @__PURE__ */ jsxs2("div", { className: cn("relative", className), children: [
90
+ /* @__PURE__ */ jsxs2(
91
+ "button",
92
+ {
93
+ ref: buttonRef,
94
+ type: "button",
95
+ onClick: handleClick,
96
+ onMouseEnter: handleMouseEnter,
97
+ onMouseLeave: handleMouseLeave,
98
+ "aria-haspopup": clickContent ? "true" : void 0,
99
+ "aria-expanded": clickContent ? clickOpen : void 0,
100
+ "aria-label": ariaLabel,
101
+ className: cn(
102
+ // align-middle keeps the inline-flex pill centred in its line box regardless of
103
+ // whether it leads with a status/audience dot — without it, the dot-less variant's
104
+ // text baseline sits ~2px lower than the dotted variant, misaligning the two pills.
105
+ "cursor-pointer inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-medium align-middle",
106
+ "border border-base-200 hover:bg-base-accent",
107
+ buttonClassName
108
+ ),
109
+ children: [
110
+ dots.length > 0 && /* @__PURE__ */ jsx2("span", { className: "flex -space-x-1", children: dots.map((dot, i) => /* @__PURE__ */ jsx2(
111
+ "span",
112
+ {
113
+ "aria-hidden": "true",
114
+ className: cn("h-3 w-3 rounded-full border border-base", dot.className),
115
+ style: dot.color ? { backgroundColor: dot.color } : void 0
116
+ },
117
+ i
118
+ )) }),
119
+ /* @__PURE__ */ jsx2("span", { children: label })
120
+ ]
121
+ }
122
+ ),
123
+ hoverOpen && hoverContent && /* @__PURE__ */ jsx2(
124
+ "div",
125
+ {
126
+ className: "absolute bottom-full right-0 z-50 mb-1 rounded-full bg-base-accent px-3 py-1 text-xs text-base-contrast-light whitespace-nowrap",
127
+ children: hoverContent
128
+ }
129
+ ),
130
+ /* @__PURE__ */ jsx2(
131
+ Popover,
132
+ {
133
+ isOpen: clickOpen,
134
+ onClose: handleClickClose,
135
+ anchorRef: buttonRef,
136
+ className: "min-w-full max-w-xs",
137
+ children: typeof clickContent === "function" ? clickContent(handleClickClose) : clickContent
138
+ }
139
+ )
140
+ ] });
141
+ }
142
+
143
+ // src/components/editor/StatusIndicator.tsx
144
+ import { Check } from "lucide-react";
145
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
146
+ var descriptions = {
147
+ "live": "Live \u2014 synced",
148
+ "live,modified": "Live \u2014 unpublished content edits",
149
+ "live,draft": "Live on site, changed to draft",
150
+ "live,archived": "Live on site, will be hidden on publish",
151
+ "draft": "Draft \u2014 editor only",
152
+ "draft,modified": "Draft \u2014 unpublished content edits",
153
+ "draft,live": "Draft, will become live on publish",
154
+ "archived": "Archived",
155
+ "archived,modified": "Archived \u2014 unpublished content edits",
156
+ "archived,live": "Archived, will become live on publish",
157
+ "modified": "Unpublished content edits"
158
+ };
159
+ function deriveStatusDisplay({ mainStatus, savedStatus, contentDiffers, isLocalOnly }) {
160
+ const unsaved = isLocalOnly;
161
+ if (!mainStatus) {
162
+ return {
163
+ dots: [{ color: savedStatus }],
164
+ unsaved,
165
+ description: `${savedStatus} \u2014 new section`
166
+ };
167
+ }
168
+ if (mainStatus !== savedStatus) {
169
+ const key = `${mainStatus},${savedStatus}`;
170
+ return {
171
+ dots: [{ color: mainStatus }, { color: savedStatus }],
172
+ unsaved,
173
+ description: descriptions[key] ?? `${mainStatus} \u2192 ${savedStatus}`
174
+ };
175
+ }
176
+ if (contentDiffers) {
177
+ const key = `${mainStatus},modified`;
178
+ return {
179
+ dots: [{ color: mainStatus }, { color: "modified" }],
180
+ unsaved,
181
+ description: descriptions[key] ?? `${mainStatus} \u2014 with edits`
182
+ };
183
+ }
184
+ return {
185
+ dots: [{ color: mainStatus }],
186
+ unsaved: false,
187
+ description: descriptions[mainStatus] ?? mainStatus
188
+ };
189
+ }
190
+ var dotColorClasses = {
191
+ live: "bg-green-500",
192
+ draft: "bg-gray-400",
193
+ archived: "bg-white border-gray-300",
194
+ modified: "bg-orange-400"
195
+ };
196
+ var STATUSES = ["draft", "live", "archived"];
197
+ function StatusIndicator({
198
+ mainStatus,
199
+ savedStatus,
200
+ contentDiffers,
201
+ isLocalOnly,
202
+ status,
203
+ dirty,
204
+ onChange
205
+ }) {
206
+ const display = deriveStatusDisplay({ mainStatus, savedStatus, contentDiffers, isLocalOnly });
207
+ const pillDots = display.dots.map((d) => ({
208
+ className: dotColorClasses[d.color]
209
+ }));
210
+ const unsavedFromDirtyOrLocal = dirty || display.unsaved;
211
+ const labelText = `${status.charAt(0).toUpperCase() + status.slice(1)}${unsavedFromDirtyOrLocal ? " (Unsaved)" : ""}`;
212
+ return /* @__PURE__ */ jsx3(
213
+ IndicatorPill,
214
+ {
215
+ dots: pillDots,
216
+ label: labelText,
217
+ ariaLabel: display.description,
218
+ buttonClassName: "text-base-contrast-light",
219
+ clickContent: (onClose) => /* @__PURE__ */ jsx3("ul", { role: "radiogroup", "aria-label": "Section status", className: "w-full overflow-hidden py-1", children: STATUSES.map((s) => {
220
+ const checked = s === status;
221
+ return /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsxs3(
222
+ PopoverItem,
223
+ {
224
+ role: "radio",
225
+ "aria-checked": checked,
226
+ onClick: () => {
227
+ if (s !== status) onChange(s);
228
+ onClose();
229
+ },
230
+ children: [
231
+ /* @__PURE__ */ jsx3(
232
+ "span",
233
+ {
234
+ "aria-hidden": "true",
235
+ className: cn("h-3 w-3 shrink-0 rounded-full border border-base-200", dotColorClasses[s])
236
+ }
237
+ ),
238
+ /* @__PURE__ */ jsx3("span", { className: "flex-1 font-medium capitalize text-base-contrast", children: s }),
239
+ checked && /* @__PURE__ */ jsx3(Check, { size: 14, strokeWidth: 3, className: "text-primary" })
240
+ ]
241
+ }
242
+ ) }, s);
243
+ }) })
244
+ }
245
+ );
246
+ }
247
+
248
+ // src/components/editor/AudienceIndicator.tsx
249
+ import { Check as Check2 } from "lucide-react";
250
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
251
+ function AudienceIndicator({ access, audiences, onChange }) {
252
+ const selected = audiences.filter((a) => access.includes(a.name));
253
+ const visibleDots = selected.slice(0, 3);
254
+ function toggle(name) {
255
+ const next = access.includes(name) ? access.filter((n) => n !== name) : [...access, name];
256
+ onChange(next);
257
+ }
258
+ const pillDots = visibleDots.map((a) => ({
259
+ color: a.color ?? "#9ca3af"
260
+ }));
261
+ let labelText;
262
+ if (selected.length === 0) labelText = "No audience";
263
+ else if (selected.length === 1) labelText = selected[0].displayName;
264
+ else labelText = `${selected.length} audiences`;
265
+ return /* @__PURE__ */ jsx4(
266
+ IndicatorPill,
267
+ {
268
+ dots: pillDots,
269
+ label: labelText,
270
+ buttonClassName: selected.length === 0 ? "text-base-contrast-light" : void 0,
271
+ clickContent: audiences.length === 0 ? /* @__PURE__ */ jsx4("div", { className: "px-3 py-2 text-xs text-base-contrast-light whitespace-nowrap", children: "No audiences configured. Add audiences in Site Settings \u2192 Viewer Access." }) : /* @__PURE__ */ jsx4("ul", { role: "list", className: "w-full overflow-hidden py-1", children: audiences.map((a) => {
272
+ const checked = access.includes(a.name);
273
+ return /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsxs4(
274
+ PopoverItem,
275
+ {
276
+ role: "checkbox",
277
+ "aria-checked": checked,
278
+ onClick: () => toggle(a.name),
279
+ children: [
280
+ /* @__PURE__ */ jsx4(
281
+ "span",
282
+ {
283
+ "aria-hidden": "true",
284
+ className: cn(
285
+ "flex h-5 w-5 shrink-0 items-center justify-center rounded border transition-colors",
286
+ checked ? "border-primary bg-primary text-primary-contrast" : "border-base-300 bg-base"
287
+ ),
288
+ children: checked && /* @__PURE__ */ jsx4(Check2, { size: 14, strokeWidth: 3 })
289
+ }
290
+ ),
291
+ /* @__PURE__ */ jsx4(
292
+ "span",
293
+ {
294
+ className: "h-3 w-3 shrink-0 rounded-full border border-base-200",
295
+ style: { backgroundColor: a.color ?? "#9ca3af" }
296
+ }
297
+ ),
298
+ /* @__PURE__ */ jsx4("span", { className: "font-medium text-base-contrast", children: a.displayName })
299
+ ]
300
+ }
301
+ ) }, a.name);
302
+ }) })
303
+ }
304
+ );
305
+ }
306
+
307
+ // src/components/editor/SectionWrapper.tsx
308
+ import { useState as useState4, useRef as useRef2, useEffect } from "react";
309
+ import { ArrowRightLeft, Plus } from "lucide-react";
310
+
311
+ // src/components/shell/EditorContext.tsx
312
+ import { createContext, useContext, useState as useState2, useCallback as useCallback2 } from "react";
313
+ import { jsx as jsx5 } from "react/jsx-runtime";
314
+ var EditorContext = createContext(null);
315
+ function useEditorContext() {
316
+ const context = useContext(EditorContext);
317
+ if (!context) {
318
+ throw new Error("useEditorContext must be used within an EditorProvider");
319
+ }
320
+ return context;
321
+ }
322
+
323
+ // src/components/shell/EditorModalContext.tsx
324
+ import { createContext as createContext2, useContext as useContext2, useState as useState3, useCallback as useCallback3 } from "react";
325
+ import { jsx as jsx6 } from "react/jsx-runtime";
326
+ var EditorModalContext = createContext2(null);
327
+ function useEditorModal() {
328
+ const context = useContext2(EditorModalContext);
329
+ if (!context) {
330
+ throw new Error("useEditorModal must be used within an EditorModalProvider");
331
+ }
332
+ return context;
333
+ }
334
+
335
+ // src/components/editor/SectionWrapper.tsx
336
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
337
+ function SectionWrapper({
338
+ sectionId,
339
+ sectionType,
340
+ status,
341
+ dirty,
342
+ index,
343
+ isLast,
344
+ containerId,
345
+ isContainerBlock,
346
+ definition,
347
+ options,
348
+ audiences,
349
+ access,
350
+ onAccessChange,
351
+ onStatusChange,
352
+ onSectionChange,
353
+ onReorder,
354
+ onRequestInsert,
355
+ onDelete,
356
+ onMoveSection,
357
+ onAddChild,
358
+ mainStatus,
359
+ contentDiffersFromMain,
360
+ isLocalOnly,
361
+ children
362
+ }) {
363
+ const { isEditMode, showAllChrome } = useEditorContext();
364
+ const { openModal } = useEditorModal();
365
+ const [isActive, setIsActive] = useState4(false);
366
+ const [chromeReady, setChromeReady] = useState4(isEditMode);
367
+ useEffect(() => {
368
+ if (isEditMode) {
369
+ const id = requestAnimationFrame(() => setChromeReady(true));
370
+ return () => cancelAnimationFrame(id);
371
+ }
372
+ setChromeReady(false);
373
+ }, [isEditMode]);
374
+ const blockRef = useRef2(null);
375
+ const handleRef = useRef2(null);
376
+ const [isDragging, setIsDragging] = useState4(false);
377
+ const [closestEdge, setClosestEdge] = useState4(null);
378
+ const hasSettings = !!((definition.settings || definition.settingsForm) && onSectionChange);
379
+ useEffect(() => {
380
+ const block = blockRef.current;
381
+ if (!block) return;
382
+ const onFocusIn = () => setIsActive(true);
383
+ const onFocusOut = (e) => {
384
+ if (!block.contains(e.relatedTarget)) setIsActive(false);
385
+ };
386
+ block.addEventListener("focusin", onFocusIn);
387
+ block.addEventListener("focusout", onFocusOut);
388
+ return () => {
389
+ block.removeEventListener("focusin", onFocusIn);
390
+ block.removeEventListener("focusout", onFocusOut);
391
+ };
392
+ }, []);
393
+ useEffect(() => {
394
+ const block = blockRef.current;
395
+ const handle = handleRef.current;
396
+ if (!block || !handle || !isEditMode) return;
397
+ let cleanup;
398
+ let cancelled = false;
399
+ const dropElement = block.closest(".section-wrapper") ?? block;
400
+ Promise.all([
401
+ import("../../adapter-HH47ZPGM.js"),
402
+ import("../../closest-edge-EBOXL3YW.js")
403
+ ]).then(([{ draggable, dropTargetForElements }, { attachClosestEdge, extractClosestEdge }]) => {
404
+ if (cancelled) return;
405
+ const cleanupDraggable = draggable({
406
+ element: dropElement,
407
+ dragHandle: handle,
408
+ getInitialData: () => buildBlockDragData({
409
+ blockId: sectionId,
410
+ containerId,
411
+ index,
412
+ isContainer: isContainerBlock
413
+ }),
414
+ onGenerateDragPreview: () => {
415
+ dropElement.style.opacity = "0.4";
416
+ requestAnimationFrame(() => {
417
+ dropElement.style.opacity = "";
418
+ });
419
+ },
420
+ onDragStart: () => {
421
+ setIsDragging(true);
422
+ setIsActive(true);
423
+ },
424
+ onDrop: () => {
425
+ setIsDragging(false);
426
+ setIsActive(false);
427
+ }
428
+ });
429
+ const cleanupDropTarget = dropTargetForElements({
430
+ element: dropElement,
431
+ canDrop: ({ source }) => isBlockDragData(source.data) && canDropBlock(source.data, containerId),
432
+ getData: ({ input, element }) => attachClosestEdge(
433
+ buildBlockDropData({ dropContainerId: containerId, index }),
434
+ { input, element, allowedEdges: ["top", "bottom"] }
435
+ ),
436
+ onDrag: ({ self, source, location }) => {
437
+ const innermost = location.current.dropTargets[0]?.element === self.element;
438
+ if (!innermost || !isBlockDragData(source.data) || source.data.blockId === sectionId) {
439
+ setClosestEdge(null);
440
+ return;
441
+ }
442
+ setClosestEdge(extractClosestEdge(self.data));
443
+ },
444
+ onDragLeave: () => {
445
+ setClosestEdge(null);
446
+ },
447
+ onDrop: () => {
448
+ setClosestEdge(null);
449
+ }
450
+ });
451
+ cleanup = () => {
452
+ cleanupDraggable();
453
+ cleanupDropTarget();
454
+ };
455
+ });
456
+ return () => {
457
+ cancelled = true;
458
+ cleanup?.();
459
+ };
460
+ }, [sectionId, index, containerId, isContainerBlock, isEditMode]);
461
+ function handleSettingsClick() {
462
+ if (!onSectionChange) return;
463
+ if (definition.settingsForm) {
464
+ const CustomForm = definition.settingsForm;
465
+ openModal(
466
+ `${definition.label} Settings`,
467
+ /* @__PURE__ */ jsx7(
468
+ CustomForm,
469
+ {
470
+ ...options ?? {},
471
+ onChange: (result) => onSectionChange(result)
472
+ }
473
+ )
474
+ );
475
+ } else if (definition.settings) {
476
+ openModal(
477
+ `${definition.label} Settings`,
478
+ /* @__PURE__ */ jsx7(
479
+ SettingsForm,
480
+ {
481
+ schema: definition.settings,
482
+ values: options ?? {},
483
+ onChange: (result) => onSectionChange(result),
484
+ tabs: definition.settingsTabs
485
+ }
486
+ )
487
+ );
488
+ }
489
+ }
490
+ if (!isEditMode) {
491
+ if (status && status !== "live") {
492
+ return /* @__PURE__ */ jsxs5(
493
+ "div",
494
+ {
495
+ className: "relative",
496
+ "data-section-id": sectionId,
497
+ "data-section-type": sectionType,
498
+ children: [
499
+ /* @__PURE__ */ jsx7("div", { className: "pointer-events-none absolute right-0 bottom-full z-30 mb-1", children: /* @__PURE__ */ jsxs5(
500
+ "span",
501
+ {
502
+ className: cn(
503
+ "rounded-full px-2 py-0.5 text-xs font-medium",
504
+ status === "live" ? "bg-status-live-bg text-status-live-text" : status === "draft" ? "bg-status-draft-bg text-status-draft-text" : "bg-status-archived-bg text-status-archived-text"
505
+ ),
506
+ children: [
507
+ status,
508
+ dirty && /* @__PURE__ */ jsx7("span", { className: "ml-1 opacity-70", children: "\xB7 Unsaved" })
509
+ ]
510
+ }
511
+ ) }),
512
+ children
513
+ ]
514
+ }
515
+ );
516
+ }
517
+ return /* @__PURE__ */ jsx7(Fragment2, { children });
518
+ }
519
+ const alwaysShow = showAllChrome || isActive;
520
+ return /* @__PURE__ */ jsxs5(
521
+ "div",
522
+ {
523
+ ref: blockRef,
524
+ className: cn(
525
+ "relative",
526
+ isDragging && "opacity-50",
527
+ isActive && "outline outline-2 outline-primary/50 rounded-sm",
528
+ !isActive && showAllChrome && "outline outline-2 outline-primary/20 rounded-sm",
529
+ !alwaysShow && "group-hover:outline group-hover:outline-2 group-hover:outline-primary/20 group-hover:rounded-sm"
530
+ ),
531
+ "data-section-id": sectionId,
532
+ "data-section-type": sectionType,
533
+ children: [
534
+ onRequestInsert && !isDragging && chromeReady && /* @__PURE__ */ jsx7(
535
+ "div",
536
+ {
537
+ className: cn(
538
+ "absolute -left-16 bottom-full z-30 mb-1 hidden xl:block transition-opacity",
539
+ alwaysShow ? "opacity-100" : "opacity-0 group-hover:opacity-100"
540
+ ),
541
+ children: /* @__PURE__ */ jsx7(InsertButton, { index, onInsert: onRequestInsert })
542
+ }
543
+ ),
544
+ /* @__PURE__ */ jsxs5(
545
+ "div",
546
+ {
547
+ className: cn(
548
+ "absolute left-0 bottom-full z-30 mb-1 flex items-center gap-1.5 transition-opacity",
549
+ alwaysShow ? "opacity-100" : "opacity-0 group-hover:opacity-100"
550
+ ),
551
+ children: [
552
+ /* @__PURE__ */ jsxs5("div", { className: "-ml-[34px] flex flex-col items-center", children: [
553
+ /* @__PURE__ */ jsx7(DragHandle, { ref: handleRef }),
554
+ onRequestInsert && !isDragging && chromeReady && /* @__PURE__ */ jsx7("div", { className: "xl:hidden -mt-1", children: /* @__PURE__ */ jsx7(InsertButton, { index, onInsert: onRequestInsert }) })
555
+ ] }),
556
+ /* @__PURE__ */ jsx7("span", { className: "pointer-events-none select-none whitespace-nowrap text-sm capitalize text-base-contrast-light/80", children: definition.label })
557
+ ]
558
+ }
559
+ ),
560
+ /* @__PURE__ */ jsxs5(
561
+ "div",
562
+ {
563
+ className: cn(
564
+ "pointer-events-none absolute right-0 bottom-full z-30 mb-1 flex items-center gap-2 transition-opacity",
565
+ alwaysShow ? "opacity-100" : "opacity-0 group-hover:opacity-100"
566
+ ),
567
+ children: [
568
+ /* @__PURE__ */ jsx7("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx7(
569
+ StatusIndicator,
570
+ {
571
+ mainStatus: mainStatus ?? null,
572
+ savedStatus: status,
573
+ contentDiffers: contentDiffersFromMain ?? false,
574
+ isLocalOnly: isLocalOnly ?? false,
575
+ status,
576
+ dirty,
577
+ onChange: (s) => onStatusChange?.(s)
578
+ }
579
+ ) }),
580
+ /* @__PURE__ */ jsx7("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx7(
581
+ AudienceIndicator,
582
+ {
583
+ access,
584
+ audiences,
585
+ onChange: (newAccess) => onAccessChange?.(newAccess)
586
+ }
587
+ ) }),
588
+ onAddChild && /* @__PURE__ */ jsx7(
589
+ "button",
590
+ {
591
+ type: "button",
592
+ onClick: onAddChild,
593
+ "aria-label": "Add block",
594
+ className: "pointer-events-auto cursor-pointer rounded p-1 text-base-contrast-light hover:text-primary",
595
+ children: /* @__PURE__ */ jsx7(Plus, { size: 16 })
596
+ }
597
+ ),
598
+ hasSettings && /* @__PURE__ */ jsx7(SettingsButton, { onClick: handleSettingsClick }),
599
+ onMoveSection && /* @__PURE__ */ jsx7(
600
+ "button",
601
+ {
602
+ type: "button",
603
+ onClick: onMoveSection,
604
+ "aria-label": "Move to page",
605
+ className: "pointer-events-auto cursor-pointer rounded p-1 text-base-contrast-light hover:text-primary",
606
+ children: /* @__PURE__ */ jsx7(ArrowRightLeft, { size: 16 })
607
+ }
608
+ ),
609
+ onDelete && /* @__PURE__ */ jsx7(DeleteButton, { onDelete })
610
+ ]
611
+ }
612
+ ),
613
+ closestEdge === "top" && /* @__PURE__ */ jsx7(
614
+ "div",
615
+ {
616
+ className: "absolute right-0 left-0 z-10 h-0.5 bg-primary",
617
+ style: {
618
+ top: "calc(var(--section-gap) / 2 * -1)",
619
+ transform: "translateY(-50%)"
620
+ }
621
+ }
622
+ ),
623
+ closestEdge === "bottom" && isLast && /* @__PURE__ */ jsx7("div", { className: "absolute right-0 bottom-0 left-0 z-10 h-0.5 bg-primary" }),
624
+ children
625
+ ]
626
+ }
627
+ );
628
+ }
629
+ export {
630
+ AudienceIndicator,
631
+ DeleteButton,
632
+ DragHandle,
633
+ IndicatorPill,
634
+ InsertButton,
635
+ SectionWrapper,
636
+ SettingsButton,
637
+ SettingsForm,
638
+ StatusIndicator,
639
+ deriveStatusDisplay
640
+ };
@@ -0,0 +1,28 @@
1
+ import type { ReactNode } from "react";
2
+ interface HeadingSectionConfig<T extends string> {
3
+ type: T;
4
+ label: string;
5
+ icon: ReactNode;
6
+ /** Heading level reported to nav generation (one above the rendered tag by design). */
7
+ navRole: "h1" | "h2" | "h3";
8
+ /** The actual HTML tag rendered. */
9
+ tag: "h2" | "h3" | "h4";
10
+ placeholder: string;
11
+ defaultHeading: string;
12
+ /** When true, adds the `excludeFromNav` content field plus its settings toggle. */
13
+ navExclusion?: boolean;
14
+ }
15
+ /**
16
+ * Factory for the heading section family (link / sub / sub-sub). They differ only
17
+ * in their type tag, label, icon, nav level, rendered tag, placeholder, default
18
+ * text, and whether they expose the nav-exclusion toggle — all three render the
19
+ * shared HeadingSection primitive through the identical edit/view plumbing.
20
+ */
21
+ export declare function defineHeadingSection<T extends string>(config: HeadingSectionConfig<T>): import("../..").SectionDefinition<{
22
+ type: T;
23
+ content: {
24
+ heading: string;
25
+ };
26
+ }>;
27
+ export {};
28
+ //# sourceMappingURL=defineHeadingSection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defineHeadingSection.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/defineHeadingSection.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKvC,UAAU,oBAAoB,CAAC,CAAC,SAAS,MAAM;IAC7C,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,uFAAuF;IACvF,OAAO,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC5B,oCAAoC;IACpC,GAAG,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,mFAAmF;IACnF,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;;;;;GA8BrF"}
@@ -0,0 +1,10 @@
1
+ export { EditableRichText } from "./EditableRichText";
2
+ export { EditablePlainText } from "./EditablePlainText";
3
+ export { EditableList } from "./EditableList";
4
+ export { EditableGrid } from "./EditableGrid";
5
+ export { ImageDropZone } from "./ImageDropZone";
6
+ export { ResolvedMedia } from "./ResolvedMedia";
7
+ export { LinkPopover } from "./LinkPopover";
8
+ export { defineHeadingSection } from "./defineHeadingSection";
9
+ export { MediaBlock } from "../sections/Media/MediaBlock";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC"}