camox 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/core/components/lexical/SidebarLexicalEditor.js +2 -1
  2. package/dist/core/createApp.d.ts +231 -209
  3. package/dist/core/createApp.js +17 -17
  4. package/dist/core/createBlock.d.ts +74 -72
  5. package/dist/core/createBlock.js +274 -267
  6. package/dist/core/createLayout.d.ts +100 -80
  7. package/dist/core/createLayout.js +93 -65
  8. package/dist/features/preview/CamoxPreview.js +76 -54
  9. package/dist/features/preview/components/AddBlockSheet.js +12 -12
  10. package/dist/features/preview/components/AssetFieldEditor.js +1 -1
  11. package/dist/features/preview/components/AssetLightbox.js +1 -1
  12. package/dist/features/preview/components/AssetPickerGrid.js +1 -1
  13. package/dist/features/preview/components/BlockActionsPopover.js +26 -26
  14. package/dist/features/preview/components/BlockErrorBoundary.js +59 -0
  15. package/dist/features/preview/components/{CreatePageSheet.js → CreatePageModal.js} +16 -18
  16. package/dist/features/preview/components/{EditPageSheet.js → EditPageModal.js} +32 -25
  17. package/dist/features/preview/components/Frame.js +1 -1
  18. package/dist/features/preview/components/ItemFieldsEditor.js +134 -98
  19. package/dist/features/preview/components/LinkFieldEditor.js +166 -146
  20. package/dist/features/preview/components/PageContentSheet.js +42 -37
  21. package/dist/features/preview/components/PageLocationFieldset.js +28 -26
  22. package/dist/features/preview/components/PagePicker.js +15 -8
  23. package/dist/features/preview/components/PageTree.js +337 -351
  24. package/dist/features/preview/components/PeekedBlock.js +38 -26
  25. package/dist/features/preview/components/PreviewPanel.js +16 -2
  26. package/dist/features/preview/components/PreviewSideSheet.js +26 -42
  27. package/dist/features/preview/components/RepeatableItemsList.js +7 -7
  28. package/dist/features/preview/previewStore.js +7 -7
  29. package/dist/features/provider/CamoxProvider.js +41 -9
  30. package/dist/features/routes/ogRoute.js +2 -2
  31. package/dist/features/routes/pageRoute.js +1 -1
  32. package/dist/features/studio/components/EnvironmentMenu.js +2 -2
  33. package/dist/features/studio/components/UserButton.js +49 -34
  34. package/dist/features/vite/blockBoilerplate.js +2 -1
  35. package/dist/features/vite/definitionsSync.js +53 -22
  36. package/dist/features/vite/routeGeneration.js +1 -0
  37. package/dist/features/vite/vite.js +51 -7
  38. package/dist/lib/auth.js +6 -4
  39. package/dist/lib/use-project-room.js +25 -13
  40. package/dist/studio-overlays.css +34 -0
  41. package/dist/studio.css +1 -1
  42. package/package.json +4 -4
  43. package/skills/camox-layout/SKILL.md +34 -30
@@ -8,45 +8,56 @@ interface OgImageParams {
8
8
  projectName: string;
9
9
  }
10
10
  interface LayoutBlockData {
11
- _id: string;
11
+ _id: number;
12
12
  type: string;
13
13
  content: Record<string, unknown>;
14
14
  settings?: Record<string, unknown>;
15
15
  position: string;
16
16
  }
17
17
  /** Minimal block interface — avoids importing the full generic Block type. */
18
- interface LayoutBlock {
19
- id: string;
20
- Component: React.ComponentType<{
21
- blockData: any;
22
- mode: "site" | "peek" | "layout";
23
- isFirstBlock?: boolean;
24
- showAddBlockTop?: boolean;
25
- showAddBlockBottom?: boolean;
26
- addBlockAfterPosition?: string | null;
27
- }>;
28
- getInitialBundle: () => {
29
- content: Record<string, unknown>;
30
- settings: Record<string, unknown>;
31
- repeatableItems: Array<{
32
- tempId: string;
33
- parentTempId: string | null;
34
- fieldName: string;
35
- content: Record<string, unknown>;
36
- position: string;
18
+ interface LayoutBlock<TLayoutOnly extends boolean = boolean> {
19
+ _internal: {
20
+ id: string;
21
+ layoutOnly: TLayoutOnly;
22
+ Component: React.ComponentType<{
23
+ blockData: any;
24
+ mode: "site" | "peek" | "layout";
25
+ isFirstBlock?: boolean;
26
+ showAddBlockTop?: boolean;
27
+ showAddBlockBottom?: boolean;
28
+ addBlockAfterPosition?: string | null;
37
29
  }>;
30
+ getInitialBundle: () => {
31
+ content: Record<string, unknown>;
32
+ settings: Record<string, unknown>;
33
+ repeatableItems: Array<{
34
+ tempId: string;
35
+ parentTempId: string | null;
36
+ fieldName: string;
37
+ content: Record<string, unknown>;
38
+ position: string;
39
+ }>;
40
+ };
38
41
  };
39
42
  }
40
- interface CreateLayoutOptions {
43
+ /**
44
+ * Per-element validators that produce a human-readable error string when a block
45
+ * is in the wrong slot. We use mapped types (instead of `LayoutBlock<true>[]` /
46
+ * `LayoutBlock<false>[]`) so TypeScript reports
47
+ * `Type 'X' is not assignable to type '❌ Camox: ...'`
48
+ * instead of the unreadable structural diff on the full Block shape.
49
+ */
50
+ type ValidateLayoutOnlyBlocks<T extends readonly LayoutBlock[]> = { [K in keyof T]: T[K] extends LayoutBlock<true> ? T[K] : "❌ Camox: blocks in `blocks.before` and `blocks.after` must be defined with `layoutOnly: true`. Add `layoutOnly: true` to this block's `createBlock` options." };
51
+ type ValidatePageContentBlocks<T extends readonly LayoutBlock[]> = { [K in keyof T]: T[K] extends LayoutBlock<true> ? "❌ Camox: blocks in `blocks.initial` must NOT be `layoutOnly: true` — `initial` is for page-content blocks. Remove `layoutOnly: true` from this block's `createBlock` options." : T[K] };
52
+ interface CreateLayoutOptions<TBefore extends readonly LayoutBlock[], TAfter extends readonly LayoutBlock[], TInitial extends readonly LayoutBlock[]> {
41
53
  id: string;
42
54
  title: string;
43
55
  description: string;
44
56
  blocks: {
45
- before: LayoutBlock[];
46
- after: LayoutBlock[];
57
+ before: ValidateLayoutOnlyBlocks<TBefore>;
58
+ after: ValidateLayoutOnlyBlocks<TAfter>; /** Ordered list of blocks to create on the initial page when a project is first set up. */
59
+ initial?: ValidatePageContentBlocks<TInitial>;
47
60
  };
48
- /** Ordered list of blocks to create on the initial page when a project is first set up. */
49
- initialBlocks?: LayoutBlock[];
50
61
  component: React.ComponentType<{
51
62
  children: React.ReactNode;
52
63
  }>;
@@ -57,64 +68,73 @@ interface CreateLayoutOptions {
57
68
  }) => string;
58
69
  buildOgImage?: (params: OgImageParams) => React.ReactElement;
59
70
  }
60
- declare function createLayout(options: CreateLayoutOptions): {
61
- id: string;
62
- title: string;
63
- description: string;
64
- buildMetaTitle: (params: {
65
- pageMetaTitle: string;
66
- projectName: string;
67
- pageFullPath: string;
68
- }) => string;
69
- buildOgImage: ((params: OgImageParams) => Promise<Response>) | undefined;
70
- blockDefinitions: ({
71
- type: string;
72
- content: Record<string, unknown>;
73
- settings: Record<string, unknown>;
74
- repeatableItems: {
75
- tempId: string;
76
- parentTempId: string | null;
77
- fieldName: string;
71
+ declare function createLayout<const TBefore extends readonly LayoutBlock[], const TAfter extends readonly LayoutBlock[], const TInitial extends readonly LayoutBlock[] = []>(options: CreateLayoutOptions<TBefore, TAfter, TInitial>): {
72
+ BeforeBlocks: {
73
+ (): _$react_jsx_runtime0.JSX.Element;
74
+ displayName: string;
75
+ };
76
+ AfterBlocks: {
77
+ (): _$react_jsx_runtime0.JSX.Element;
78
+ displayName: string;
79
+ };
80
+ _internal: {
81
+ id: string;
82
+ title: string;
83
+ description: string;
84
+ buildMetaTitle: (params: {
85
+ pageMetaTitle: string;
86
+ projectName: string;
87
+ pageFullPath: string;
88
+ }) => string;
89
+ buildOgImage: ((params: OgImageParams) => Promise<Response>) | undefined;
90
+ blockDefinitions: ({
91
+ type: string;
78
92
  content: Record<string, unknown>;
79
- position: string;
80
- }[];
81
- placement: "before";
82
- } | {
83
- type: string;
84
- content: Record<string, unknown>;
85
- settings: Record<string, unknown>;
86
- repeatableItems: {
87
- tempId: string;
88
- parentTempId: string | null;
89
- fieldName: string;
93
+ settings: Record<string, unknown>;
94
+ repeatableItems: {
95
+ tempId: string;
96
+ parentTempId: string | null;
97
+ fieldName: string;
98
+ content: Record<string, unknown>;
99
+ position: string;
100
+ }[];
101
+ placement: "before";
102
+ } | {
103
+ type: string;
90
104
  content: Record<string, unknown>;
91
- position: string;
92
- }[];
93
- placement: "after";
94
- })[];
95
- initialBlockBundles: {
96
- type: string;
97
- content: Record<string, unknown>;
98
- settings: Record<string, unknown>;
99
- repeatableItems: {
100
- tempId: string;
101
- parentTempId: string | null;
102
- fieldName: string;
105
+ settings: Record<string, unknown>;
106
+ repeatableItems: {
107
+ tempId: string;
108
+ parentTempId: string | null;
109
+ fieldName: string;
110
+ content: Record<string, unknown>;
111
+ position: string;
112
+ }[];
113
+ placement: "after";
114
+ })[];
115
+ initialBlockBundles: {
116
+ type: string;
103
117
  content: Record<string, unknown>;
104
- position: string;
105
- }[];
106
- }[] | undefined;
107
- component: React.ComponentType<{
108
- children: React.ReactNode;
109
- }>;
110
- Provider: ({
111
- layoutBlocks,
112
- children
113
- }: {
114
- layoutBlocks: Record<string, LayoutBlockData>;
115
- children: React.ReactNode;
116
- }) => _$react_jsx_runtime0.JSX.Element;
117
- blocks: Record<string, React.ComponentType>;
118
+ settings: Record<string, unknown>;
119
+ repeatableItems: {
120
+ tempId: string;
121
+ parentTempId: string | null;
122
+ fieldName: string;
123
+ content: Record<string, unknown>;
124
+ position: string;
125
+ }[];
126
+ }[] | undefined;
127
+ component: React.ComponentType<{
128
+ children: React.ReactNode;
129
+ }>;
130
+ Provider: ({
131
+ layoutBlocks,
132
+ children
133
+ }: {
134
+ layoutBlocks: Record<string, LayoutBlockData>;
135
+ children: React.ReactNode;
136
+ }) => _$react_jsx_runtime0.JSX.Element;
137
+ };
118
138
  };
119
139
  type Layout = ReturnType<typeof createLayout>;
120
140
  //#endregion
@@ -1,55 +1,80 @@
1
+ import { BlockErrorBoundary } from "../features/preview/components/BlockErrorBoundary.js";
1
2
  import { c } from "react/compiler-runtime";
2
3
  import * as React from "react";
3
- import { jsx } from "react/jsx-runtime";
4
+ import { Fragment, jsx } from "react/jsx-runtime";
4
5
 
5
6
  //#region src/core/createLayout.tsx
6
- function toPascalCase(str) {
7
- return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
8
- }
9
7
  function createLayout(options) {
10
8
  const LayoutContext = React.createContext(null);
11
- const allBlocks = [...options.blocks.before, ...options.blocks.after];
12
- const slotComponents = {};
13
- const lastBeforeBlock = options.blocks.before[options.blocks.before.length - 1];
14
- const firstAfterBlock = options.blocks.after[0];
15
- for (const block of allBlocks) {
16
- const isLastBefore = block === lastBeforeBlock;
17
- const isFirstAfter = block === firstAfterBlock;
18
- const SlotComponent = () => {
19
- const $ = c(2);
20
- const ctx = React.use(LayoutContext);
21
- if (!ctx) throw new Error(`Layout slot "${block.id}" must be rendered inside a LayoutContextProvider`);
22
- const blockData = ctx.layoutBlocks[block.id];
23
- if (!blockData) return null;
24
- let t0;
25
- bb0: {
26
- if (isLastBefore) {
27
- t0 = "";
28
- break bb0;
29
- }
30
- if (isFirstAfter) {
31
- t0 = null;
32
- break bb0;
33
- }
34
- t0 = void 0;
35
- }
36
- let t1;
37
- if ($[0] !== blockData) {
38
- t1 = /* @__PURE__ */ jsx(block.Component, {
39
- blockData,
40
- mode: "layout",
41
- showAddBlockTop: isFirstAfter || void 0,
42
- showAddBlockBottom: isLastBefore || void 0,
43
- addBlockAfterPosition: t0
44
- });
45
- $[0] = blockData;
46
- $[1] = t1;
47
- } else t1 = $[1];
48
- return t1;
49
- };
50
- SlotComponent.displayName = `LayoutSlot(${toPascalCase(block.id)})`;
51
- slotComponents[toPascalCase(block.id)] = SlotComponent;
52
- }
9
+ const beforeBlocks = options.blocks.before;
10
+ const afterBlocks = options.blocks.after;
11
+ const initialBlocks = options.blocks.initial;
12
+ const BeforeBlocks = () => {
13
+ const $ = c(4);
14
+ const ctx = React.use(LayoutContext);
15
+ if (!ctx) throw new Error(`Layout "${options.id}" BeforeBlocks must be rendered inside its Provider`);
16
+ let t0;
17
+ if ($[0] !== ctx) {
18
+ t0 = beforeBlocks.map((block, i) => {
19
+ const blockData = ctx.layoutBlocks[block._internal.id];
20
+ if (!blockData) return null;
21
+ const isLastBefore = i === beforeBlocks.length - 1;
22
+ return /* @__PURE__ */ jsx(BlockErrorBoundary, {
23
+ blockId: blockData._id,
24
+ blockType: blockData.type,
25
+ children: /* @__PURE__ */ jsx(block._internal.Component, {
26
+ blockData,
27
+ mode: "layout",
28
+ showAddBlockBottom: isLastBefore || void 0,
29
+ addBlockAfterPosition: isLastBefore ? "" : void 0
30
+ })
31
+ }, block._internal.id);
32
+ });
33
+ $[0] = ctx;
34
+ $[1] = t0;
35
+ } else t0 = $[1];
36
+ let t1;
37
+ if ($[2] !== t0) {
38
+ t1 = /* @__PURE__ */ jsx(Fragment, { children: t0 });
39
+ $[2] = t0;
40
+ $[3] = t1;
41
+ } else t1 = $[3];
42
+ return t1;
43
+ };
44
+ BeforeBlocks.displayName = `LayoutBeforeBlocks(${options.id})`;
45
+ const AfterBlocks = () => {
46
+ const $ = c(4);
47
+ const ctx = React.use(LayoutContext);
48
+ if (!ctx) throw new Error(`Layout "${options.id}" AfterBlocks must be rendered inside its Provider`);
49
+ let t0;
50
+ if ($[0] !== ctx) {
51
+ t0 = afterBlocks.map((block, i) => {
52
+ const blockData = ctx.layoutBlocks[block._internal.id];
53
+ if (!blockData) return null;
54
+ const isFirstAfter = i === 0;
55
+ return /* @__PURE__ */ jsx(BlockErrorBoundary, {
56
+ blockId: blockData._id,
57
+ blockType: blockData.type,
58
+ children: /* @__PURE__ */ jsx(block._internal.Component, {
59
+ blockData,
60
+ mode: "layout",
61
+ showAddBlockTop: isFirstAfter || void 0,
62
+ addBlockAfterPosition: isFirstAfter ? null : void 0
63
+ })
64
+ }, block._internal.id);
65
+ });
66
+ $[0] = ctx;
67
+ $[1] = t0;
68
+ } else t0 = $[1];
69
+ let t1;
70
+ if ($[2] !== t0) {
71
+ t1 = /* @__PURE__ */ jsx(Fragment, { children: t0 });
72
+ $[2] = t0;
73
+ $[3] = t1;
74
+ } else t1 = $[3];
75
+ return t1;
76
+ };
77
+ AfterBlocks.displayName = `LayoutAfterBlocks(${options.id})`;
53
78
  const Provider = (t0) => {
54
79
  const $ = c(5);
55
80
  const { layoutBlocks, children } = t0;
@@ -72,19 +97,19 @@ function createLayout(options) {
72
97
  } else t2 = $[4];
73
98
  return t2;
74
99
  };
75
- const blockDefinitions = [...options.blocks.before.map((block) => {
76
- const bundle = block.getInitialBundle();
100
+ const blockDefinitions = [...beforeBlocks.map((block) => {
101
+ const bundle = block._internal.getInitialBundle();
77
102
  return {
78
- type: block.id,
103
+ type: block._internal.id,
79
104
  content: bundle.content,
80
105
  settings: bundle.settings,
81
106
  repeatableItems: bundle.repeatableItems,
82
107
  placement: "before"
83
108
  };
84
- }), ...options.blocks.after.map((block) => {
85
- const bundle = block.getInitialBundle();
109
+ }), ...afterBlocks.map((block) => {
110
+ const bundle = block._internal.getInitialBundle();
86
111
  return {
87
- type: block.id,
112
+ type: block._internal.id,
88
113
  content: bundle.content,
89
114
  settings: bundle.settings,
90
115
  repeatableItems: bundle.repeatableItems,
@@ -98,26 +123,29 @@ function createLayout(options) {
98
123
  height: 630
99
124
  });
100
125
  } : void 0;
101
- const initialBlockBundles = options.initialBlocks?.map((block) => {
102
- const bundle = block.getInitialBundle();
126
+ const initialBlockBundles = initialBlocks?.map((block) => {
127
+ const bundle = block._internal.getInitialBundle();
103
128
  return {
104
- type: block.id,
129
+ type: block._internal.id,
105
130
  content: bundle.content,
106
131
  settings: bundle.settings,
107
132
  repeatableItems: bundle.repeatableItems
108
133
  };
109
134
  });
110
135
  return {
111
- id: options.id,
112
- title: options.title,
113
- description: options.description,
114
- buildMetaTitle: options.buildMetaTitle,
115
- buildOgImage,
116
- blockDefinitions,
117
- initialBlockBundles,
118
- component: options.component,
119
- Provider,
120
- blocks: slotComponents
136
+ BeforeBlocks,
137
+ AfterBlocks,
138
+ _internal: {
139
+ id: options.id,
140
+ title: options.title,
141
+ description: options.description,
142
+ buildMetaTitle: options.buildMetaTitle,
143
+ buildOgImage,
144
+ blockDefinitions,
145
+ initialBlockBundles,
146
+ component: options.component,
147
+ Provider
148
+ }
121
149
  };
122
150
  }
123
151