camox 0.0.0 → 0.1.2-alpha.2

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 (255) hide show
  1. package/LICENSE.md +110 -0
  2. package/dist/components/AuthGate.d.ts +7 -0
  3. package/dist/components/AuthGate.d.ts.map +1 -0
  4. package/dist/core/components/AddBlockControlBar.d.ts +9 -0
  5. package/dist/core/components/AddBlockControlBar.d.ts.map +1 -0
  6. package/dist/core/components/AddBlockControlBar.js +65 -0
  7. package/dist/core/components/lexical/InlineContentEditable.d.ts +7 -0
  8. package/dist/core/components/lexical/InlineContentEditable.d.ts.map +1 -0
  9. package/dist/core/components/lexical/InlineContentEditable.js +40 -0
  10. package/dist/core/components/lexical/InlineLexicalEditor.d.ts +12 -0
  11. package/dist/core/components/lexical/InlineLexicalEditor.d.ts.map +1 -0
  12. package/dist/core/components/lexical/InlineLexicalEditor.js +133 -0
  13. package/dist/core/components/lexical/InlineParagraphNode.d.ts +10 -0
  14. package/dist/core/components/lexical/InlineParagraphNode.d.ts.map +1 -0
  15. package/dist/core/components/lexical/InlineParagraphNode.js +34 -0
  16. package/dist/core/components/lexical/SelectionBroadcaster.d.ts +6 -0
  17. package/dist/core/components/lexical/SelectionBroadcaster.d.ts.map +1 -0
  18. package/dist/core/components/lexical/SelectionBroadcaster.js +62 -0
  19. package/dist/core/components/lexical/SidebarLexicalEditor.d.ts +9 -0
  20. package/dist/core/components/lexical/SidebarLexicalEditor.d.ts.map +1 -0
  21. package/dist/core/components/lexical/editorConfig.d.ts +4 -0
  22. package/dist/core/components/lexical/editorConfig.d.ts.map +1 -0
  23. package/dist/core/components/lexical/editorConfig.js +24 -0
  24. package/dist/core/createApp.d.ts +373 -0
  25. package/dist/core/createApp.d.ts.map +1 -0
  26. package/dist/core/createApp.js +40 -0
  27. package/dist/core/createBlock.d.ts +947 -0
  28. package/dist/core/createBlock.d.ts.map +1 -0
  29. package/dist/core/createBlock.js +873 -0
  30. package/dist/core/createLayout.d.ts +78 -0
  31. package/dist/core/createLayout.d.ts.map +1 -0
  32. package/dist/core/createLayout.js +73 -0
  33. package/dist/core/hooks/useFieldSelection.d.ts +11 -0
  34. package/dist/core/hooks/useFieldSelection.d.ts.map +1 -0
  35. package/dist/core/hooks/useFieldSelection.js +25 -0
  36. package/dist/core/hooks/useIsEditable.d.ts +2 -0
  37. package/dist/core/hooks/useIsEditable.d.ts.map +1 -0
  38. package/dist/core/hooks/useIsEditable.js +12 -0
  39. package/dist/core/hooks/useOverlayMessage.d.ts +10 -0
  40. package/dist/core/hooks/useOverlayMessage.d.ts.map +1 -0
  41. package/dist/core/hooks/useOverlayMessage.js +37 -0
  42. package/dist/core/lib/contentType.d.ts +165 -0
  43. package/dist/core/lib/contentType.d.ts.map +1 -0
  44. package/dist/core/lib/contentType.js +148 -0
  45. package/dist/core/lib/fieldTypes.d.ts +85 -0
  46. package/dist/core/lib/fieldTypes.d.ts.map +1 -0
  47. package/dist/core/lib/lexicalReact.d.ts +3 -0
  48. package/dist/core/lib/lexicalReact.d.ts.map +1 -0
  49. package/dist/core/lib/lexicalReact.js +24 -0
  50. package/dist/core/lib/lexicalState.d.ts +10 -0
  51. package/dist/core/lib/lexicalState.d.ts.map +1 -0
  52. package/dist/core/lib/lexicalState.js +38 -0
  53. package/dist/core/lib/modifierFormats.d.ts +9 -0
  54. package/dist/core/lib/modifierFormats.d.ts.map +1 -0
  55. package/dist/core/lib/modifierFormats.js +8 -0
  56. package/dist/core/lib/modifiers.d.ts +12 -0
  57. package/dist/core/lib/modifiers.d.ts.map +1 -0
  58. package/dist/core/lib/modifiers.js +27 -0
  59. package/dist/features/content/CamoxContent.d.ts +2 -0
  60. package/dist/features/content/CamoxContent.d.ts.map +1 -0
  61. package/dist/features/content/CamoxContent.js +100 -0
  62. package/dist/features/content/components/AssetCard.d.ts +10 -0
  63. package/dist/features/content/components/AssetCard.d.ts.map +1 -0
  64. package/dist/features/content/components/AssetCard.js +41 -0
  65. package/dist/features/content/components/AssetCardSkeleton.d.ts +2 -0
  66. package/dist/features/content/components/AssetCardSkeleton.d.ts.map +1 -0
  67. package/dist/features/content/components/AssetCardSkeleton.js +11 -0
  68. package/dist/features/content/components/ContentSidebar.d.ts +2 -0
  69. package/dist/features/content/components/ContentSidebar.d.ts.map +1 -0
  70. package/dist/features/content/components/ContentSidebar.js +15 -0
  71. package/dist/features/content/components/UploadDropZone.d.ts +9 -0
  72. package/dist/features/content/components/UploadDropZone.d.ts.map +1 -0
  73. package/dist/features/content/components/UploadDropZone.js +51 -0
  74. package/dist/features/content/components/UploadProgressDrawer.d.ts +11 -0
  75. package/dist/features/content/components/UploadProgressDrawer.d.ts.map +1 -0
  76. package/dist/features/content/components/UploadProgressDrawer.js +72 -0
  77. package/dist/features/preview/CamoxPreview.d.ts +124 -0
  78. package/dist/features/preview/CamoxPreview.d.ts.map +1 -0
  79. package/dist/features/preview/CamoxPreview.js +253 -0
  80. package/dist/features/preview/components/AddBlockSheet.d.ts +3 -0
  81. package/dist/features/preview/components/AddBlockSheet.d.ts.map +1 -0
  82. package/dist/features/preview/components/AddBlockSheet.js +121 -0
  83. package/dist/features/preview/components/AgentChatSheet.d.ts +3 -0
  84. package/dist/features/preview/components/AgentChatSheet.d.ts.map +1 -0
  85. package/dist/features/preview/components/AgentChatSheet.js +24 -0
  86. package/dist/features/preview/components/AssetFieldEditor.d.ts +18 -0
  87. package/dist/features/preview/components/AssetFieldEditor.d.ts.map +1 -0
  88. package/dist/features/preview/components/AssetFieldEditor.js +139 -0
  89. package/dist/features/preview/components/AssetLightbox.d.ts +9 -0
  90. package/dist/features/preview/components/AssetLightbox.d.ts.map +1 -0
  91. package/dist/features/preview/components/AssetLightbox.js +421 -0
  92. package/dist/features/preview/components/AssetPickerGrid.d.ts +11 -0
  93. package/dist/features/preview/components/AssetPickerGrid.d.ts.map +1 -0
  94. package/dist/features/preview/components/AssetPickerGrid.js +92 -0
  95. package/dist/features/preview/components/BlockActionsPopover.d.ts +15 -0
  96. package/dist/features/preview/components/BlockActionsPopover.d.ts.map +1 -0
  97. package/dist/features/preview/components/BlockActionsPopover.js +506 -0
  98. package/dist/features/preview/components/CreatePageSheet.d.ts +3 -0
  99. package/dist/features/preview/components/CreatePageSheet.d.ts.map +1 -0
  100. package/dist/features/preview/components/CreatePageSheet.js +159 -0
  101. package/dist/features/preview/components/DebouncedFieldEditor.d.ts +10 -0
  102. package/dist/features/preview/components/DebouncedFieldEditor.d.ts.map +1 -0
  103. package/dist/features/preview/components/DebouncedFieldEditor.js +51 -0
  104. package/dist/features/preview/components/EditPageSheet.d.ts +3 -0
  105. package/dist/features/preview/components/EditPageSheet.d.ts.map +1 -0
  106. package/dist/features/preview/components/EditPageSheet.js +352 -0
  107. package/dist/features/preview/components/ItemFieldsEditor.d.ts +29 -0
  108. package/dist/features/preview/components/ItemFieldsEditor.d.ts.map +1 -0
  109. package/dist/features/preview/components/ItemFieldsEditor.js +308 -0
  110. package/dist/features/preview/components/LinkFieldEditor.d.ts +8 -0
  111. package/dist/features/preview/components/LinkFieldEditor.d.ts.map +1 -0
  112. package/dist/features/preview/components/LinkFieldEditor.js +190 -0
  113. package/dist/features/preview/components/MultipleAssetFieldEditor.d.ts +10 -0
  114. package/dist/features/preview/components/MultipleAssetFieldEditor.d.ts.map +1 -0
  115. package/dist/features/preview/components/MultipleAssetFieldEditor.js +232 -0
  116. package/dist/features/preview/components/OverlayTracker.d.ts +6 -0
  117. package/dist/features/preview/components/OverlayTracker.d.ts.map +1 -0
  118. package/dist/features/preview/components/OverlayTracker.js +41 -0
  119. package/dist/features/preview/components/Overlays.d.ts +6 -0
  120. package/dist/features/preview/components/Overlays.d.ts.map +1 -0
  121. package/dist/features/preview/components/Overlays.js +58 -0
  122. package/dist/features/preview/components/PageContentSheet.d.ts +3 -0
  123. package/dist/features/preview/components/PageContentSheet.d.ts.map +1 -0
  124. package/dist/features/preview/components/PageContentSheet.js +492 -0
  125. package/dist/features/preview/components/PageLocationFieldset.d.ts +14 -0
  126. package/dist/features/preview/components/PageLocationFieldset.d.ts.map +1 -0
  127. package/dist/features/preview/components/PageLocationFieldset.js +77 -0
  128. package/dist/features/preview/components/PagePicker.d.ts +3 -0
  129. package/dist/features/preview/components/PagePicker.d.ts.map +1 -0
  130. package/dist/features/preview/components/PagePicker.js +185 -0
  131. package/dist/features/preview/components/PageTree.d.ts +3 -0
  132. package/dist/features/preview/components/PageTree.d.ts.map +1 -0
  133. package/dist/features/preview/components/PageTree.js +410 -0
  134. package/dist/features/preview/components/PeekedBlock.d.ts +6 -0
  135. package/dist/features/preview/components/PeekedBlock.d.ts.map +1 -0
  136. package/dist/features/preview/components/PeekedBlock.js +95 -0
  137. package/dist/features/preview/components/PreviewPanel.d.ts +12 -0
  138. package/dist/features/preview/components/PreviewPanel.d.ts.map +1 -0
  139. package/dist/features/preview/components/PreviewPanel.js +192 -0
  140. package/dist/features/preview/components/PreviewSideSheet.d.ts +13 -0
  141. package/dist/features/preview/components/PreviewSideSheet.d.ts.map +1 -0
  142. package/dist/features/preview/components/PreviewSideSheet.js +28 -0
  143. package/dist/features/preview/components/PreviewToolbar.d.ts +2 -0
  144. package/dist/features/preview/components/PreviewToolbar.d.ts.map +1 -0
  145. package/dist/features/preview/components/PreviewToolbar.js +79 -0
  146. package/dist/features/preview/components/RepeatableItemsList.d.ts +14 -0
  147. package/dist/features/preview/components/RepeatableItemsList.d.ts.map +1 -0
  148. package/dist/features/preview/components/RepeatableItemsList.js +366 -0
  149. package/dist/features/preview/components/ShikiMarkdown.d.ts +4 -0
  150. package/dist/features/preview/components/ShikiMarkdown.d.ts.map +1 -0
  151. package/dist/features/preview/components/ShikiMarkdown.js +37 -0
  152. package/dist/features/preview/components/TextFormatToolbar.d.ts +2 -0
  153. package/dist/features/preview/components/TextFormatToolbar.d.ts.map +1 -0
  154. package/dist/features/preview/components/TextFormatToolbar.js +73 -0
  155. package/dist/features/preview/components/UnlinkAssetButton.d.ts +9 -0
  156. package/dist/features/preview/components/UnlinkAssetButton.d.ts.map +1 -0
  157. package/dist/features/preview/components/UnlinkAssetButton.js +55 -0
  158. package/dist/features/preview/overlayConstants.d.ts +19 -0
  159. package/dist/features/preview/overlayConstants.d.ts.map +1 -0
  160. package/dist/features/preview/overlayConstants.js +21 -0
  161. package/dist/features/preview/overlayMessages.d.ts +62 -0
  162. package/dist/features/preview/overlayMessages.d.ts.map +1 -0
  163. package/dist/features/preview/overlayMessages.js +9 -0
  164. package/dist/features/preview/previewConstants.d.ts +2 -0
  165. package/dist/features/preview/previewConstants.d.ts.map +1 -0
  166. package/dist/features/preview/previewStore.d.ts +116 -0
  167. package/dist/features/preview/previewStore.d.ts.map +1 -0
  168. package/dist/features/preview/previewStore.js +321 -0
  169. package/dist/features/provider/CamoxProvider.d.ts +11 -0
  170. package/dist/features/provider/CamoxProvider.d.ts.map +1 -0
  171. package/dist/features/provider/CamoxProvider.js +73 -0
  172. package/dist/features/provider/actionsStore.d.ts +39 -0
  173. package/dist/features/provider/actionsStore.d.ts.map +1 -0
  174. package/dist/features/provider/actionsStore.js +35 -0
  175. package/dist/features/provider/components/CamoxAppContext.d.ts +371 -0
  176. package/dist/features/provider/components/CamoxAppContext.d.ts.map +1 -0
  177. package/dist/features/provider/components/CamoxAppContext.js +17 -0
  178. package/dist/features/provider/components/CommandPalette.d.ts +3 -0
  179. package/dist/features/provider/components/CommandPalette.d.ts.map +1 -0
  180. package/dist/features/provider/components/CommandPalette.js +127 -0
  181. package/dist/features/provider/useAdminShortcuts.d.ts +5 -0
  182. package/dist/features/provider/useAdminShortcuts.d.ts.map +1 -0
  183. package/dist/features/provider/useAdminShortcuts.js +83 -0
  184. package/dist/features/routes/ogRoute.d.ts +7 -0
  185. package/dist/features/routes/ogRoute.d.ts.map +1 -0
  186. package/dist/features/routes/ogRoute.js +19 -0
  187. package/dist/features/routes/pageRoute.d.ts +135 -0
  188. package/dist/features/routes/pageRoute.d.ts.map +1 -0
  189. package/dist/features/routes/pageRoute.js +112 -0
  190. package/dist/features/studio/CamoxStudio.d.ts +7 -0
  191. package/dist/features/studio/CamoxStudio.d.ts.map +1 -0
  192. package/dist/features/studio/CamoxStudio.js +24 -0
  193. package/dist/features/studio/components/Navbar.d.ts +4 -0
  194. package/dist/features/studio/components/Navbar.d.ts.map +1 -0
  195. package/dist/features/studio/components/Navbar.js +95 -0
  196. package/dist/features/studio/components/ProjectMenu.d.ts +2 -0
  197. package/dist/features/studio/components/ProjectMenu.d.ts.map +1 -0
  198. package/dist/features/studio/components/ProjectMenu.js +132 -0
  199. package/dist/features/studio/components/UserButton.d.ts +2 -0
  200. package/dist/features/studio/components/UserButton.d.ts.map +1 -0
  201. package/dist/features/studio/components/UserButton.js +96 -0
  202. package/dist/features/studio/studioStore.d.ts +17 -0
  203. package/dist/features/studio/studioStore.d.ts.map +1 -0
  204. package/dist/features/studio/studioStore.js +44 -0
  205. package/dist/features/studio/useTheme.d.ts +9 -0
  206. package/dist/features/studio/useTheme.d.ts.map +1 -0
  207. package/dist/features/studio/useTheme.js +98 -0
  208. package/dist/features/vite/appGeneration.d.ts +4 -0
  209. package/dist/features/vite/appGeneration.d.ts.map +1 -0
  210. package/dist/features/vite/appGeneration.js +67 -0
  211. package/dist/features/vite/blockBoilerplate.d.ts +3 -0
  212. package/dist/features/vite/blockBoilerplate.d.ts.map +1 -0
  213. package/dist/features/vite/blockBoilerplate.js +59 -0
  214. package/dist/features/vite/convexSync.d.ts +6 -0
  215. package/dist/features/vite/convexSync.d.ts.map +1 -0
  216. package/dist/features/vite/convexSync.js +98 -0
  217. package/dist/features/vite/definitionsSync.d.ts +11 -0
  218. package/dist/features/vite/definitionsSync.d.ts.map +1 -0
  219. package/dist/features/vite/definitionsSync.js +157 -0
  220. package/dist/features/vite/routeGeneration.d.ts +4 -0
  221. package/dist/features/vite/routeGeneration.d.ts.map +1 -0
  222. package/dist/features/vite/routeGeneration.js +194 -0
  223. package/dist/features/vite/skillGeneration.d.ts +4 -0
  224. package/dist/features/vite/skillGeneration.d.ts.map +1 -0
  225. package/dist/features/vite/skillGeneration.js +69 -0
  226. package/dist/features/vite/utils.d.ts +2 -0
  227. package/dist/features/vite/utils.d.ts.map +1 -0
  228. package/dist/features/vite/utils.js +10 -0
  229. package/dist/features/vite/vite.d.ts +18 -0
  230. package/dist/features/vite/vite.d.ts.map +1 -0
  231. package/dist/features/vite/vite.js +77 -0
  232. package/dist/hooks/use-file-upload.d.ts +22 -0
  233. package/dist/hooks/use-file-upload.d.ts.map +1 -0
  234. package/dist/hooks/use-marquee-selection.d.ts +17 -0
  235. package/dist/hooks/use-marquee-selection.d.ts.map +1 -0
  236. package/dist/lib/analytics-client.d.ts +3 -0
  237. package/dist/lib/analytics-client.d.ts.map +1 -0
  238. package/dist/lib/analytics.d.ts +3 -0
  239. package/dist/lib/analytics.d.ts.map +1 -0
  240. package/dist/lib/analytics.js +24 -0
  241. package/dist/lib/auth.d.ts +3683 -0
  242. package/dist/lib/auth.d.ts.map +1 -0
  243. package/dist/lib/convex-site.d.ts +3 -0
  244. package/dist/lib/convex-site.d.ts.map +1 -0
  245. package/dist/lib/utils.d.ts +40 -0
  246. package/dist/lib/utils.d.ts.map +1 -0
  247. package/dist/studio.css +2 -0
  248. package/package.json +123 -10
  249. package/server/api.d.ts +1 -0
  250. package/server/api.js +1 -0
  251. package/server/dataModel.d.ts +1 -0
  252. package/server/dataModel.js +1 -0
  253. package/skills/camox-block/SKILL.md +357 -0
  254. package/skills/camox-layout/SKILL.md +181 -0
  255. package/index.js +0 -3
@@ -0,0 +1,873 @@
1
+ import { LAYOUT_OVERLAY_COLORS, OVERLAY_COLORS, OVERLAY_OFFSETS, OVERLAY_WIDTHS } from "../features/preview/overlayConstants.js";
2
+ import { postOverlayMessage } from "../features/preview/overlayMessages.js";
3
+ import { previewStore } from "../features/preview/previewStore.js";
4
+ import { AddBlockControlBar } from "./components/AddBlockControlBar.js";
5
+ import { InlineLexicalEditor } from "./components/lexical/InlineLexicalEditor.js";
6
+ import { useFieldSelection } from "./hooks/useFieldSelection.js";
7
+ import { useIsEditable } from "./hooks/useIsEditable.js";
8
+ import { useOverlayMessage } from "./hooks/useOverlayMessage.js";
9
+ import { Type } from "./lib/contentType.js";
10
+ import { lexicalStateToReactNodes } from "./lib/lexicalReact.js";
11
+ import { useFrame } from "@camox/ui/frame";
12
+ import { Input } from "@camox/ui/input";
13
+ import { Kbd } from "@camox/ui/kbd";
14
+ import { Label } from "@camox/ui/label";
15
+ import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "@camox/ui/popover";
16
+ import { toast } from "@camox/ui/toaster";
17
+ import { Slot } from "@radix-ui/react-slot";
18
+ import { Type as Type$1 } from "@sinclair/typebox";
19
+ import { useSelector } from "@xstate/store/react";
20
+ import { api } from "camox/server/api";
21
+ import { useMutation, useQuery } from "convex/react";
22
+ import * as React from "react";
23
+ import { createPortal } from "react-dom";
24
+ import { useIsPreviewSheetOpen } from "@/features/preview/components/PreviewSideSheet.tsx";
25
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
26
+ //#region src/core/createBlock.tsx
27
+ /** Normalize legacy links (no `type` field) to the new union shape */
28
+ var normalizeLinkValue = (value) => {
29
+ if (!value.type) return {
30
+ type: "external",
31
+ ...value
32
+ };
33
+ return value;
34
+ };
35
+ /** Resolve a LinkValue to an href string */
36
+ var resolveLinkHref = (link, pages) => {
37
+ if (link.type === "page") return (pages?.find((p) => p._id === link.pageId))?.fullPath ?? "#";
38
+ return link.href;
39
+ };
40
+ var hasShownEmbedLockToast = false;
41
+ function createBlock(options) {
42
+ const typeboxSchema = Type$1.Object(options.content);
43
+ const contentSchema = {
44
+ type: "object",
45
+ title: options.title,
46
+ description: options.description,
47
+ properties: typeboxSchema.properties,
48
+ required: Object.keys(options.content),
49
+ toMarkdown: options.toMarkdown
50
+ };
51
+ const settingsTypeboxSchema = options.settings ? Type$1.Object(options.settings) : null;
52
+ const settingsSchema = settingsTypeboxSchema ? {
53
+ type: "object",
54
+ properties: settingsTypeboxSchema.properties,
55
+ required: Object.keys(options.settings)
56
+ } : void 0;
57
+ const contentDefaults = {};
58
+ const contentDefaultsForStorage = {};
59
+ for (const [key, prop] of Object.entries(typeboxSchema.properties)) if ("default" in prop) {
60
+ contentDefaults[key] = prop.default;
61
+ const ft = prop.fieldType;
62
+ const ait = prop.arrayItemType;
63
+ if (ft === "Image" || ft === "File" || ait === "Image" || ait === "File") continue;
64
+ contentDefaultsForStorage[key] = prop.default;
65
+ }
66
+ const repeatableItemDefaults = {};
67
+ for (const [key, prop] of Object.entries(typeboxSchema.properties)) {
68
+ const p = prop;
69
+ if (p.type === "array" && p.items?.properties) {
70
+ const itemDefaults = {};
71
+ for (const [itemKey, itemProp] of Object.entries(p.items.properties)) if (itemProp && typeof itemProp === "object" && "default" in itemProp) itemDefaults[itemKey] = itemProp.default;
72
+ if (Object.keys(itemDefaults).length > 0) repeatableItemDefaults[key] = itemDefaults;
73
+ }
74
+ }
75
+ const settingsDefaults = {};
76
+ if (settingsTypeboxSchema) {
77
+ for (const [key, prop] of Object.entries(settingsTypeboxSchema.properties)) if ("default" in prop) settingsDefaults[key] = prop.default;
78
+ }
79
+ const Context = React.createContext(null);
80
+ const RepeaterItemContext = React.createContext(null);
81
+ const RepeaterHoverContext = React.createContext(false);
82
+ /**
83
+ * Build a field ID that matches the sidebar's `getFieldId` format.
84
+ * Root fields: blockId__fieldName
85
+ * Repeater item fields: blockId__itemId__fieldName
86
+ * Nested item fields: blockId__parentItemId:nestedFieldName:index__fieldName
87
+ */
88
+ const getOverlayFieldId = (blockId, repeaterContext, fieldName) => {
89
+ if (repeaterContext?.itemId) return `${blockId}__${repeaterContext.itemId}__${fieldName}`;
90
+ if (repeaterContext?.nested) {
91
+ const { parentItemId, nestedFieldName, nestedIndex } = repeaterContext.nested;
92
+ return `${blockId}__${parentItemId}:${nestedFieldName}:${nestedIndex}__${fieldName}`;
93
+ }
94
+ return `${blockId}__${fieldName}`;
95
+ };
96
+ const Field = ({ name, children }) => {
97
+ const blockContext = React.use(Context);
98
+ if (!blockContext) throw new Error("Field must be used within a Block Component");
99
+ const { blockId, content, mode } = blockContext;
100
+ const isContentEditable = useIsEditable(mode);
101
+ const colors = mode === "layout" ? LAYOUT_OVERLAY_COLORS : OVERLAY_COLORS;
102
+ const elementRef = React.useRef(null);
103
+ const { window: iframeWindow } = useFrame();
104
+ const repeaterContext = React.use(RepeaterItemContext);
105
+ const fieldId = getOverlayFieldId(blockId, repeaterContext, String(name));
106
+ const fieldValue = repeaterContext ? repeaterContext.itemContent[name] : content[name];
107
+ const [isHovered, setIsHovered] = React.useState(false);
108
+ const [isEditorFocused, setIsEditorFocused] = React.useState(false);
109
+ const isSelectedFromBreadcrumbs = useFieldSelection(blockId, String(name), "String", repeaterContext?.itemId);
110
+ const isFocused = isEditorFocused || isSelectedFromBreadcrumbs;
111
+ const isHoveredFromSidebar = useOverlayMessage(iframeWindow, isContentEditable, "CAMOX_HOVER_FIELD", "CAMOX_HOVER_FIELD_END", { fieldId });
112
+ React.useEffect(() => {
113
+ setIsHovered(isHoveredFromSidebar);
114
+ }, [isHoveredFromSidebar]);
115
+ const updateBlockContent = useMutation(api.blocks.updateBlockContent);
116
+ const updateRepeatableItemContent = useMutation(api.repeatableItems.updateRepeatableItemContent);
117
+ const handleChange = React.useCallback((newValue) => {
118
+ if (repeaterContext) {
119
+ const { itemId } = repeaterContext;
120
+ if (itemId) updateRepeatableItemContent({
121
+ itemId,
122
+ content: { [name]: newValue }
123
+ });
124
+ } else updateBlockContent({
125
+ blockId,
126
+ content: { [name]: newValue }
127
+ });
128
+ }, [
129
+ blockId,
130
+ name,
131
+ repeaterContext,
132
+ updateRepeatableItemContent,
133
+ updateBlockContent
134
+ ]);
135
+ const handleFocus = React.useCallback(() => {
136
+ setIsEditorFocused(true);
137
+ if (repeaterContext && repeaterContext.itemId) previewStore.send({
138
+ type: "setSelectedRepeatableItem",
139
+ blockId,
140
+ itemId: repeaterContext.itemId,
141
+ fieldName: repeaterContext.arrayFieldName
142
+ });
143
+ else previewStore.send({
144
+ type: "setSelectedField",
145
+ blockId,
146
+ fieldName: name.toString(),
147
+ fieldType: "String"
148
+ });
149
+ }, [
150
+ blockId,
151
+ name,
152
+ repeaterContext?.itemId
153
+ ]);
154
+ const handleBlur = React.useCallback(() => {
155
+ setIsEditorFocused(false);
156
+ }, []);
157
+ const handleMouseEnter = () => {
158
+ if (isContentEditable) setIsHovered(true);
159
+ };
160
+ const handleMouseLeave = () => {
161
+ if (isContentEditable) setIsHovered(false);
162
+ };
163
+ const activateRef = React.useRef(null);
164
+ const handleSlotClick = React.useCallback(() => {
165
+ activateRef.current?.();
166
+ }, []);
167
+ const overlayStyle = isContentEditable && (isHovered || isFocused) ? {
168
+ outline: `${isFocused ? OVERLAY_WIDTHS.selected : OVERLAY_WIDTHS.hover} solid ${isFocused ? colors.selected : colors.hover}`,
169
+ outlineOffset: isFocused ? OVERLAY_OFFSETS.fieldSelected : OVERLAY_OFFSETS.fieldHover
170
+ } : void 0;
171
+ if (!isContentEditable) return /* @__PURE__ */ jsx(Fragment, { children: children(lexicalStateToReactNodes(fieldValue)) });
172
+ return /* @__PURE__ */ jsx(Slot, {
173
+ ref: elementRef,
174
+ "data-camox-field-id": fieldId,
175
+ onMouseEnter: handleMouseEnter,
176
+ onMouseLeave: handleMouseLeave,
177
+ onClick: handleSlotClick,
178
+ style: overlayStyle,
179
+ children: children(/* @__PURE__ */ jsx(InlineLexicalEditor, {
180
+ initialState: fieldValue,
181
+ externalState: fieldValue,
182
+ onChange: handleChange,
183
+ onFocus: handleFocus,
184
+ onBlur: handleBlur,
185
+ activateRef
186
+ }))
187
+ });
188
+ };
189
+ const Embed = ({ name, children }) => {
190
+ const blockContext = React.use(Context);
191
+ if (!blockContext) throw new Error("Embed must be used within a Block Component");
192
+ const { blockId, content, mode } = blockContext;
193
+ const isContentEditable = useIsEditable(mode);
194
+ const colors = mode === "layout" ? LAYOUT_OVERLAY_COLORS : OVERLAY_COLORS;
195
+ const { window: iframeWindow } = useFrame();
196
+ const repeaterContext = React.use(RepeaterItemContext);
197
+ const fieldValue = repeaterContext ? repeaterContext.itemContent[name] : content[name];
198
+ const fieldId = getOverlayFieldId(blockId, repeaterContext, String(name));
199
+ const [isOpen, setIsOpen] = React.useState(false);
200
+ const [urlValue, setUrlValue] = React.useState(fieldValue);
201
+ const [isHovered, setIsHovered] = React.useState(false);
202
+ const timerRef = React.useRef(null);
203
+ const isHoveredFromSidebar = useOverlayMessage(iframeWindow, isContentEditable, "CAMOX_HOVER_FIELD", "CAMOX_HOVER_FIELD_END", { fieldId });
204
+ React.useEffect(() => {
205
+ setIsHovered(isHoveredFromSidebar);
206
+ }, [isHoveredFromSidebar]);
207
+ const updateBlockContent = useMutation(api.blocks.updateBlockContent);
208
+ const updateRepeatableItemContent = useMutation(api.repeatableItems.updateRepeatableItemContent);
209
+ React.useEffect(() => {
210
+ if (!isOpen) setUrlValue(fieldValue);
211
+ }, [fieldValue, isOpen]);
212
+ React.useEffect(() => {
213
+ return () => {
214
+ if (timerRef.current) clearTimeout(timerRef.current);
215
+ };
216
+ }, []);
217
+ const handleUrlChange = (e) => {
218
+ const newValue = e.target.value;
219
+ setUrlValue(newValue);
220
+ if (timerRef.current) clearTimeout(timerRef.current);
221
+ timerRef.current = window.setTimeout(() => {
222
+ if (repeaterContext?.nested) {
223
+ const { parentItemId, parentContent, nestedFieldName, nestedIndex } = repeaterContext.nested;
224
+ const nestedArray = [...parentContent[nestedFieldName] || []];
225
+ nestedArray[nestedIndex] = {
226
+ ...nestedArray[nestedIndex],
227
+ [name]: newValue
228
+ };
229
+ updateRepeatableItemContent({
230
+ itemId: parentItemId,
231
+ content: { [nestedFieldName]: nestedArray }
232
+ });
233
+ } else if (repeaterContext?.itemId) updateRepeatableItemContent({
234
+ itemId: repeaterContext.itemId,
235
+ content: { [name]: newValue }
236
+ });
237
+ else updateBlockContent({
238
+ blockId,
239
+ content: { [name]: newValue }
240
+ });
241
+ }, 500);
242
+ };
243
+ const handleOpenChange = (open) => {
244
+ setIsOpen(open);
245
+ if (open) if (repeaterContext?.nested) previewStore.send({
246
+ type: "setSelectedRepeatableItem",
247
+ blockId,
248
+ itemId: repeaterContext.nested.parentItemId,
249
+ fieldName: repeaterContext.nested.parentArrayFieldName
250
+ });
251
+ else if (repeaterContext?.itemId) previewStore.send({
252
+ type: "setSelectedRepeatableItem",
253
+ blockId,
254
+ itemId: repeaterContext.itemId,
255
+ fieldName: repeaterContext.arrayFieldName
256
+ });
257
+ else previewStore.send({
258
+ type: "setSelectedField",
259
+ blockId,
260
+ fieldName: name.toString(),
261
+ fieldType: "Embed"
262
+ });
263
+ };
264
+ return /* @__PURE__ */ jsxs(Popover, {
265
+ open: isContentEditable ? isOpen : false,
266
+ onOpenChange: isContentEditable ? handleOpenChange : void 0,
267
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, {
268
+ asChild: true,
269
+ children: /* @__PURE__ */ jsxs("div", {
270
+ style: { position: "relative" },
271
+ onMouseEnter: isContentEditable ? () => setIsHovered(true) : void 0,
272
+ onMouseLeave: isContentEditable ? () => setIsHovered(false) : void 0,
273
+ children: [children(fieldValue), isContentEditable && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
274
+ style: {
275
+ position: "absolute",
276
+ inset: 0,
277
+ zIndex: 10
278
+ },
279
+ onClick: () => {
280
+ if (hasShownEmbedLockToast) return;
281
+ hasShownEmbedLockToast = true;
282
+ toast(/* @__PURE__ */ jsxs("span", { children: [
283
+ "Hold ",
284
+ /* @__PURE__ */ jsx(Kbd, { children: "L" }),
285
+ " to interact with the embed content"
286
+ ] }));
287
+ }
288
+ }), (isHovered || isOpen) && /* @__PURE__ */ jsx("div", { style: {
289
+ position: "absolute",
290
+ inset: isOpen ? OVERLAY_OFFSETS.blockSelected : OVERLAY_OFFSETS.blockHover,
291
+ border: `${isOpen ? OVERLAY_WIDTHS.selected : OVERLAY_WIDTHS.hover} solid ${isOpen ? colors.selected : colors.hover}`,
292
+ pointerEvents: "none",
293
+ zIndex: 11
294
+ } })] })]
295
+ })
296
+ }), isContentEditable && /* @__PURE__ */ jsx(PopoverContent, {
297
+ className: "w-96 gap-2",
298
+ children: /* @__PURE__ */ jsxs("form", {
299
+ className: "grid gap-2",
300
+ children: [/* @__PURE__ */ jsx(Label, {
301
+ htmlFor: "url",
302
+ children: options.content[name]?.title ?? String(name)
303
+ }), /* @__PURE__ */ jsx(Input, {
304
+ type: "url",
305
+ id: "url",
306
+ value: urlValue,
307
+ onChange: handleUrlChange
308
+ })]
309
+ })
310
+ })]
311
+ });
312
+ };
313
+ const Link = ({ name, children }) => {
314
+ const blockContext = React.use(Context);
315
+ if (!blockContext) throw new Error("Link must be used within a Block Component");
316
+ const { blockId, content, mode } = blockContext;
317
+ const isContentEditable = useIsEditable(mode);
318
+ const colors = mode === "layout" ? LAYOUT_OVERLAY_COLORS : OVERLAY_COLORS;
319
+ const elementRef = React.useRef(null);
320
+ const { window: iframeWindow } = useFrame();
321
+ const repeaterContext = React.use(RepeaterItemContext);
322
+ const fieldValue = normalizeLinkValue(repeaterContext ? repeaterContext.itemContent[name] : content[name]);
323
+ const resolvedHref = resolveLinkHref(fieldValue, useQuery(api.pages.listPages));
324
+ const fieldId = getOverlayFieldId(blockId, repeaterContext, String(name));
325
+ const [isEditing, setIsEditing] = React.useState(false);
326
+ const [displayText, setDisplayText] = React.useState(fieldValue.text);
327
+ const [isHovered, setIsHovered] = React.useState(false);
328
+ const [isEditorFocused, setIsEditorFocused] = React.useState(false);
329
+ const isSelectedFromBreadcrumbs = useFieldSelection(blockId, String(name), "Link", repeaterContext?.itemId);
330
+ const isFocused = isEditorFocused || isSelectedFromBreadcrumbs;
331
+ React.useEffect(() => {
332
+ if (!isEditing) setDisplayText(fieldValue.text);
333
+ }, [fieldValue.text, isEditing]);
334
+ const isHoveredFromSidebar = useOverlayMessage(iframeWindow, isContentEditable, "CAMOX_HOVER_FIELD", "CAMOX_HOVER_FIELD_END", { fieldId });
335
+ React.useEffect(() => {
336
+ setIsHovered(isHoveredFromSidebar);
337
+ }, [isHoveredFromSidebar]);
338
+ const updateBlockContent = useMutation(api.blocks.updateBlockContent);
339
+ const updateRepeatableItemContent = useMutation(api.repeatableItems.updateRepeatableItemContent);
340
+ const saveLinkValue = (newLinkValue) => {
341
+ if (repeaterContext?.nested) {
342
+ const { parentItemId, parentContent, nestedFieldName, nestedIndex } = repeaterContext.nested;
343
+ const nestedArray = [...parentContent[nestedFieldName] || []];
344
+ nestedArray[nestedIndex] = {
345
+ ...nestedArray[nestedIndex],
346
+ [name]: newLinkValue
347
+ };
348
+ updateRepeatableItemContent({
349
+ itemId: parentItemId,
350
+ content: { [nestedFieldName]: nestedArray }
351
+ });
352
+ } else if (repeaterContext?.itemId) updateRepeatableItemContent({
353
+ itemId: repeaterContext.itemId,
354
+ content: { [name]: newLinkValue }
355
+ });
356
+ else updateBlockContent({
357
+ blockId,
358
+ content: { [name]: newLinkValue }
359
+ });
360
+ };
361
+ const handleInput = (e) => {
362
+ const newText = e.target.textContent || "";
363
+ saveLinkValue({
364
+ ...fieldValue,
365
+ text: newText
366
+ });
367
+ };
368
+ const buildLinkBreadcrumbs = () => {
369
+ const crumbs = [{
370
+ type: "Block",
371
+ id: blockId
372
+ }];
373
+ if (repeaterContext?.nested) {
374
+ crumbs.push({
375
+ type: "RepeatableObject",
376
+ id: repeaterContext.nested.parentItemId,
377
+ fieldName: repeaterContext.nested.parentArrayFieldName
378
+ });
379
+ crumbs.push({
380
+ type: "RepeatableObject",
381
+ id: `idx:${repeaterContext.nested.nestedIndex}`,
382
+ fieldName: repeaterContext.nested.nestedFieldName
383
+ });
384
+ } else if (repeaterContext?.itemId) crumbs.push({
385
+ type: "RepeatableObject",
386
+ id: repeaterContext.itemId,
387
+ fieldName: repeaterContext.arrayFieldName
388
+ });
389
+ crumbs.push({
390
+ type: "Link",
391
+ id: String(name),
392
+ fieldName: String(name)
393
+ });
394
+ return crumbs;
395
+ };
396
+ const handleFocus = () => {
397
+ setIsEditing(true);
398
+ setIsEditorFocused(true);
399
+ previewStore.send({
400
+ type: "setSelectionBreadcrumbs",
401
+ breadcrumbs: buildLinkBreadcrumbs()
402
+ });
403
+ };
404
+ const handleBlur = () => {
405
+ setIsEditing(false);
406
+ setIsEditorFocused(false);
407
+ };
408
+ const handleEditLink = (e) => {
409
+ e.stopPropagation();
410
+ previewStore.send({ type: "toggleContentSheet" });
411
+ setIsEditorFocused(false);
412
+ setIsEditing(false);
413
+ };
414
+ return /* @__PURE__ */ jsxs(Popover, {
415
+ open: isContentEditable && isEditorFocused,
416
+ children: [/* @__PURE__ */ jsx(PopoverAnchor, {
417
+ asChild: true,
418
+ children: /* @__PURE__ */ jsx(Slot, {
419
+ ref: elementRef,
420
+ "data-camox-field-id": isContentEditable ? fieldId : void 0,
421
+ contentEditable: isContentEditable,
422
+ onClick: isContentEditable ? (e) => e.preventDefault() : void 0,
423
+ onInput: handleInput,
424
+ onFocus: handleFocus,
425
+ onBlur: handleBlur,
426
+ onMouseEnter: isContentEditable ? () => setIsHovered(true) : void 0,
427
+ onMouseLeave: isContentEditable ? () => setIsHovered(false) : void 0,
428
+ onKeyDown: (e) => {
429
+ if (e.key === "Escape") e.target.blur();
430
+ },
431
+ spellCheck: false,
432
+ suppressContentEditableWarning: true,
433
+ style: isContentEditable && (isHovered || isFocused) ? {
434
+ outline: `${isFocused ? OVERLAY_WIDTHS.selected : OVERLAY_WIDTHS.hover} solid ${isFocused ? colors.selected : colors.hover}`,
435
+ outlineOffset: isFocused ? OVERLAY_OFFSETS.fieldSelected : OVERLAY_OFFSETS.fieldHover
436
+ } : void 0,
437
+ children: children({
438
+ text: displayText,
439
+ href: resolvedHref,
440
+ newTab: fieldValue.newTab
441
+ })
442
+ })
443
+ }), isContentEditable && /* @__PURE__ */ jsx(PopoverContent, {
444
+ className: "w-auto p-2",
445
+ onOpenAutoFocus: (e) => e.preventDefault(),
446
+ align: "end",
447
+ children: /* @__PURE__ */ jsx("button", {
448
+ type: "button",
449
+ className: "hover:bg-accent flex items-center gap-1.5 rounded-md px-2 py-1 text-sm transition-colors",
450
+ onMouseDown: (e) => e.preventDefault(),
451
+ onClick: handleEditLink,
452
+ children: "Edit link"
453
+ })
454
+ })]
455
+ });
456
+ };
457
+ const Image = ({ name, children }) => {
458
+ const blockContext = React.use(Context);
459
+ if (!blockContext) throw new Error("Image must be used within a Block Component");
460
+ const { blockId, content, mode } = blockContext;
461
+ const isContentEditable = useIsEditable(mode);
462
+ const colors = mode === "layout" ? LAYOUT_OVERLAY_COLORS : OVERLAY_COLORS;
463
+ const { window: iframeWindow } = useFrame();
464
+ const repeaterContext = React.use(RepeaterItemContext);
465
+ const fieldValue = repeaterContext ? repeaterContext.itemContent[name] : content[name];
466
+ const fieldId = getOverlayFieldId(blockId, repeaterContext, String(name));
467
+ const [isHovered, setIsHovered] = React.useState(false);
468
+ const isFocused = useFieldSelection(blockId, String(name), "Image", repeaterContext?.itemId);
469
+ const isHoveredFromSidebar = useOverlayMessage(iframeWindow, isContentEditable, "CAMOX_HOVER_FIELD", "CAMOX_HOVER_FIELD_END", { fieldId });
470
+ React.useEffect(() => {
471
+ setIsHovered(isHoveredFromSidebar);
472
+ }, [isHoveredFromSidebar]);
473
+ const buildImageBreadcrumbs = () => {
474
+ const crumbs = [{
475
+ type: "Block",
476
+ id: blockId
477
+ }];
478
+ if (repeaterContext?.nested) {
479
+ crumbs.push({
480
+ type: "RepeatableObject",
481
+ id: repeaterContext.nested.parentItemId,
482
+ fieldName: repeaterContext.nested.parentArrayFieldName
483
+ });
484
+ crumbs.push({
485
+ type: "RepeatableObject",
486
+ id: `idx:${repeaterContext.nested.nestedIndex}`,
487
+ fieldName: repeaterContext.nested.nestedFieldName
488
+ });
489
+ } else if (repeaterContext?.itemId) crumbs.push({
490
+ type: "RepeatableObject",
491
+ id: repeaterContext.itemId,
492
+ fieldName: repeaterContext.arrayFieldName
493
+ });
494
+ crumbs.push({
495
+ type: "Image",
496
+ id: String(name),
497
+ fieldName: String(name)
498
+ });
499
+ return crumbs;
500
+ };
501
+ const handleClick = () => {
502
+ if (!isContentEditable) return;
503
+ previewStore.send({
504
+ type: "setSelectionBreadcrumbs",
505
+ breadcrumbs: buildImageBreadcrumbs()
506
+ });
507
+ previewStore.send({ type: "toggleContentSheet" });
508
+ };
509
+ if (!isContentEditable) return /* @__PURE__ */ jsx(Fragment, { children: children(fieldValue) });
510
+ const showOverlay = isHovered || isFocused;
511
+ return /* @__PURE__ */ jsxs("div", {
512
+ style: { position: "relative" },
513
+ "data-camox-field-id": fieldId,
514
+ onMouseEnter: () => setIsHovered(true),
515
+ onMouseLeave: () => setIsHovered(false),
516
+ onClick: handleClick,
517
+ children: [children(fieldValue), showOverlay && /* @__PURE__ */ jsx("div", { style: {
518
+ position: "absolute",
519
+ inset: isFocused ? OVERLAY_OFFSETS.blockSelected : OVERLAY_OFFSETS.blockHover,
520
+ border: `${isFocused ? OVERLAY_WIDTHS.selected : OVERLAY_WIDTHS.hover} solid ${isFocused ? colors.selected : colors.hover}`,
521
+ pointerEvents: "none",
522
+ zIndex: 10
523
+ } })]
524
+ });
525
+ };
526
+ const File = ({ name, children }) => {
527
+ const blockContext = React.use(Context);
528
+ if (!blockContext) throw new Error("File must be used within a Block Component");
529
+ const { content } = blockContext;
530
+ const repeaterContext = React.use(RepeaterItemContext);
531
+ return /* @__PURE__ */ jsx(Fragment, { children: children(repeaterContext ? repeaterContext.itemContent[name] : content[name]) });
532
+ };
533
+ const RepeaterItemWrapper = ({ itemId, blockId, mode, children }) => {
534
+ const isContentEditable = useIsEditable(mode);
535
+ const colors = mode === "layout" ? LAYOUT_OVERLAY_COLORS : OVERLAY_COLORS;
536
+ const { window: iframeWindow } = useFrame();
537
+ const isRepeaterHovered = React.useContext(RepeaterHoverContext);
538
+ const isHovered = useOverlayMessage(iframeWindow, isContentEditable, "CAMOX_HOVER_REPEATER_ITEM", "CAMOX_HOVER_REPEATER_ITEM_END", {
539
+ blockId,
540
+ itemId
541
+ });
542
+ const showOverlay = isContentEditable && (isHovered || isRepeaterHovered);
543
+ return /* @__PURE__ */ jsxs("div", {
544
+ style: { position: "relative" },
545
+ "data-camox-repeater-item-id": isContentEditable ? itemId : void 0,
546
+ children: [children, showOverlay && /* @__PURE__ */ jsx("div", { style: {
547
+ position: "absolute",
548
+ inset: OVERLAY_OFFSETS.blockHover,
549
+ border: `${OVERLAY_WIDTHS.hover} solid ${colors.hover}`,
550
+ pointerEvents: "none",
551
+ zIndex: 10
552
+ } })]
553
+ });
554
+ };
555
+ const RepeaterHoverProvider = ({ blockId, fieldName, children }) => {
556
+ const isContentEditable = useIsEditable("site");
557
+ const { window: iframeWindow } = useFrame();
558
+ const isHovered = useOverlayMessage(iframeWindow, isContentEditable, "CAMOX_HOVER_REPEATER", "CAMOX_HOVER_REPEATER_END", {
559
+ blockId,
560
+ fieldName
561
+ });
562
+ return /* @__PURE__ */ jsx(RepeaterHoverContext.Provider, {
563
+ value: isHovered,
564
+ children
565
+ });
566
+ };
567
+ const Repeater = ({ name, children }) => {
568
+ const blockContext = React.use(Context);
569
+ if (!blockContext) throw new Error("Repeater must be used within a Block Component");
570
+ const { blockId, content, mode } = blockContext;
571
+ const parentRepeaterContext = React.use(RepeaterItemContext);
572
+ const fieldName = String(name);
573
+ const itemComponents = {
574
+ Field,
575
+ Link,
576
+ Embed,
577
+ Image,
578
+ File,
579
+ Repeater
580
+ };
581
+ if (parentRepeaterContext) {
582
+ const nestedArray = parentRepeaterContext.itemContent[name] ?? [];
583
+ if (!Array.isArray(nestedArray)) throw new Error(`Field "${String(name)}" is not an array`);
584
+ const parentItemId = parentRepeaterContext.itemId;
585
+ return /* @__PURE__ */ jsx(RepeaterHoverProvider, {
586
+ blockId,
587
+ fieldName,
588
+ children: nestedArray.map((item, index) => {
589
+ const nestedItemId = `nested:${parentItemId}:${fieldName}:${index}`;
590
+ return /* @__PURE__ */ jsx(RepeaterItemContext.Provider, {
591
+ value: {
592
+ arrayFieldName: fieldName,
593
+ itemIndex: index,
594
+ itemContent: {
595
+ ...repeatableItemDefaults[fieldName],
596
+ ...item
597
+ },
598
+ nested: {
599
+ parentItemId,
600
+ parentContent: parentRepeaterContext.itemContent,
601
+ parentArrayFieldName: parentRepeaterContext.arrayFieldName,
602
+ nestedFieldName: fieldName,
603
+ nestedIndex: index
604
+ }
605
+ },
606
+ children: /* @__PURE__ */ jsx(RepeaterItemWrapper, {
607
+ itemId: nestedItemId,
608
+ blockId,
609
+ mode,
610
+ children: children(itemComponents, index)
611
+ })
612
+ }, index);
613
+ })
614
+ });
615
+ }
616
+ const arrayValue = content[name] ?? [];
617
+ if (!Array.isArray(arrayValue)) throw new Error(`Field "${String(name)}" is not an array`);
618
+ return /* @__PURE__ */ jsx(RepeaterHoverProvider, {
619
+ blockId,
620
+ fieldName,
621
+ children: arrayValue.map((item, index) => {
622
+ const itemContent = {
623
+ ...repeatableItemDefaults[fieldName],
624
+ ...item.content
625
+ };
626
+ const itemId = item._id;
627
+ return /* @__PURE__ */ jsx(RepeaterItemContext.Provider, {
628
+ value: {
629
+ arrayFieldName: fieldName,
630
+ itemIndex: index,
631
+ itemContent,
632
+ itemId
633
+ },
634
+ children: /* @__PURE__ */ jsx(RepeaterItemWrapper, {
635
+ itemId,
636
+ blockId,
637
+ mode,
638
+ children: children(itemComponents, index)
639
+ })
640
+ }, itemId || index);
641
+ })
642
+ });
643
+ };
644
+ const BlockComponent = ({ blockData, mode, isFirstBlock, showAddBlockTop, showAddBlockBottom, addBlockAfterPosition }) => {
645
+ const isContentEditable = useIsEditable(mode);
646
+ const { window: iframeWindow } = useFrame();
647
+ const [isHovered, setIsHovered] = React.useState(false);
648
+ const selectionBreadcrumbs = useSelector(previewStore, (state) => state.context.selectionBreadcrumbs);
649
+ const isPageContentSheetOpen = useSelector(previewStore, (state) => state.context.isPageContentSheetOpen);
650
+ const isAddBlockSheetOpen = useSelector(previewStore, (state) => state.context.isAddBlockSheetOpen);
651
+ const isAnySideSheetOpen = useIsPreviewSheetOpen();
652
+ const isBlockSelected = (selectionBreadcrumbs[0]?.id ?? null) === blockData._id;
653
+ const ref = React.useRef(null);
654
+ const [isFirstRender, setIsFirstRender] = React.useState(true);
655
+ React.useEffect(() => {
656
+ if (isFirstRender) setIsFirstRender(false);
657
+ }, [isFirstRender]);
658
+ React.useEffect(() => {
659
+ if (isBlockSelected && ref.current) ref.current.scrollIntoView({
660
+ behavior: isFirstRender ? "instant" : "smooth",
661
+ block: isFirstRender ? "start" : "nearest"
662
+ });
663
+ }, [
664
+ isBlockSelected,
665
+ isFirstRender,
666
+ isPageContentSheetOpen
667
+ ]);
668
+ const isHoveredFromSidebar = useOverlayMessage(iframeWindow, isContentEditable, "CAMOX_HOVER_BLOCK", "CAMOX_HOVER_BLOCK_END", { blockId: blockData._id });
669
+ React.useEffect(() => {
670
+ setIsHovered(isHoveredFromSidebar);
671
+ }, [isHoveredFromSidebar]);
672
+ const normalizedContent = React.useMemo(() => {
673
+ const result = { ...blockData.content };
674
+ for (const key in result) {
675
+ const value = result[key];
676
+ if (Array.isArray(value) && value.length > 0 && value[0]?.content !== void 0) result[key] = value.map((item) => item.content);
677
+ }
678
+ return result;
679
+ }, [blockData.content]);
680
+ const handleClick = (e) => {
681
+ if (!isContentEditable) return;
682
+ if (e.target.closest("[data-camox-field-id]")) return;
683
+ previewStore.send({
684
+ type: "setFocusedBlock",
685
+ blockId: blockData._id
686
+ });
687
+ };
688
+ const handleMouseEnter = () => {
689
+ if (isContentEditable) setIsHovered(true);
690
+ };
691
+ const handleMouseLeave = () => {
692
+ if (isContentEditable) setIsHovered(false);
693
+ };
694
+ const handleAddBlockClick = (insertPosition) => {
695
+ postOverlayMessage({
696
+ type: "CAMOX_ADD_BLOCK_REQUEST",
697
+ blockPosition: blockData.position,
698
+ insertPosition,
699
+ ...addBlockAfterPosition !== void 0 && { afterPosition: addBlockAfterPosition }
700
+ });
701
+ };
702
+ const shouldShowOverlay = isContentEditable && (isHovered || isBlockSelected) && !isAddBlockSheetOpen;
703
+ const shouldShowSheetOverlay = isAddBlockSheetOpen && mode !== "peek" || isPageContentSheetOpen && !isBlockSelected;
704
+ return /* @__PURE__ */ jsxs("div", {
705
+ className: "group visual-editing-block",
706
+ ref,
707
+ style: {
708
+ position: "relative",
709
+ scrollMargin: "5rem",
710
+ background: "var(--background)"
711
+ },
712
+ "data-camox-block-id": isContentEditable ? blockData._id : void 0,
713
+ onClick: handleClick,
714
+ onMouseEnter: handleMouseEnter,
715
+ onMouseLeave: handleMouseLeave,
716
+ children: [
717
+ /* @__PURE__ */ jsx(Context.Provider, {
718
+ value: {
719
+ blockId: blockData._id,
720
+ content: (() => {
721
+ const merged = {
722
+ ...contentDefaults,
723
+ ...blockData.content
724
+ };
725
+ const overrides = {};
726
+ for (const key in merged) {
727
+ const val = merged[key];
728
+ if (val && typeof val === "object" && "url" in val && !val.url && contentDefaults[key]) overrides[key] = contentDefaults[key];
729
+ }
730
+ return {
731
+ ...merged,
732
+ ...overrides
733
+ };
734
+ })(),
735
+ settings: {
736
+ ...settingsDefaults,
737
+ ...blockData.settings
738
+ },
739
+ mode,
740
+ isHovered,
741
+ setIsHovered
742
+ },
743
+ children: /* @__PURE__ */ jsx(options.component, { content: normalizedContent })
744
+ }),
745
+ /* @__PURE__ */ jsx("div", {
746
+ style: {
747
+ position: "absolute",
748
+ width: "100%",
749
+ height: "100%",
750
+ top: 0,
751
+ left: 0,
752
+ background: "#000",
753
+ opacity: shouldShowSheetOverlay ? .6 : 0,
754
+ transition: "opacity 0.3s ease-in-out",
755
+ pointerEvents: "none",
756
+ zIndex: 20
757
+ },
758
+ id: "hello"
759
+ }),
760
+ shouldShowOverlay && (() => {
761
+ const colors = mode === "layout" ? LAYOUT_OVERLAY_COLORS : OVERLAY_COLORS;
762
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", { style: {
763
+ position: "absolute",
764
+ inset: isBlockSelected ? OVERLAY_OFFSETS.blockSelected : OVERLAY_OFFSETS.blockHover,
765
+ border: `${isBlockSelected ? OVERLAY_WIDTHS.selected : OVERLAY_WIDTHS.hover} solid ${isBlockSelected ? colors.selected : colors.hover}`,
766
+ pointerEvents: "none",
767
+ zIndex: 10
768
+ } }), /* @__PURE__ */ jsxs(Fragment, { children: [(showAddBlockTop ?? (mode !== "layout" && !isFirstBlock)) && /* @__PURE__ */ jsx(AddBlockControlBar, {
769
+ position: "top",
770
+ hidden: isAnySideSheetOpen,
771
+ onMouseLeave: () => setIsHovered(false),
772
+ onClick: () => handleAddBlockClick("before")
773
+ }), (showAddBlockBottom ?? mode !== "layout") && /* @__PURE__ */ jsx(AddBlockControlBar, {
774
+ position: "bottom",
775
+ hidden: isAnySideSheetOpen,
776
+ onMouseLeave: () => setIsHovered(false),
777
+ onClick: () => handleAddBlockClick("after")
778
+ })] })] });
779
+ })()
780
+ ]
781
+ });
782
+ };
783
+ const useSetting = (name) => {
784
+ const ctx = React.use(Context);
785
+ if (!ctx) throw new Error("useSetting must be used within a Block Component");
786
+ return ctx.settings[name];
787
+ };
788
+ /**
789
+ * Wraps block content that renders outside the block's visual bounds (fixed navbars, modals, portals, etc.).
790
+ * Provides the same hover, selection, and sheet overlays as the main BlockComponent.
791
+ */
792
+ const Detached = ({ children }) => {
793
+ const ctx = React.use(Context);
794
+ if (!ctx) throw new Error("Detached must be used within a Block Component");
795
+ const { blockId, mode, isHovered, setIsHovered } = ctx;
796
+ const isContentEditable = useIsEditable(mode);
797
+ const { window: iframeWindow } = useFrame();
798
+ const selectionBreadcrumbs = useSelector(previewStore, (state) => state.context.selectionBreadcrumbs);
799
+ const isAddBlockSheetOpen = useSelector(previewStore, (state) => state.context.isAddBlockSheetOpen);
800
+ const isPageContentSheetOpen = useSelector(previewStore, (state) => state.context.isPageContentSheetOpen);
801
+ const isBlockSelected = (selectionBreadcrumbs[0]?.id ?? null) === blockId;
802
+ const isHoveredFromSidebar = useOverlayMessage(iframeWindow, isContentEditable, "CAMOX_HOVER_BLOCK", "CAMOX_HOVER_BLOCK_END", { blockId });
803
+ React.useEffect(() => {
804
+ setIsHovered(isHoveredFromSidebar);
805
+ }, [isHoveredFromSidebar, setIsHovered]);
806
+ const shouldShowOverlay = isContentEditable && (isHovered || isBlockSelected) && !isAddBlockSheetOpen;
807
+ const shouldShowSheetOverlay = isAddBlockSheetOpen && mode !== "peek" || isPageContentSheetOpen && !isBlockSelected;
808
+ const handleClick = (e) => {
809
+ if (!isContentEditable) return;
810
+ e.stopPropagation();
811
+ previewStore.send({
812
+ type: "setFocusedBlock",
813
+ blockId
814
+ });
815
+ };
816
+ const handleMouseEnter = () => {
817
+ if (isContentEditable) setIsHovered(true);
818
+ };
819
+ const handleMouseLeave = () => {
820
+ if (isContentEditable) setIsHovered(false);
821
+ };
822
+ const [container, setContainer] = React.useState(null);
823
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Slot, {
824
+ ref: setContainer,
825
+ onClick: handleClick,
826
+ onMouseEnter: handleMouseEnter,
827
+ onMouseLeave: handleMouseLeave,
828
+ children
829
+ }), container && createPortal(/* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", { style: {
830
+ position: "absolute",
831
+ inset: 0,
832
+ background: "#000",
833
+ opacity: shouldShowSheetOverlay ? .6 : 0,
834
+ transition: "opacity 0.3s ease-in-out",
835
+ pointerEvents: "none",
836
+ zIndex: 20
837
+ } }), shouldShowOverlay && (() => {
838
+ const colors = mode === "layout" ? LAYOUT_OVERLAY_COLORS : OVERLAY_COLORS;
839
+ return /* @__PURE__ */ jsx("div", { style: {
840
+ position: "absolute",
841
+ inset: isBlockSelected ? OVERLAY_OFFSETS.blockSelected : OVERLAY_OFFSETS.blockHover,
842
+ border: `${isBlockSelected ? OVERLAY_WIDTHS.selected : OVERLAY_WIDTHS.hover} solid ${isBlockSelected ? colors.selected : colors.hover}`,
843
+ pointerEvents: "none",
844
+ zIndex: 10
845
+ } });
846
+ })()] }), container)] });
847
+ };
848
+ return {
849
+ Component: BlockComponent,
850
+ Detached,
851
+ Field,
852
+ Embed,
853
+ Link,
854
+ Image,
855
+ File,
856
+ Repeater,
857
+ useSetting,
858
+ id: options.id,
859
+ title: options.title,
860
+ description: options.description,
861
+ contentSchema,
862
+ settingsSchema,
863
+ getInitialContent: () => {
864
+ return { ...contentDefaultsForStorage };
865
+ },
866
+ getInitialSettings: () => {
867
+ return { ...settingsDefaults };
868
+ },
869
+ layoutOnly: options.layoutOnly ?? false
870
+ };
871
+ }
872
+ //#endregion
873
+ export { Type, createBlock };