@delmaredigital/payload-puck 0.6.25 → 0.6.27
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.
- package/dist/admin/PuckEditorView.d.ts +10 -1
- package/dist/admin/PuckEditorView.js +2 -2
- package/dist/ai/presets/componentAiDefaults.js +17 -0
- package/dist/components/exports.d.ts +2 -0
- package/dist/components/exports.js +2 -0
- package/dist/components/layout/Columns.d.ts +40 -0
- package/dist/components/layout/Columns.js +274 -0
- package/dist/components/layout/Columns.server.d.ts +48 -0
- package/dist/components/layout/Columns.server.js +157 -0
- package/dist/config/config.editor.d.ts +1 -0
- package/dist/config/config.editor.js +3 -0
- package/dist/config/index.js +3 -0
- package/dist/config/presets.d.ts +1 -0
- package/dist/config/presets.js +3 -1
- package/dist/endpoints/index.d.ts +7 -0
- package/dist/endpoints/index.js +33 -9
- package/dist/plugin/index.js +7 -2
- package/dist/types/index.d.ts +11 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/views/PuckEditorView.js +5 -2
- package/package.json +5 -5
|
@@ -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: {
|
|
@@ -153,6 +153,23 @@
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
},
|
|
156
|
+
Columns: {
|
|
157
|
+
ai: {
|
|
158
|
+
instructions: 'Use when content must be targeted into specific columns (each column is its own drop zone), e.g. a text column beside an image column. For content that just flows across equal cells, prefer Grid.'
|
|
159
|
+
},
|
|
160
|
+
fields: {
|
|
161
|
+
count: {
|
|
162
|
+
ai: {
|
|
163
|
+
instructions: 'Number of columns, 2-4.'
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
distribution: {
|
|
167
|
+
ai: {
|
|
168
|
+
instructions: "Column width split. 'equal' for even columns, or an fr ratio like '2:1' / '1:2' / '1:1:2' for asymmetric layouts (must match the column count)."
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
},
|
|
156
173
|
// Content Components
|
|
157
174
|
Card: {
|
|
158
175
|
ai: {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
export { ContainerConfig } from './layout/Container.js';
|
|
26
26
|
export { FlexConfig } from './layout/Flex.js';
|
|
27
27
|
export { GridConfig } from './layout/Grid.js';
|
|
28
|
+
export { ColumnsConfig } from './layout/Columns.js';
|
|
28
29
|
export { SectionConfig } from './layout/Section.js';
|
|
29
30
|
export { SpacerConfig } from './layout/Spacer.js';
|
|
30
31
|
export { TemplateConfig } from './layout/Template.js';
|
|
@@ -40,6 +41,7 @@ export { AccordionConfig } from './interactive/Accordion.js';
|
|
|
40
41
|
export { ContainerConfig as ContainerServerConfig } from './layout/Container.server.js';
|
|
41
42
|
export { FlexConfig as FlexServerConfig } from './layout/Flex.server.js';
|
|
42
43
|
export { GridConfig as GridServerConfig } from './layout/Grid.server.js';
|
|
44
|
+
export { ColumnsConfig as ColumnsServerConfig } from './layout/Columns.server.js';
|
|
43
45
|
export { SectionConfig as SectionServerConfig } from './layout/Section.server.js';
|
|
44
46
|
export { SpacerConfig as SpacerServerConfig } from './layout/Spacer.server.js';
|
|
45
47
|
export { TemplateServerConfig } from './layout/Template.server.js';
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
export { ContainerConfig } from './layout/Container.js';
|
|
30
30
|
export { FlexConfig } from './layout/Flex.js';
|
|
31
31
|
export { GridConfig } from './layout/Grid.js';
|
|
32
|
+
export { ColumnsConfig } from './layout/Columns.js';
|
|
32
33
|
export { SectionConfig } from './layout/Section.js';
|
|
33
34
|
export { SpacerConfig } from './layout/Spacer.js';
|
|
34
35
|
export { TemplateConfig } from './layout/Template.js';
|
|
@@ -53,6 +54,7 @@ export { AccordionConfig } from './interactive/Accordion.js';
|
|
|
53
54
|
export { ContainerConfig as ContainerServerConfig } from './layout/Container.server.js';
|
|
54
55
|
export { FlexConfig as FlexServerConfig } from './layout/Flex.server.js';
|
|
55
56
|
export { GridConfig as GridServerConfig } from './layout/Grid.server.js';
|
|
57
|
+
export { ColumnsConfig as ColumnsServerConfig } from './layout/Columns.server.js';
|
|
56
58
|
export { SectionConfig as SectionServerConfig } from './layout/Section.server.js';
|
|
57
59
|
export { SpacerConfig as SpacerServerConfig } from './layout/Spacer.server.js';
|
|
58
60
|
export { TemplateServerConfig } from './layout/Template.server.js';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Columns Component - Puck Configuration (editor)
|
|
3
|
+
*
|
|
4
|
+
* Unlike Grid (a single CSS-grid slot where children auto-place into cells),
|
|
5
|
+
* Columns exposes one independent drop zone PER column — components can be
|
|
6
|
+
* dropped into a specific column. Puck slots are static named fields, so a fixed
|
|
7
|
+
* maximum set of column slots (column1..columnN) is declared and only the first
|
|
8
|
+
* `count` are rendered.
|
|
9
|
+
*
|
|
10
|
+
* Responsive: stacks vertically on mobile, switches to a CSS grid at md+ (≥768px).
|
|
11
|
+
*
|
|
12
|
+
* Width distribution supports equal columns or fr ratios (e.g. 2:1, 1:2, 1:1:2)
|
|
13
|
+
* for asymmetric layouts.
|
|
14
|
+
*/
|
|
15
|
+
import type { ComponentConfig } from '@puckeditor/core';
|
|
16
|
+
import { type PaddingValue, type BorderValue, type DimensionsValue, type BackgroundValue, type AnimationValue, type ResponsiveValue, type VisibilityValue } from '../../fields/shared.js';
|
|
17
|
+
export declare const MAX_COLUMNS = 4;
|
|
18
|
+
export type ColumnCount = 2 | 3 | 4;
|
|
19
|
+
export interface ColumnsProps {
|
|
20
|
+
column1: unknown;
|
|
21
|
+
column2: unknown;
|
|
22
|
+
column3: unknown;
|
|
23
|
+
column4: unknown;
|
|
24
|
+
count: ColumnCount;
|
|
25
|
+
distribution: string;
|
|
26
|
+
gap: number;
|
|
27
|
+
background: BackgroundValue | null;
|
|
28
|
+
customPadding: ResponsiveValue<PaddingValue> | PaddingValue | null;
|
|
29
|
+
dimensions: ResponsiveValue<DimensionsValue> | DimensionsValue | null;
|
|
30
|
+
border: BorderValue | null;
|
|
31
|
+
margin: ResponsiveValue<PaddingValue> | PaddingValue | null;
|
|
32
|
+
animation: AnimationValue | null;
|
|
33
|
+
visibility: VisibilityValue | null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve `grid-template-columns` from the column count and distribution preset.
|
|
37
|
+
* Falls back to equal widths when the ratio's segment count doesn't match.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveColumnsTemplate(count: number, distribution: string): string;
|
|
40
|
+
export declare const ColumnsConfig: ComponentConfig;
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Columns Component - Puck Configuration (editor)
|
|
4
|
+
*
|
|
5
|
+
* Unlike Grid (a single CSS-grid slot where children auto-place into cells),
|
|
6
|
+
* Columns exposes one independent drop zone PER column — components can be
|
|
7
|
+
* dropped into a specific column. Puck slots are static named fields, so a fixed
|
|
8
|
+
* maximum set of column slots (column1..columnN) is declared and only the first
|
|
9
|
+
* `count` are rendered.
|
|
10
|
+
*
|
|
11
|
+
* Responsive: stacks vertically on mobile, switches to a CSS grid at md+ (≥768px).
|
|
12
|
+
*
|
|
13
|
+
* Width distribution supports equal columns or fr ratios (e.g. 2:1, 1:2, 1:1:2)
|
|
14
|
+
* for asymmetric layouts.
|
|
15
|
+
*/ import { useId } from 'react';
|
|
16
|
+
import { cn, dimensionsValueToCSS, marginValueToCSS, paddingValueToCSS, borderValueToCSS, backgroundValueToCSS, responsiveValueToCSS, visibilityValueToCSS } from '../../fields/shared.js';
|
|
17
|
+
import { AnimatedWrapper } from '../AnimatedWrapper.js';
|
|
18
|
+
import { createPaddingField } from '../../fields/PaddingField.js';
|
|
19
|
+
import { createBorderField } from '../../fields/BorderField.js';
|
|
20
|
+
import { createDimensionsField } from '../../fields/DimensionsField.js';
|
|
21
|
+
import { createMarginField } from '../../fields/MarginField.js';
|
|
22
|
+
import { createResetField } from '../../fields/ResetField.js';
|
|
23
|
+
import { createBackgroundField } from '../../fields/BackgroundField.js';
|
|
24
|
+
import { createAnimationField } from '../../fields/AnimationField.js';
|
|
25
|
+
import { createResponsiveField } from '../../fields/ResponsiveField.js';
|
|
26
|
+
import { createResponsiveVisibilityField } from '../../fields/ResponsiveVisibilityField.js';
|
|
27
|
+
export const MAX_COLUMNS = 4;
|
|
28
|
+
const DEFAULT_PADDING = {
|
|
29
|
+
top: 0,
|
|
30
|
+
right: 0,
|
|
31
|
+
bottom: 0,
|
|
32
|
+
left: 0,
|
|
33
|
+
unit: 'px',
|
|
34
|
+
linked: true
|
|
35
|
+
};
|
|
36
|
+
const DEFAULT_DIMENSIONS = {
|
|
37
|
+
mode: 'full',
|
|
38
|
+
alignment: 'center',
|
|
39
|
+
maxWidth: {
|
|
40
|
+
value: 100,
|
|
41
|
+
unit: '%',
|
|
42
|
+
enabled: true
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const defaultProps = {
|
|
46
|
+
column1: [],
|
|
47
|
+
column2: [],
|
|
48
|
+
column3: [],
|
|
49
|
+
column4: [],
|
|
50
|
+
count: 2,
|
|
51
|
+
distribution: 'equal',
|
|
52
|
+
gap: 24,
|
|
53
|
+
background: null,
|
|
54
|
+
customPadding: null,
|
|
55
|
+
dimensions: null,
|
|
56
|
+
border: null,
|
|
57
|
+
margin: null,
|
|
58
|
+
animation: null,
|
|
59
|
+
visibility: null
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Resolve `grid-template-columns` from the column count and distribution preset.
|
|
63
|
+
* Falls back to equal widths when the ratio's segment count doesn't match.
|
|
64
|
+
*/ export function resolveColumnsTemplate(count, distribution) {
|
|
65
|
+
const safeCount = Math.min(Math.max(Math.round(count) || 2, 1), MAX_COLUMNS);
|
|
66
|
+
if (distribution && distribution !== 'equal') {
|
|
67
|
+
const parts = distribution.split(':').map((p)=>p.trim());
|
|
68
|
+
if (parts.length === safeCount && parts.every((p)=>/^\d+(\.\d+)?$/.test(p))) {
|
|
69
|
+
return parts.map((p)=>`${p}fr`).join(' ');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return `repeat(${safeCount}, minmax(0, 1fr))`;
|
|
73
|
+
}
|
|
74
|
+
export const ColumnsConfig = {
|
|
75
|
+
label: 'Columns',
|
|
76
|
+
fields: {
|
|
77
|
+
_reset: createResetField({
|
|
78
|
+
defaultProps
|
|
79
|
+
}),
|
|
80
|
+
// One independent drop zone per column. Disallow Section (full-width) inside
|
|
81
|
+
// a column, matching Grid — it would break the column layout.
|
|
82
|
+
column1: {
|
|
83
|
+
type: 'slot',
|
|
84
|
+
disallow: [
|
|
85
|
+
'Section'
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
column2: {
|
|
89
|
+
type: 'slot',
|
|
90
|
+
disallow: [
|
|
91
|
+
'Section'
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
column3: {
|
|
95
|
+
type: 'slot',
|
|
96
|
+
disallow: [
|
|
97
|
+
'Section'
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
column4: {
|
|
101
|
+
type: 'slot',
|
|
102
|
+
disallow: [
|
|
103
|
+
'Section'
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
// Responsive visibility control
|
|
107
|
+
visibility: createResponsiveVisibilityField({
|
|
108
|
+
label: 'Visibility'
|
|
109
|
+
}),
|
|
110
|
+
count: {
|
|
111
|
+
type: 'select',
|
|
112
|
+
label: 'Number of Columns',
|
|
113
|
+
options: [
|
|
114
|
+
{
|
|
115
|
+
label: '2 Columns',
|
|
116
|
+
value: 2
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
label: '3 Columns',
|
|
120
|
+
value: 3
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
label: '4 Columns',
|
|
124
|
+
value: 4
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
distribution: {
|
|
129
|
+
type: 'select',
|
|
130
|
+
label: 'Column Widths',
|
|
131
|
+
options: [
|
|
132
|
+
{
|
|
133
|
+
label: 'Equal',
|
|
134
|
+
value: 'equal'
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
label: '2 : 1 (wide left)',
|
|
138
|
+
value: '2:1'
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
label: '1 : 2 (wide right)',
|
|
142
|
+
value: '1:2'
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
label: '3 : 1',
|
|
146
|
+
value: '3:1'
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
label: '1 : 3',
|
|
150
|
+
value: '1:3'
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
label: '2 : 1 : 1',
|
|
154
|
+
value: '2:1:1'
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
label: '1 : 2 : 1 (wide center)',
|
|
158
|
+
value: '1:2:1'
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
label: '1 : 1 : 2',
|
|
162
|
+
value: '1:1:2'
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
},
|
|
166
|
+
gap: {
|
|
167
|
+
type: 'number',
|
|
168
|
+
label: 'Gap (px)',
|
|
169
|
+
min: 0
|
|
170
|
+
},
|
|
171
|
+
background: createBackgroundField({
|
|
172
|
+
label: 'Background'
|
|
173
|
+
}),
|
|
174
|
+
border: createBorderField({
|
|
175
|
+
label: 'Border'
|
|
176
|
+
}),
|
|
177
|
+
dimensions: createResponsiveField({
|
|
178
|
+
label: 'Dimensions (Responsive)',
|
|
179
|
+
innerField: (config)=>createDimensionsField(config),
|
|
180
|
+
defaultValue: DEFAULT_DIMENSIONS
|
|
181
|
+
}),
|
|
182
|
+
animation: createAnimationField({
|
|
183
|
+
label: 'Animation'
|
|
184
|
+
}),
|
|
185
|
+
margin: createResponsiveField({
|
|
186
|
+
label: 'Margin (Responsive)',
|
|
187
|
+
innerField: (config)=>createMarginField(config),
|
|
188
|
+
defaultValue: DEFAULT_PADDING
|
|
189
|
+
}),
|
|
190
|
+
customPadding: createResponsiveField({
|
|
191
|
+
label: 'Padding (Responsive)',
|
|
192
|
+
innerField: (config)=>createPaddingField(config),
|
|
193
|
+
defaultValue: DEFAULT_PADDING
|
|
194
|
+
})
|
|
195
|
+
},
|
|
196
|
+
defaultProps,
|
|
197
|
+
render: ({ column1: Column1, column2: Column2, column3: Column3, column4: Column4, count = 2, distribution = 'equal', gap, background, customPadding, dimensions, border, margin, animation, visibility })=>{
|
|
198
|
+
const safeCount = Math.min(Math.max(Math.round(count) || 2, 1), MAX_COLUMNS);
|
|
199
|
+
// Generate unique IDs for CSS targeting
|
|
200
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
201
|
+
const uniqueId = useId().replace(/:/g, '');
|
|
202
|
+
const wrapperClass = `puck-columns-${uniqueId}`;
|
|
203
|
+
const contentClass = `puck-columns-content-${uniqueId}`;
|
|
204
|
+
const mediaQueries = [];
|
|
205
|
+
const backgroundStyles = backgroundValueToCSS(background);
|
|
206
|
+
const wrapperStyles = {
|
|
207
|
+
...backgroundStyles
|
|
208
|
+
};
|
|
209
|
+
const paddingResult = responsiveValueToCSS(customPadding, (v)=>({
|
|
210
|
+
padding: paddingValueToCSS(v)
|
|
211
|
+
}), wrapperClass);
|
|
212
|
+
Object.assign(wrapperStyles, paddingResult.baseStyles);
|
|
213
|
+
if (paddingResult.mediaQueryCSS) mediaQueries.push(paddingResult.mediaQueryCSS);
|
|
214
|
+
const borderStyles = borderValueToCSS(border);
|
|
215
|
+
if (borderStyles) Object.assign(wrapperStyles, borderStyles);
|
|
216
|
+
const marginResult = responsiveValueToCSS(margin, (v)=>({
|
|
217
|
+
margin: marginValueToCSS(v)
|
|
218
|
+
}), wrapperClass);
|
|
219
|
+
Object.assign(wrapperStyles, marginResult.baseStyles);
|
|
220
|
+
if (marginResult.mediaQueryCSS) mediaQueries.push(marginResult.mediaQueryCSS);
|
|
221
|
+
const dimensionsResult = responsiveValueToCSS(dimensions, dimensionsValueToCSS, contentClass);
|
|
222
|
+
const visibilityCSS = visibilityValueToCSS(visibility, wrapperClass);
|
|
223
|
+
if (visibilityCSS) mediaQueries.push(visibilityCSS);
|
|
224
|
+
const contentClasses = cn('flex flex-col w-full', 'md:grid', contentClass);
|
|
225
|
+
const contentStyles = {
|
|
226
|
+
gap,
|
|
227
|
+
...dimensionsResult.baseStyles
|
|
228
|
+
};
|
|
229
|
+
if (dimensionsResult.mediaQueryCSS) mediaQueries.push(dimensionsResult.mediaQueryCSS);
|
|
230
|
+
const colsTemplate = resolveColumnsTemplate(safeCount, distribution);
|
|
231
|
+
const gridStyles = {
|
|
232
|
+
...contentStyles,
|
|
233
|
+
'--cols-template': colsTemplate
|
|
234
|
+
};
|
|
235
|
+
const slots = [
|
|
236
|
+
Column1,
|
|
237
|
+
Column2,
|
|
238
|
+
Column3,
|
|
239
|
+
Column4
|
|
240
|
+
];
|
|
241
|
+
const allMediaQueryCSS = mediaQueries.join('\n');
|
|
242
|
+
return /*#__PURE__*/ _jsxs(AnimatedWrapper, {
|
|
243
|
+
animation: animation,
|
|
244
|
+
children: [
|
|
245
|
+
allMediaQueryCSS && /*#__PURE__*/ _jsx("style", {
|
|
246
|
+
children: allMediaQueryCSS
|
|
247
|
+
}),
|
|
248
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
249
|
+
className: wrapperClass,
|
|
250
|
+
style: wrapperStyles,
|
|
251
|
+
children: [
|
|
252
|
+
/*#__PURE__*/ _jsx("div", {
|
|
253
|
+
className: contentClasses,
|
|
254
|
+
style: gridStyles,
|
|
255
|
+
children: slots.slice(0, safeCount).map((Slot, i)=>{
|
|
256
|
+
const ColumnSlot = Slot;
|
|
257
|
+
return /*#__PURE__*/ _jsx(ColumnSlot, {}, i);
|
|
258
|
+
})
|
|
259
|
+
}),
|
|
260
|
+
/*#__PURE__*/ _jsx("style", {
|
|
261
|
+
children: `
|
|
262
|
+
@media (min-width: 768px) {
|
|
263
|
+
.${contentClass} {
|
|
264
|
+
grid-template-columns: var(--cols-template);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
`
|
|
268
|
+
})
|
|
269
|
+
]
|
|
270
|
+
})
|
|
271
|
+
]
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Columns Component - Server-safe Puck Configuration
|
|
3
|
+
*
|
|
4
|
+
* Unlike Grid (a single slot styled as CSS grid, where children auto-place into
|
|
5
|
+
* cells), Columns exposes one independent drop zone PER column. Each column is
|
|
6
|
+
* its own slot, so components can be targeted into a specific column.
|
|
7
|
+
*
|
|
8
|
+
* Puck slots are static named fields, so we declare a fixed maximum set of
|
|
9
|
+
* column slots (column1..columnN) and render only the first `count`.
|
|
10
|
+
*
|
|
11
|
+
* Responsive: stacks vertically on mobile (flex column), switches to a CSS grid
|
|
12
|
+
* at md+ (≥768px), matching the Grid component's behavior.
|
|
13
|
+
*
|
|
14
|
+
* This is the server-safe variant (slots only, no editor fields). For the full
|
|
15
|
+
* editor version with fields, use Columns.tsx
|
|
16
|
+
*/
|
|
17
|
+
import type { ComponentConfig } from '@puckeditor/core';
|
|
18
|
+
import { type PaddingValue, type BorderValue, type DimensionsValue, type BackgroundValue, type AnimationValue, type ResponsiveValue, type VisibilityValue } from '../../fields/shared.js';
|
|
19
|
+
/** Maximum number of column slots the component declares. */
|
|
20
|
+
export declare const MAX_COLUMNS = 4;
|
|
21
|
+
export type ColumnCount = 2 | 3 | 4;
|
|
22
|
+
export interface ColumnsProps {
|
|
23
|
+
column1: unknown;
|
|
24
|
+
column2: unknown;
|
|
25
|
+
column3: unknown;
|
|
26
|
+
column4: unknown;
|
|
27
|
+
count: ColumnCount;
|
|
28
|
+
/**
|
|
29
|
+
* Column width distribution. 'equal' splits evenly; otherwise a colon-separated
|
|
30
|
+
* fr ratio (e.g. '2:1', '1:2', '1:1:2'). Ratios whose segment count doesn't
|
|
31
|
+
* match `count` fall back to equal.
|
|
32
|
+
*/
|
|
33
|
+
distribution: string;
|
|
34
|
+
gap: number;
|
|
35
|
+
background: BackgroundValue | null;
|
|
36
|
+
customPadding: ResponsiveValue<PaddingValue> | PaddingValue | null;
|
|
37
|
+
dimensions: ResponsiveValue<DimensionsValue> | DimensionsValue | null;
|
|
38
|
+
border: BorderValue | null;
|
|
39
|
+
margin: ResponsiveValue<PaddingValue> | PaddingValue | null;
|
|
40
|
+
animation: AnimationValue | null;
|
|
41
|
+
visibility: VisibilityValue | null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the `grid-template-columns` value from the column count and the
|
|
45
|
+
* distribution preset. Falls back to equal widths when the ratio doesn't match.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveColumnsTemplate(count: number, distribution: string): string;
|
|
48
|
+
export declare const ColumnsConfig: ComponentConfig;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Columns Component - Server-safe Puck Configuration
|
|
3
|
+
*
|
|
4
|
+
* Unlike Grid (a single slot styled as CSS grid, where children auto-place into
|
|
5
|
+
* cells), Columns exposes one independent drop zone PER column. Each column is
|
|
6
|
+
* its own slot, so components can be targeted into a specific column.
|
|
7
|
+
*
|
|
8
|
+
* Puck slots are static named fields, so we declare a fixed maximum set of
|
|
9
|
+
* column slots (column1..columnN) and render only the first `count`.
|
|
10
|
+
*
|
|
11
|
+
* Responsive: stacks vertically on mobile (flex column), switches to a CSS grid
|
|
12
|
+
* at md+ (≥768px), matching the Grid component's behavior.
|
|
13
|
+
*
|
|
14
|
+
* This is the server-safe variant (slots only, no editor fields). For the full
|
|
15
|
+
* editor version with fields, use Columns.tsx
|
|
16
|
+
*/ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
17
|
+
import { cn, dimensionsValueToCSS, marginValueToCSS, paddingValueToCSS, borderValueToCSS, backgroundValueToCSS, responsiveValueToCSS, visibilityValueToCSS } from '../../fields/shared.js';
|
|
18
|
+
import { AnimatedWrapper } from '../AnimatedWrapper.js';
|
|
19
|
+
/** Maximum number of column slots the component declares. */ export const MAX_COLUMNS = 4;
|
|
20
|
+
const defaultProps = {
|
|
21
|
+
column1: [],
|
|
22
|
+
column2: [],
|
|
23
|
+
column3: [],
|
|
24
|
+
column4: [],
|
|
25
|
+
count: 2,
|
|
26
|
+
distribution: 'equal',
|
|
27
|
+
gap: 24,
|
|
28
|
+
background: null,
|
|
29
|
+
customPadding: null,
|
|
30
|
+
dimensions: null,
|
|
31
|
+
border: null,
|
|
32
|
+
margin: null,
|
|
33
|
+
animation: null,
|
|
34
|
+
visibility: null
|
|
35
|
+
};
|
|
36
|
+
// Simple ID generator for server-side rendering
|
|
37
|
+
let idCounter = 0;
|
|
38
|
+
function generateUniqueId() {
|
|
39
|
+
return `c${(++idCounter).toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the `grid-template-columns` value from the column count and the
|
|
43
|
+
* distribution preset. Falls back to equal widths when the ratio doesn't match.
|
|
44
|
+
*/ export function resolveColumnsTemplate(count, distribution) {
|
|
45
|
+
const safeCount = Math.min(Math.max(Math.round(count) || 2, 1), MAX_COLUMNS);
|
|
46
|
+
if (distribution && distribution !== 'equal') {
|
|
47
|
+
const parts = distribution.split(':').map((p)=>p.trim());
|
|
48
|
+
if (parts.length === safeCount && parts.every((p)=>/^\d+(\.\d+)?$/.test(p))) {
|
|
49
|
+
return parts.map((p)=>`${p}fr`).join(' ');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return `repeat(${safeCount}, minmax(0, 1fr))`;
|
|
53
|
+
}
|
|
54
|
+
export const ColumnsConfig = {
|
|
55
|
+
label: 'Columns',
|
|
56
|
+
fields: {
|
|
57
|
+
column1: {
|
|
58
|
+
type: 'slot'
|
|
59
|
+
},
|
|
60
|
+
column2: {
|
|
61
|
+
type: 'slot'
|
|
62
|
+
},
|
|
63
|
+
column3: {
|
|
64
|
+
type: 'slot'
|
|
65
|
+
},
|
|
66
|
+
column4: {
|
|
67
|
+
type: 'slot'
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
defaultProps,
|
|
71
|
+
render: ({ column1: Column1, column2: Column2, column3: Column3, column4: Column4, count = 2, distribution = 'equal', gap, background, customPadding, dimensions, border, margin, animation, visibility })=>{
|
|
72
|
+
const safeCount = Math.min(Math.max(Math.round(count) || 2, 1), MAX_COLUMNS);
|
|
73
|
+
// Generate unique IDs for CSS targeting (server-safe)
|
|
74
|
+
const uniqueId = generateUniqueId();
|
|
75
|
+
const wrapperClass = `puck-columns-${uniqueId}`;
|
|
76
|
+
const contentClass = `puck-columns-content-${uniqueId}`;
|
|
77
|
+
// Collect all media query CSS
|
|
78
|
+
const mediaQueries = [];
|
|
79
|
+
// Generate styles from BackgroundValue
|
|
80
|
+
const backgroundStyles = backgroundValueToCSS(background);
|
|
81
|
+
const wrapperStyles = {
|
|
82
|
+
...backgroundStyles
|
|
83
|
+
};
|
|
84
|
+
// Padding (responsive)
|
|
85
|
+
const paddingResult = responsiveValueToCSS(customPadding, (v)=>({
|
|
86
|
+
padding: paddingValueToCSS(v)
|
|
87
|
+
}), wrapperClass);
|
|
88
|
+
Object.assign(wrapperStyles, paddingResult.baseStyles);
|
|
89
|
+
if (paddingResult.mediaQueryCSS) mediaQueries.push(paddingResult.mediaQueryCSS);
|
|
90
|
+
// Border
|
|
91
|
+
const borderStyles = borderValueToCSS(border);
|
|
92
|
+
if (borderStyles) Object.assign(wrapperStyles, borderStyles);
|
|
93
|
+
// Margin (responsive)
|
|
94
|
+
const marginResult = responsiveValueToCSS(margin, (v)=>({
|
|
95
|
+
margin: marginValueToCSS(v)
|
|
96
|
+
}), wrapperClass);
|
|
97
|
+
Object.assign(wrapperStyles, marginResult.baseStyles);
|
|
98
|
+
if (marginResult.mediaQueryCSS) mediaQueries.push(marginResult.mediaQueryCSS);
|
|
99
|
+
// Dimensions (responsive)
|
|
100
|
+
const dimensionsResult = responsiveValueToCSS(dimensions, dimensionsValueToCSS, contentClass);
|
|
101
|
+
// Visibility media queries
|
|
102
|
+
const visibilityCSS = visibilityValueToCSS(visibility, wrapperClass);
|
|
103
|
+
if (visibilityCSS) mediaQueries.push(visibilityCSS);
|
|
104
|
+
// Tailwind: flex column on mobile, grid on md+
|
|
105
|
+
const contentClasses = cn('flex flex-col w-full', 'md:grid', contentClass);
|
|
106
|
+
const contentStyles = {
|
|
107
|
+
gap,
|
|
108
|
+
...dimensionsResult.baseStyles
|
|
109
|
+
};
|
|
110
|
+
if (dimensionsResult.mediaQueryCSS) mediaQueries.push(dimensionsResult.mediaQueryCSS);
|
|
111
|
+
// grid-template-columns is dynamic, so drive it from a CSS var and scope the
|
|
112
|
+
// media-query rule to this instance's content class (no cross-instance leak).
|
|
113
|
+
const colsTemplate = resolveColumnsTemplate(safeCount, distribution);
|
|
114
|
+
const gridStyles = {
|
|
115
|
+
...contentStyles,
|
|
116
|
+
'--cols-template': colsTemplate
|
|
117
|
+
};
|
|
118
|
+
const slots = [
|
|
119
|
+
Column1,
|
|
120
|
+
Column2,
|
|
121
|
+
Column3,
|
|
122
|
+
Column4
|
|
123
|
+
];
|
|
124
|
+
const allMediaQueryCSS = mediaQueries.join('\n');
|
|
125
|
+
return /*#__PURE__*/ _jsxs(AnimatedWrapper, {
|
|
126
|
+
animation: animation,
|
|
127
|
+
children: [
|
|
128
|
+
allMediaQueryCSS && /*#__PURE__*/ _jsx("style", {
|
|
129
|
+
children: allMediaQueryCSS
|
|
130
|
+
}),
|
|
131
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
132
|
+
className: wrapperClass,
|
|
133
|
+
style: wrapperStyles,
|
|
134
|
+
children: [
|
|
135
|
+
/*#__PURE__*/ _jsx("div", {
|
|
136
|
+
className: contentClasses,
|
|
137
|
+
style: gridStyles,
|
|
138
|
+
children: slots.slice(0, safeCount).map((Slot, i)=>{
|
|
139
|
+
const ColumnSlot = Slot;
|
|
140
|
+
return /*#__PURE__*/ _jsx(ColumnSlot, {}, i);
|
|
141
|
+
})
|
|
142
|
+
}),
|
|
143
|
+
/*#__PURE__*/ _jsx("style", {
|
|
144
|
+
children: `
|
|
145
|
+
@media (min-width: 768px) {
|
|
146
|
+
.${contentClass} {
|
|
147
|
+
grid-template-columns: var(--cols-template);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
`
|
|
151
|
+
})
|
|
152
|
+
]
|
|
153
|
+
})
|
|
154
|
+
]
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
};
|
|
@@ -94,6 +94,7 @@ export declare const editorConfig: {
|
|
|
94
94
|
Container: ComponentConfig<any>;
|
|
95
95
|
Flex: ComponentConfig<any>;
|
|
96
96
|
Grid: ComponentConfig<any>;
|
|
97
|
+
Columns: ComponentConfig<any>;
|
|
97
98
|
Section: ComponentConfig<any>;
|
|
98
99
|
Spacer: ComponentConfig<any>;
|
|
99
100
|
Template: ComponentConfig<any>;
|
|
@@ -6,6 +6,7 @@ import { lockedSlugField, lockedHomepageField } from '../fields/LockedField.js';
|
|
|
6
6
|
import { ContainerConfig } from '../components/layout/Container.js';
|
|
7
7
|
import { FlexConfig } from '../components/layout/Flex.js';
|
|
8
8
|
import { GridConfig } from '../components/layout/Grid.js';
|
|
9
|
+
import { ColumnsConfig } from '../components/layout/Columns.js';
|
|
9
10
|
import { SectionConfig } from '../components/layout/Section.js';
|
|
10
11
|
import { SpacerConfig } from '../components/layout/Spacer.js';
|
|
11
12
|
import { TemplateConfig } from '../components/layout/Template.js';
|
|
@@ -146,6 +147,7 @@ export const editorConfig = {
|
|
|
146
147
|
'Container',
|
|
147
148
|
'Flex',
|
|
148
149
|
'Grid',
|
|
150
|
+
'Columns',
|
|
149
151
|
'Section',
|
|
150
152
|
'Spacer',
|
|
151
153
|
'Template'
|
|
@@ -181,6 +183,7 @@ export const editorConfig = {
|
|
|
181
183
|
Container: ContainerConfig,
|
|
182
184
|
Flex: FlexConfig,
|
|
183
185
|
Grid: GridConfig,
|
|
186
|
+
Columns: ColumnsConfig,
|
|
184
187
|
Section: SectionConfig,
|
|
185
188
|
Spacer: SpacerConfig,
|
|
186
189
|
Template: TemplateConfig,
|
package/dist/config/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { DEFAULT_LAYOUTS, layoutsToOptions } from '../layouts/index.js';
|
|
|
9
9
|
import { ContainerConfig } from '../components/layout/Container.server.js';
|
|
10
10
|
import { FlexConfig } from '../components/layout/Flex.server.js';
|
|
11
11
|
import { GridConfig } from '../components/layout/Grid.server.js';
|
|
12
|
+
import { ColumnsConfig } from '../components/layout/Columns.server.js';
|
|
12
13
|
import { SectionConfig } from '../components/layout/Section.server.js';
|
|
13
14
|
import { SpacerConfig } from '../components/layout/Spacer.server.js';
|
|
14
15
|
import { TemplateServerConfig } from '../components/layout/Template.server.js';
|
|
@@ -75,6 +76,7 @@ import { AccordionConfig } from '../components/interactive/Accordion.server.js';
|
|
|
75
76
|
'Container',
|
|
76
77
|
'Flex',
|
|
77
78
|
'Grid',
|
|
79
|
+
'Columns',
|
|
78
80
|
'Section',
|
|
79
81
|
'Spacer',
|
|
80
82
|
'Template'
|
|
@@ -110,6 +112,7 @@ import { AccordionConfig } from '../components/interactive/Accordion.server.js';
|
|
|
110
112
|
Container: ContainerConfig,
|
|
111
113
|
Flex: FlexConfig,
|
|
112
114
|
Grid: GridConfig,
|
|
115
|
+
Columns: ColumnsConfig,
|
|
113
116
|
Section: SectionConfig,
|
|
114
117
|
Spacer: SpacerConfig,
|
|
115
118
|
Template: TemplateServerConfig,
|
package/dist/config/presets.d.ts
CHANGED
|
@@ -244,6 +244,7 @@ export declare const fullConfig: {
|
|
|
244
244
|
Container: ComponentConfig<any>;
|
|
245
245
|
Flex: ComponentConfig<any>;
|
|
246
246
|
Grid: ComponentConfig<any>;
|
|
247
|
+
Columns: ComponentConfig<any>;
|
|
247
248
|
Section: ComponentConfig<any>;
|
|
248
249
|
Spacer: ComponentConfig<any>;
|
|
249
250
|
Template: ComponentConfig<any>;
|
package/dist/config/presets.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
// Import component configs
|
|
4
|
-
import { ContainerConfig, FlexConfig, GridConfig, SectionConfig, SpacerConfig, TemplateConfig, HeadingConfig, TextConfig, RichTextEditorConfig, ImageConfig, ButtonConfig, CardConfig, DividerConfig, AccordionConfig } from '../components/exports.js';
|
|
4
|
+
import { ContainerConfig, FlexConfig, GridConfig, ColumnsConfig, SectionConfig, SpacerConfig, TemplateConfig, HeadingConfig, TextConfig, RichTextEditorConfig, ImageConfig, ButtonConfig, CardConfig, DividerConfig, AccordionConfig } from '../components/exports.js';
|
|
5
5
|
// Import field factories for root config
|
|
6
6
|
import { createBackgroundField } from '../fields/BackgroundField.js';
|
|
7
7
|
import { lockedSlugField, lockedHomepageField } from '../fields/LockedField.js';
|
|
@@ -278,6 +278,7 @@ import { createSlugPreviewField } from '../fields/SlugPreviewField.js';
|
|
|
278
278
|
'Container',
|
|
279
279
|
'Flex',
|
|
280
280
|
'Grid',
|
|
281
|
+
'Columns',
|
|
281
282
|
'Section',
|
|
282
283
|
'Spacer',
|
|
283
284
|
'Template'
|
|
@@ -313,6 +314,7 @@ import { createSlugPreviewField } from '../fields/SlugPreviewField.js';
|
|
|
313
314
|
Container: ContainerConfig,
|
|
314
315
|
Flex: FlexConfig,
|
|
315
316
|
Grid: GridConfig,
|
|
317
|
+
Columns: ColumnsConfig,
|
|
316
318
|
Section: SectionConfig,
|
|
317
319
|
Spacer: SpacerConfig,
|
|
318
320
|
Template: TemplateConfig,
|
|
@@ -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
|
package/dist/endpoints/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
package/dist/plugin/index.js
CHANGED
|
@@ -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,
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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.
|
|
1
|
+
export declare const VERSION = "0.6.27";
|
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.
|
|
2
|
+
export const VERSION = '0.6.27';
|
|
@@ -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
|
-
|
|
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.
|
|
3
|
+
"version": "0.6.27",
|
|
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.
|
|
156
|
-
"@puckeditor/plugin-ai": "^0.
|
|
157
|
-
"@puckeditor/plugin-heading-analyzer": "^0.21.
|
|
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",
|
|
@@ -173,7 +173,7 @@
|
|
|
173
173
|
"devDependencies": {
|
|
174
174
|
"@payloadcms/next": "^3.84.1",
|
|
175
175
|
"@payloadcms/ui": "^3.84.1",
|
|
176
|
-
"@puckeditor/core": "^0.21.
|
|
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",
|