Releases: measuredco/puck
v0.17.1
v0.17.0
Puck v0.17.0 adds support for React 19, improves various field APIs and squashes some bugs.
This is our final feature release of the year! We're delaying the new drag-and-drop engine release until January while we iron out the last few kinks. Happy holidays, and thanks for being a wonderful community 🎄
TLDR
- React 19 support: We've made React 19 support official by adding it to the supported list of peer dependencies.
- Next.js 15: Upgraded the next recipe to use Next 15.1 and React 19 by default.
- New external field APIs: External fields now support react elements as rows, and a custom footer, enabling much more customization. Thanks to @camhammel for these improvements!
- Duplicate array items: Added a new action to the array field so you can easily duplicate array items. Thank you @siarhei-zharnasek 🙏
- resolveFields enhancement: You can now access the DropZone parent data when using the
resolveFields
API, enabling you to customize the fields based on the container's type or props.
Highlights
React 19 support
We've made React 19 support official by adding it to the supported list of peer dependencies. It turns out that earlier versions of Puck already play well with React 19, you don't need to upgrade Puck to use React 19 if you don't want to.
Next 15
The official next recipe now uses Next 15.1 and React 19 by default. If you're using the recipe and want to upgrade, make sure you also upgrade to React 19 as required by Next 15 when using the App Router. See the official Next.js upgrade guide for more info.
New external field APIs
The mapRow
API now supports react elements, making it possible to add richer content to your external field table:
{
type: "external",
fetchList: async () => {
return [
{ title: "Hello, world", imgUrl: "https://example.com/img.jpg" },
{ title: "Goodbye, world", imgUrl: "https://example.com/img.jpg" },
];
},
mapRow: (item) => ({ ...item, imgUrl: <img src={item.imgUrl} /> }),
}
And the new renderFooter
API enables you to customise the footer of your external field modal.
{
type: "external",
fetchList: async () => {
return [
{ title: "Hello, world" },
{ title: "Goodbye, world" },
];
},
renderFooter: ({ items }) => (
<b>Custom footer with {items.length} results</b>
),
}
Duplicate array items
A new action in the array field allows you to easily duplicate array items.
Access parent data in resolveFields
The new parent
param on the resolveFields
API enables you to configure your fields based on the props of the parent item when using DropZones.
Here's an example that shows a field called flex
when the parent component has a display
prop of "flex"
:
const config = {
components: {
MyComponent: {
resolveFields: (data, { fields, parent }) => {
if (parent.props.display === "flex") {
return {
...fields,
flex: {
type: "radio",
options: [
{ label: "Grow", value: "grow" },
{ label: "Shrink", value: "shrink" },
],
}
}
}
return fields;
},
// ...
},
},
};
Full changelog
Features
- add duplicate action to array field (229cbdd)
- add renderFooter API to external field (ccec96e)
- allow react elements in external field mapRow (2f781de)
- enable resolveFields to access parent data (196227b)
- list React 19 as supported peer dependency (85e8cc1)
- track focused field in app state (91bc97a)
- upgrade next recipe to v15.1 (8ef51c5)
- use React 19 in next recipe (6b3d97f)
Bug Fixes
- always run field resolvers when item change (159d819)
- always update fields when resolveData runs (39dd619)
- ensure radio fields are functional inside arrays (7736294)
- prevent field name collision causing hook render mismatch (b51954a)
- prevent flicker when using resolveData with arrays (1be9b88)
- provide better error when usePuck used inappropriately (9991c07)
- remove leading zeros in Number field (5ba9399)
- respect original value type in radio and select fields (00ccd1d and 6e5864a)
New Contributors
- @ruofee made their first contribution in #693
- @DevOfManyThings made their first contribution in #698
- @benlife5 made their first contribution in #695
- @camhammel made their first contribution in #682
- @siarhei-zharnasek made their first contribution in #676
- @antonva made their first contribution in #707
- @jswhisperer made their first contribution in #722
Full Changelog: v0.16.2...v0.17.0
v0.16.2
Bug Fixes
- always treat data as immutable, fixing Redux issues (51154e9)
- don't crash if component definition missing (525b506)
- don't crash when selecting component with no config (cb90f5d), closes #671
- export missing resolveAllData lib in RSC bundle (2f5fb7b)
- fix RTL styles in action bar overlay (bf5c5a3)
- remove internal AutoField and FieldLabel components from bundle (5df1597)
- remove unused label from AutoField type (18b6f1a)
New Contributors
- @IMythx made their first contribution in #650
- @novacoole made their first contribution in #678
- @missilev made their first contribution in #677
Full Changelog: v0.16.1...v0.16.2
v0.16.1
Bug Fixes
- don't delete array field on click in FieldLabel (ed282b9)
- don't overwrite user input when field recently changed (6126040)
- don't show field loader if no resolver defined (8c706cd)
- hide ActionBar.Group border when empty (4345165)
- prevent item click before iframe load (61e1653)
- prevent flash of field loader when no data changed (20d7309)
- respect readOnly styles in AutoField (9ffe817)
Full Changelog: v0.16.0...v0.16.1
v0.16.0
We're celebrating 5,000 stars on GitHub! Thank you to our wonderful community! 🤩
Puck v0.16 is a big release, introducing the headline permissions API and — you guessed it — quality of life improvements. This one took a while to put together, and we appreciate your patience and support 🙏
TLDR
- Permissions: Toggle Puck features like duplication, dragging, deletion through the new permissions and resolvePermissions APIs.
- Action bar override: Create custom action bars using the
actionBar
override, or extend the default one using the new<ActionBar>
component. - iframe style injection: Access the iframe document to inject styles directly, or make other changes, via the new
iframe
override. We also introduced theemotion-cache
plugin for the common Emotion use-case. - History injection: Inject the undo/redo history via the a series of new APIs
- React to actions: React to actions dispatched by Puck via the
onAction
callback. - Optional fields: Optional props are no longer required to define in fields, since they may be defined
Highlights
🔐 Permissions
Permissions enable you to toggle core Puck functionality globally, on a per-component basis or dynamically. Huge thanks to @xaviemirmon for his efforts on this.
export function Editor() {
return (
<Puck
permissions={{
delete: false,
duplicate: true
}}
/>
);
}
🪛 Action bar override
The new actionBar
override enables you to create a custom action bar overlay, or extend the default one using the <ActionBar>
component:
const overrides = {
actionBar: ({ children }) => (
<ActionBar label="Actions">
{/* Render default actions */}
<ActionBar.Group>{children}</ActionBar.Group>
{/* Render new actions */}
<ActionBar.Group>
<ActionBar.Action onClick={() => console.log("Clicked!")}>
★
</ActionBar.Action>
</ActionBar.Group>
</ActionBar>
),
};
💅 iframe style injection
The iframe
override enables you to access the iframe document
, making it possible to inject styles into the head:
const overrides = {
iframe: ({ children, document }) => {
useEffect(() => {
if (document) {
document.body.setAttribute("style", "background: hotpink;");
}
}, [document]);
return <>{children}</>;
},
};
The new emotion-cache
plugin uses this API to create an emotion cache inside the iframe, making Puck easy to use with any Emotion-based component library.
📜 History injection
Use the new history injection APIs to provide your own undo/redo history via the initialHistory
prop, or dynamically via the setHistories
and setHistoryIndex
functions from usePuck().history
.
const historyState = {
data: {
root: {
props: { title: "My History" },
},
},
};
export function Editor() {
return (
<Puck
initialHistory={{
histories: [{ state: historyState }],
index: 0,
}}
// ...
/>
);
}
React to actions
The onAction
API enables you to react to Puck’s internal actions as they’re dispatched:
export function Editor() {
return (
<Puck
onAction={(action, appState, prevAppState) => {
if (action.type === "insert") {
console.log("New component was inserted", appState);
}
}}
/>
);
}
Breaking changes
history.data
is now history.state
When using the usePuck history
API, data
is now renamed state
.
history.id
is now optional (TypeScript)
When using the usePuck history
API id
is now optional. Puck will always generate an id
, but TypeScript may complain.
lastData
is now returned as null
instead of {}
when empty in resolvers
When using the lastData
option provided to resolveData
or resolveFields
functions, and there is no previous data, lastData
will now be null
instead of {}
.
Full changelog
Features
- add actionBar override for adding component controls (48ec0d7)
- add automatic RSC export, replacing /rsc bundle (d21eba6)
- add isDisabled prop to Drawer.Item (cad95b8)
- add generic type to usePuck hook (01703a9)
- add iframe override for style injection (7cac376)
- add initialHistory prop to Puck (54b5a87)
- add onAction API to track and react to state changes (c7007ac)
- add permissions API (a43914d)
- add plugin for injecting Emotion cache (f8a88b9)
- add resolvePermissions API (f0655f0)
- add waitForStyles option to iframe config (bc81d9c)
- call resolveData when new item inserted (3298831)
- don't mandate fields for optional props (5a219ef)
- export ActionBar component for use in overrides (04fd6c5)
- infer Data type from user config (50045bb)
- make ID optional in History type (BREAKING CHANGE) (d917229)
- provide ES Module build (ff9076b)
- rename history.data to history.state (BREAKING CHANGE) (b09244c)
- show spinner if iframe load takes over 500ms (cfecf54)
- streamline usePuck history API (c8b2807)
- upgrade "next" recipe to [email protected] (60fe631)
Bug Fixes
- add favicon to next recipe to prevent Puck 404 (2c52d27)
- add missing readOnly state to External fields (bf1449d)
- always record history on component insert (88c5ab6)
- don't cache /edit route in Next recipe (94f16b2)
- don't submit buttons if Puck used in form (f761e5f)
- ensure demo types are satisfied with TypeScript@5 (958dc25)
- export missing Plugin type (eb42734)
- fix crash if component in data is missing from config (0daf478)
- improve resiliency of iframe CSS for some frameworks, like Mantine (538cb05)
- make Config and Data types more robust (6bcf555)
- prevent infinite loop when using plugins with some frameworks (3870871)
- prevent Tailwind from clashing with viewport...
v0.15.0
Puck v0.15.0 introduces the powerful resolveFields API for dynamically defining fields, and exports Puck's internal field component for use within custom fields.
TLDR
- Dynamic fields: Use the
resolveFields
API to dynamically define your fields - great for showing fields conditionally or loading external APIs. - AutoField component: Build custom fields using Puck's own field component for a seamless UI.
- Override Publish button: Swap out the publish button without swapping out the entire header.
- New
puck.isEditing
prop: Modify component behaviour based on whether it's inside<Puck>
or<Render>
.
Highlights
🪄 Dynamic fields
Dynamic field resolution via the resolveFields
API allows you to change the field configuration whenever the props change. You can use this to show and hide fields or even load data from an API.
dynamic.fields.full.page.mp4
Code example:
const config = {
components: {
MyComponent: {
// ...
resolveFields: (data) => ({
myField: {
type: "radio",
options: [], // Populate dynamically
},
}),
},
},
};
🔤 AutoField component
The AutoField component lets you render a Puck field based on a Field object. Use this when building custom fields that need to use Puck-style fields internally.
const CustomField = ({ onChange, value }) => (
<AutoField field={{ type: "text" }} onChange={onChange} value={value} />
);
🌐 Enable override of Publish button via headerActions
It's now possible to implement a custom Publish button without overriding the entire header by using the headerActions
override.
<Puck
overrides={{
headerActions: ({ children }) => (
<>
{/* children will render default Publish button */}
{children}
{/* Or you can define your own */}
<button>Click me</button>
</>
),
}}
/>;
This creates a breaking change for existing headerActions
overrides, which will now need to render children
to show the default Publish button.
🖊️ New puck.isEditing
prop provided to all components
Components now receive the puck.isEditing
prop. Use this to toggle functionality based on whether the component is being rendered in the <Puck>
or <Render>
contexts.
const config = {
components: {
MyComponent: {
render: ({ puck }) => (
<div style={{ background: puck.isEditing ? "hotpink" : "transparent" }}>
Hello, world
</div>
),
},
},
};
Breaking changes
headerActions
override must now render {children}
to show default Publish button
In order to support custom Publish buttons, the headerActions
override will no longer render the default Publish button unless children
are rendered.
<Puck
overrides={{
headerActions: ({ children }) => (
<>
{/* Render default Publish button */}
{children}
</>
),
}}
/>;
Deprecations
Undocumented editMode
API deprecated
The undocumented editMode
prop is now deprecated in favor of puck.isEditing
.
Changelog
Features
- add AutoField component for using Puck fields inside custom fields (106028b)
- add isEditing flag to
puck
object prop (13bb1bd) - add resolveFields API for dynamic fields (0a18bdb)
- allow data prop to accept an empty object (aedd401)
- bump next recipe to Next@14 (47a27ed)
- enable override of publish button (breaking change) (480467a)
- expose previous data to resolveData via
lastData
param (dd7051e) - replace history chevrons with undo/redo icons (91dff22)
Bug Fixes
- align Drawer behaviour and docs with expectation (e2cd445)
- animate loader in iframe (151a267)
- don't inline link stylesheets for more predictable behaviour (c0a331d)
- don't overflow external inputs inside arrays/objects (42ef582)
- don't throw warning when user is correctly specifying root props (46aa8ff)
- don't unintentionally use read-only styles in external fields (acaf727)
- fix defaultProps for root (9a1cc7c)
- infer correct value types in Custom fields (5c8c0e1)
- position field loader relative to sidebar, not fields (2e8936e)
- show external field modal when using custom interfaces (6e97a0e)
- show field loader when using field overrides (8ccfa4c)
- still load iframe if styles fail to load (3e56bc1)
New Contributors
Full Changelog: v0.14.2...v0.15.0
v0.14.2
Puck v0.14.2 addresses numerous stability issues and significantly improves loading performance for iframes.
Changelog
Bug Fixes
- add DropZone iframe compatablity mode for bug in Safari 17.2, 17.3 and 17.4 (47496c2)
- check for optionality to handle race condition when dragging (4dbd487)
- defer iframe event binding until contentWindow is ready (268ea53)
- don't crash if component is missing after referenced in category (dc93789)
- don't force height of DropZones in custom interfaces (046c255)
- don't query iframe document if not ready (2b2ef32)
- don't throw undefined error if rapidly zooming browser in some environments (282a8b0)
- fix drag-and-drop when entire Puck component used inside an iframe (23db292)
- fix support for boolean values in select fields (c4a66ad)
- make draggable outlines consistent (9008b70)
- prevent grid layout issues in generated apps (5c05f94)
- reflect value changes made via resolveData in radio fields (9a7066f)
- remove peer dependencies causing warnings (041ca64)
- resolve security warning when additional iframes present (03ab0bd)
- use 100% width for Puck preview when iframe disabled (#414) (64303c8)
- use more custom interface friendly styles for iframes (e6e01c6)
Performance Improvements
- add API for disabling auto-scroll due to performance issues (3e5599e)
- batch load initial iframe styles (e585f20)
- don't lock main thread when iframe styles changed (e529e85)
- reuse host window styles in iframes (e7fe7e0)
New Contributors
Full Changelog: v0.14.1...v0.14.2
v0.14.1
v0.14.0
Puck v0.14.0 introduces the long awaited viewport switching feature, with drag-and-drop-friendly iframe rendering for full CSS media query support.
TLDR
- Viewport switching: Render your entire Puck preview inside an iframe for full CSS media query support. Override the viewports using the
viewports
API. - New field APIs: Array and number fields now accept
min
/max
params, and external fields can now render search filters using the familiarfields
API. - New component label API: Provide a custom label to use as your component name.
- Contentful field package: Easily load your Contentful content into Puck with this pre-configured field.
- Color, accessibility and keyboard improvements: A new 12-tint color palette for improved contrast, amongst various other accessibility fixes.
Highlights
📱 Viewport switching
Puck now supports viewport switching with full iframe rendering, without compromising the drag-and-drop experience. This was a significant effort that required patching the underlying drag-and-drop library to support iframes and CSS transforms, and introducing a new library for syncing styles between host and iframe.
Puck.Viewport.Switching.mp4
Viewports are enabled by default, and can be customized by passing an array to the viewports
API.
import { Puck } from "@measured/puck";
export function Editor() {
return (
<Puck
viewports={[
{
width: 1440,
height: "auto", // Optional height. Can be numeric or "auto". Defaults to "auto".
label: "My Viewport", // Optional. Shown in tooltip.
icon: <svg />, // Optional. Use lucide-icons to align with Puck UI.
},
]}
// ...
/>
);
}
🔢 New field APIs
Both number
and array
fields now accept min
and max
parameters, allowing you to control the minimum and maximum values (or number of values) for user input. Thanks to @jabba-the-bug and @jperasmus for their contributions.
const numberField = {
type: "number",
max: 10,
};
The new filterFields
API on external
fields allows you to render filters that are provided to your fetchList
method using the familiar fields
API.
const externalField = {
type: "external",
fetchList: async ({ filters }) => {
// Query content and apply filters
},
filterFields: {
rating: {
type: "number", // Render a number field
},
},
},
🔡 New component label API
Customize the name of your component with the new label
API. Thanks to @bwat-dev for contributing this feature.
const config = {
components: {
HeadingBlock: {
label: "Heading Block",
render: () => <h1>Hello, World</h1>,
},
},
};
Contentful field package
Use the new field-contentful
package to load content out of your Contentful space.
NB An issue occurred publishing @measured/[email protected]
. We will be introducing @measured/[email protected]
soon to address this.
import createFieldContentful from "@measured/puck-field-contentful";
const config = {
components: {
Example: {
fields: {
movie: createFieldContentful("movies", {
space: "my_space",
accessToken: "abcdefg123456",
}),
},
render: ({ data }) => {
return <p>{data?.fields.title || "No data selected"}</p>;
},
},
},
};
Breaking changes
iframes are enabled by default
Viewport rendering with iframes is enabled by default. If you need to disable this, you can pass iframes={{ enabled: false }}
to the Puck component.
Changelog
Features
- add "name" prop to componentItem override (45bbceb)
- add
min
andmax
APIs to array fields (53b7937) - add API to opt-out of iframes (03dd90b)
- add Contentful field package (d944288)
- add filter fields to ExternalFields (7a55053)
- add iframe support (1d0bf57)
- add
min
andmax
APIs to number fields (4932a6e) - add
selectedItem
convenience param to usePuck (c1224d0) - add viewport switching (ccf9149)
- enable mapping of table rows in external fields (d50c56e)
- expose history via usePuck hook (1b907cb)
- hide array Add button when array is readOnly (4e27c3f)
- improve touch, contrast & keyboard a11y (f975d87)
- refine UI for external field modal (6a2afa1)
- support custom component labels via the new label param (712fb8e)
- update to 12-tint color palette (d43da58)
- use InterVariable font (88532fb)
Bug Fixes
- avoid FOUC of side bars on mobile (83be956)
- correctly infer objectFields type from props (e8991cc)
- don't attempt to resolve data if component missing from config (cc7d391)
- don't flash nested DropZones on first drag (38c3dc4)
- don't unexpectedly show DropZone background (2001fa2)
- ensure font loads for ExternalFields (e9bca75)
- ensure heading-analyzer updates when content changes (d75df7a)
- ensure select and radio fields support read only arrays (cbdf66d)
- fix array field when used on root (95280e6)
- fix renderDropZone method in editor (2c738dd)
- lower opacity of DropZone background to support dark backgrounds (9a5c0b8)
- make getItemSummary optional on ExternalFields, as expected (26bc4ff)
- only import Puck CSS on editor pages (22a4182)
- prevent unexpected field behaviour when pressing "Enter" key (bf4f527)
- use strict return type for resolveData (777cd3c)
- vertically align field icons (fa92436)
New Contributors
- @jperasmus made their first contribution in #306
- @nguyendhst made their first contribution in #338
- @prashanthvdckap made their first contribution in #342
- @jabba-the-bug made their first contribution in #341
- @mfakhrys made their first contribution in #370
- @hjbrand made their first contribution in #368
- @bwat-dev made their first contribution in #367
- @jonduarte made their first contribution in #375
Full Changelog: v0.13.1...v0.14.0