@delmaredigital/payload-puck 0.6.24 → 0.6.26

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.
@@ -1,4 +1,5 @@
1
1
  import type { Config as PuckConfig, Data, Plugin as PuckPlugin } from '@puckeditor/core';
2
+ import type { RootPropsMapping } from '../api/types.js';
2
3
  /**
3
4
  * Props for the PuckEditorView component
4
5
  */
@@ -52,6 +53,14 @@ export interface PuckEditorViewProps {
52
53
  * Callback on save error
53
54
  */
54
55
  onSaveError?: (error: Error) => void;
56
+ /**
57
+ * Custom root.props ↔ Payload field mappings, merged with the defaults.
58
+ *
59
+ * Must match the `rootPropsMapping` passed to the API routes / plugin so the
60
+ * editor hydrates the same fields it persists on save — otherwise
61
+ * custom-mapped fields appear to revert after publish.
62
+ */
63
+ rootPropsMapping?: RootPropsMapping[];
55
64
  }
56
65
  /**
57
66
  * Ready-to-use Puck editor page component
@@ -80,5 +89,5 @@ export interface PuckEditorViewProps {
80
89
  * }
81
90
  * ```
82
91
  */
83
- export declare function PuckEditorView({ config, collectionSlug, apiBasePath, backUrl, previewUrl, layoutStyles, layoutKey, plugins, onSaveSuccess, onSaveError, }: PuckEditorViewProps): import("react").JSX.Element;
92
+ export declare function PuckEditorView({ config, collectionSlug, apiBasePath, backUrl, previewUrl, layoutStyles, layoutKey, plugins, onSaveSuccess, onSaveError, rootPropsMapping, }: PuckEditorViewProps): import("react").JSX.Element;
84
93
  export default PuckEditorView;
@@ -29,7 +29,7 @@ import { mapPayloadFieldsToRootProps } from '../api/utils/mapRootProps.js';
29
29
  * )
30
30
  * }
31
31
  * ```
32
- */ export function PuckEditorView({ config, collectionSlug = 'pages', apiBasePath = '/api/puck', backUrl, previewUrl, layoutStyles, layoutKey = 'pageLayout', plugins, onSaveSuccess, onSaveError }) {
32
+ */ export function PuckEditorView({ config, collectionSlug = 'pages', apiBasePath = '/api/puck', backUrl, previewUrl, layoutStyles, layoutKey = 'pageLayout', plugins, onSaveSuccess, onSaveError, rootPropsMapping }) {
33
33
  const params = useParams();
34
34
  const searchParams = useSearchParams();
35
35
  // Get page ID from route params or search params
@@ -203,7 +203,7 @@ import { mapPayloadFieldsToRootProps } from '../api/utils/mapRootProps.js';
203
203
  // Hydrate root.props from Payload fields — ensures saved values like pageLayout,
204
204
  // isHomepage, conversion settings etc. are reflected in the editor UI even if
205
205
  // they weren't stored in puckData (Puck may strip props that match defaults)
206
- const payloadRootProps = mapPayloadFieldsToRootProps(page);
206
+ const payloadRootProps = mapPayloadFieldsToRootProps(page, rootPropsMapping);
207
207
  const initialData = {
208
208
  ...basePuckData,
209
209
  root: {
@@ -8,8 +8,15 @@
8
8
  * Payload's local API, so collection-level access rules are enforced.
9
9
  */
10
10
  import type { PayloadHandler } from 'payload';
11
+ import type { RootPropsMapping } from '../api/types.js';
11
12
  export interface PuckEndpointOptions {
12
13
  collections: string[];
14
+ /**
15
+ * Custom root.props → Payload field mappings, merged with the defaults.
16
+ * Lets fields edited via Puck root fields (e.g. conversionTracking) sync
17
+ * back to their Payload columns on save/publish.
18
+ */
19
+ rootPropsMapping?: RootPropsMapping[];
13
20
  }
14
21
  /**
15
22
  * GET /api/puck/:collection
@@ -9,6 +9,25 @@
9
9
  */ import { APIError } from 'payload';
10
10
  import { unsetHomepage, HomepageConflictError } from '../plugin/hooks/isHomepageUnique.js';
11
11
  import { resolveLocale } from '../utils/locale.js';
12
+ import { mapRootPropsToPayloadFields, deepMerge } from '../api/utils/mapRootProps.js';
13
+ /**
14
+ * Merge a Puck save payload's `data` with the Payload fields derived from its
15
+ * `puckData.root.props`.
16
+ *
17
+ * Mapped fields form the base; the explicitly-sent fields (puckData, title,
18
+ * slug, isHomepage, folder, pageSegment) take precedence. Without this, fields
19
+ * exposed as Puck root fields (e.g. conversionTracking, meta, pageLayout) are
20
+ * persisted only inside the puckData blob and never reach their Payload
21
+ * columns — so they appear to "revert" on publish. Mirrors the sync performed
22
+ * by createPuckApiRoutesWithId's PATCH handler.
23
+ */ function applyRootPropsMapping(data, rootPropsMapping) {
24
+ const rootProps = data?.puckData?.root?.props || {};
25
+ const mappedFields = mapRootPropsToPayloadFields(rootProps, rootPropsMapping);
26
+ const merged = {};
27
+ deepMerge(merged, mappedFields);
28
+ deepMerge(merged, data);
29
+ return merged;
30
+ }
12
31
  /**
13
32
  * GET /api/puck/:collection
14
33
  * List all documents in a Puck-enabled collection
@@ -51,7 +70,7 @@ import { resolveLocale } from '../utils/locale.js';
51
70
  * POST /api/puck/:collection
52
71
  * Create a new document in a Puck-enabled collection
53
72
  */ export function createCreateHandler(options) {
54
- const { collections } = options;
73
+ const { collections, rootPropsMapping } = options;
55
74
  return async (req)=>{
56
75
  try {
57
76
  const collection = req.routeParams?.collection;
@@ -65,11 +84,12 @@ import { resolveLocale } from '../utils/locale.js';
65
84
  const body = await req.json?.();
66
85
  const { _locale, ...data } = body || {};
67
86
  const locale = resolveLocale(req, _locale);
87
+ const createData = applyRootPropsMapping(data, rootPropsMapping);
68
88
  const doc = await req.payload.create({
69
89
  collection: collection,
70
90
  req,
71
91
  overrideAccess: false,
72
- data,
92
+ data: createData,
73
93
  draft: true,
74
94
  ...locale ? {
75
95
  locale
@@ -133,7 +153,7 @@ import { resolveLocale } from '../utils/locale.js';
133
153
  * PATCH /api/puck/:collection/:id
134
154
  * Update a document (supports draft saving, publishing, and homepage swapping)
135
155
  */ export function createUpdateHandler(options) {
136
- const { collections } = options;
156
+ const { collections, rootPropsMapping } = options;
137
157
  return async (req)=>{
138
158
  try {
139
159
  const collection = req.routeParams?.collection;
@@ -150,10 +170,17 @@ import { resolveLocale } from '../utils/locale.js';
150
170
  const locale = resolveLocale(req, _locale);
151
171
  // Determine if this is a publish or draft save
152
172
  const shouldPublish = _status === 'published';
173
+ // Sync Puck root.props → Payload fields (e.g. conversionTracking, meta,
174
+ // pageLayout) so values edited via Puck root fields persist to their
175
+ // columns on publish instead of living only inside the puckData blob.
176
+ const updateData = applyRootPropsMapping(data, rootPropsMapping);
177
+ updateData._status = shouldPublish ? 'published' : 'draft';
153
178
  // Handle homepage swap if requested
154
179
  // When swapHomepage is true and isHomepage is being set to true,
155
- // we need to unset the current homepage first
156
- if (swapHomepage && data.isHomepage === true) {
180
+ // we need to unset the current homepage first. Read the resolved value
181
+ // from updateData so this works whether isHomepage arrived as a top-level
182
+ // field or was mapped in from root.props.
183
+ if (swapHomepage && updateData.isHomepage === true) {
157
184
  // Find the current homepage
158
185
  const existingHomepage = await req.payload.find({
159
186
  collection: collection,
@@ -190,10 +217,7 @@ import { resolveLocale } from '../utils/locale.js';
190
217
  req,
191
218
  overrideAccess: false,
192
219
  id,
193
- data: {
194
- ...data,
195
- _status: shouldPublish ? 'published' : 'draft'
196
- },
220
+ data: updateData,
197
221
  draft: !shouldPublish,
198
222
  context: {
199
223
  // Skip the isHomepage hook if we've already handled the swap
@@ -105,7 +105,7 @@ import { createContextListHandler, createContextCreateHandler, createContextUpda
105
105
  * })
106
106
  * ```
107
107
  */ export function createPuckPlugin(options = {}) {
108
- const { pagesCollection = 'pages', autoGenerateCollection = true, admin: pluginAdminConfig = {}, enableAdminView = true, adminViewPath = '/puck-editor', enableEndpoints = true, pageTreeIntegration, editorStylesheet, editorStylesheetUrls = [], editorStylesheetCompiled, ai: aiConfig, previewUrl } = options;
108
+ const { pagesCollection = 'pages', autoGenerateCollection = true, admin: pluginAdminConfig = {}, enableAdminView = true, adminViewPath = '/puck-editor', enableEndpoints = true, pageTreeIntegration, editorStylesheet, editorStylesheetUrls = [], editorStylesheetCompiled, ai: aiConfig, previewUrl, rootPropsMapping } = options;
109
109
  const { addEditButton = true } = pluginAdminConfig;
110
110
  // Parse page-tree integration config
111
111
  // - undefined: auto-detect at runtime (null stored, view will check for pageSegment field)
@@ -264,7 +264,8 @@ import { createContextListHandler, createContextCreateHandler, createContextUpda
264
264
  pagesCollection
265
265
  ];
266
266
  const endpointOptions = {
267
- collections: puckCollections
267
+ collections: puckCollections,
268
+ rootPropsMapping
268
269
  };
269
270
  // Parameterized endpoint paths that should only exist once (with merged collections)
270
271
  const parameterizedPuckPaths = new Set([
@@ -411,6 +412,10 @@ import { createContextListHandler, createContextCreateHandler, createContextUpda
411
412
  pageTree: pageTreeConfig ?? incomingConfig.custom?.puck?.pageTree,
412
413
  editorStylesheets: editorStylesheets.length > 0 ? editorStylesheets : incomingConfig.custom?.puck?.editorStylesheets,
413
414
  previewUrl: previewUrl ?? incomingConfig.custom?.puck?.previewUrl,
415
+ // Custom root.props ↔ Payload field mappings. Stored so the editor
416
+ // load view can hydrate root.props from the same mappings the save
417
+ // endpoints use, keeping the round-trip symmetric for custom fields.
418
+ rootPropsMapping: rootPropsMapping ?? incomingConfig.custom?.puck?.rootPropsMapping,
414
419
  ai: aiConfig?.enabled ? {
415
420
  enabled: true,
416
421
  context: aiConfig.context,
@@ -3,6 +3,7 @@ import type { Config as PuckConfig, Data as PuckData } from '@puckeditor/core';
3
3
  import type { ThemeConfig } from '../theme/types.js';
4
4
  import type { LayoutDefinition } from '../layouts/types.js';
5
5
  import type { PuckPluginAiConfig } from '../ai/types.js';
6
+ import type { RootPropsMapping } from '../api/types.js';
6
7
  /**
7
8
  * Admin UI configuration for the Puck plugin
8
9
  */
@@ -96,6 +97,16 @@ export interface PuckPluginOptions {
96
97
  * @default true
97
98
  */
98
99
  enableEndpoints?: boolean;
100
+ /**
101
+ * Custom mappings from Puck `root.props` to Payload collection fields.
102
+ *
103
+ * Merged with the built-in defaults (title, slug, meta.*, pageLayout,
104
+ * conversionTracking.*, etc.). Applied in both directions: Payload field →
105
+ * root.prop when loading the editor, and root.prop → Payload field when
106
+ * saving/publishing, so values edited via Puck root fields persist to their
107
+ * columns instead of living only inside the puckData blob.
108
+ */
109
+ rootPropsMapping?: RootPropsMapping[];
99
110
  /**
100
111
  * Integration with @delmaredigital/payload-page-tree plugin
101
112
  *
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.6.24";
1
+ export declare const VERSION = "0.6.26";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by scripts/generate-version.js - do not edit manually
2
- export const VERSION = '0.6.24';
2
+ export const VERSION = '0.6.26';
@@ -63,6 +63,7 @@ import { mapPayloadFieldsToRootProps } from '../api/utils/mapRootProps.js';
63
63
  const aiConfig = payload.config.custom?.puck?.ai;
64
64
  const editorStylesheets = payload.config.custom?.puck?.editorStylesheets;
65
65
  const previewUrlConfig = payload.config.custom?.puck?.previewUrl;
66
+ const rootPropsMapping = payload.config.custom?.puck?.rootPropsMapping;
66
67
  // Fetch the page data
67
68
  // Use depth: 1 if previewUrl is a function (may need relationship data like organization)
68
69
  let page = null;
@@ -163,8 +164,10 @@ import { mapPayloadFieldsToRootProps } from '../api/utils/mapRootProps.js';
163
164
  };
164
165
  if (page) {
165
166
  // Map Payload document fields to root.props format
166
- // This ensures fields like title, slug, isHomepage, pageLayout are synced
167
- const syncedRootProps = mapPayloadFieldsToRootProps(page);
167
+ // This ensures fields like title, slug, isHomepage, pageLayout are synced.
168
+ // Pass the configured custom mappings so the load direction mirrors the
169
+ // save endpoints (otherwise custom-mapped fields would appear to revert).
170
+ const syncedRootProps = mapPayloadFieldsToRootProps(page, rootPropsMapping);
168
171
  // Handle folder ID specially (could be object or string)
169
172
  if (pageTreeConfig && page.folder !== undefined) {
170
173
  const folderId = typeof page.folder === 'object' ? page.folder?.id : page.folder;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delmaredigital/payload-puck",
3
- "version": "0.6.24",
3
+ "version": "0.6.26",
4
4
  "description": "Puck visual page builder plugin for Payload CMS",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -152,9 +152,9 @@
152
152
  }
153
153
  },
154
154
  "dependencies": {
155
- "@puckeditor/cloud-client": "^0.6.0",
156
- "@puckeditor/plugin-ai": "^0.6.0",
157
- "@puckeditor/plugin-heading-analyzer": "^0.21.1",
155
+ "@puckeditor/cloud-client": "^0.7.0",
156
+ "@puckeditor/plugin-ai": "^0.7.0",
157
+ "@puckeditor/plugin-heading-analyzer": "^0.21.2",
158
158
  "@radix-ui/react-popover": "^1.1.15",
159
159
  "@tiptap/core": "^3.20.1",
160
160
  "@tiptap/extension-color": "^3.20.1",
@@ -171,9 +171,9 @@
171
171
  "lucide-react": "^0.469.0"
172
172
  },
173
173
  "devDependencies": {
174
- "@payloadcms/next": "^3.79.0",
175
- "@payloadcms/ui": "^3.79.0",
176
- "@puckeditor/core": "^0.21.1",
174
+ "@payloadcms/next": "^3.84.1",
175
+ "@payloadcms/ui": "^3.84.1",
176
+ "@puckeditor/core": "^0.21.2",
177
177
  "@swc/cli": "^0.6.0",
178
178
  "@swc/core": "^1.15.18",
179
179
  "@types/node": "^24.12.0",
@@ -181,7 +181,7 @@
181
181
  "@types/react-dom": "^19.2.3",
182
182
  "copyfiles": "^2.4.1",
183
183
  "next": "^16.2.6",
184
- "payload": "^3.79.0",
184
+ "payload": "^3.84.1",
185
185
  "react": "^19.2.4",
186
186
  "react-dom": "^19.2.4",
187
187
  "typescript": "^5.9.3"